Expand framework usage guides
This commit is contained in:
parent
cec65381dd
commit
9d950db890
@ -37,3 +37,4 @@ connect_timeout_ms = 5000
|
|||||||
|
|
||||||
- [API Reference](./api-reference.md)
|
- [API Reference](./api-reference.md)
|
||||||
- [Usage Examples](./usage-examples.md)
|
- [Usage Examples](./usage-examples.md)
|
||||||
|
- [Framework Module Guides](../../../docs/README.md)
|
||||||
|
|||||||
@ -33,3 +33,16 @@ Game systems should call the domain APIs instead of raw database operations:
|
|||||||
|
|
||||||
Large request and response payloads are routed through the transport layer when
|
Large request and response payloads are routed through the transport layer when
|
||||||
needed by `forge_server_addons_extension_fnc_extCall`.
|
needed by `forge_server_addons_extension_fnc_extCall`.
|
||||||
|
|
||||||
|
## Module Guides
|
||||||
|
|
||||||
|
- [Actor](../../../docs/ACTOR_USAGE_GUIDE.md)
|
||||||
|
- [Bank](../../../docs/BANK_USAGE_GUIDE.md)
|
||||||
|
- [CAD](../../../docs/CAD_USAGE_GUIDE.md)
|
||||||
|
- [Garage](../../../docs/GARAGE_USAGE_GUIDE.md)
|
||||||
|
- [Locker](../../../docs/LOCKER_USAGE_GUIDE.md)
|
||||||
|
- [Organization](../../../docs/ORG_USAGE_GUIDE.md)
|
||||||
|
- [Owned Storage](../../../docs/OWNED_STORAGE_USAGE_GUIDE.md)
|
||||||
|
- [Phone](../../../docs/PHONE_USAGE_GUIDE.md)
|
||||||
|
- [Store](../../../docs/STORE_USAGE_GUIDE.md)
|
||||||
|
- [Task](../../../docs/TASK_USAGE_GUIDE.md)
|
||||||
|
|||||||
127
docs/ACTOR_USAGE_GUIDE.md
Normal file
127
docs/ACTOR_USAGE_GUIDE.md
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
# Actor Usage Guide
|
||||||
|
|
||||||
|
The actor module stores persistent player character data: identity, loadout,
|
||||||
|
position, direction, stance, contact fields, state, holster status, rank, and
|
||||||
|
organization.
|
||||||
|
|
||||||
|
## Storage Model
|
||||||
|
|
||||||
|
Actor data is persisted through SurrealDB by the server extension.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"uid": "76561198000000000",
|
||||||
|
"name": "Player Name",
|
||||||
|
"loadout": {},
|
||||||
|
"position": [1234.5, 6789.0, 0.0],
|
||||||
|
"direction": 90.0,
|
||||||
|
"stance": "STAND",
|
||||||
|
"email": "0160000000@spearnet.mil",
|
||||||
|
"phone_number": "0160000000",
|
||||||
|
"state": "HEALTHY",
|
||||||
|
"holster": true,
|
||||||
|
"rank": null,
|
||||||
|
"organization": "default"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules validated by the Rust service:
|
||||||
|
|
||||||
|
- `uid` is authoritative from the command argument and must be a 17-digit Steam
|
||||||
|
UID.
|
||||||
|
- `name` is optional, but cannot be empty when set and cannot exceed 50
|
||||||
|
characters.
|
||||||
|
- `position` must be three finite numbers when set.
|
||||||
|
- `direction` must be in the `0.0 <= direction < 360.0` range.
|
||||||
|
- `email` must contain `@` and end with `.mil` when set.
|
||||||
|
- `phone_number` must start with `0160` and be 10 digits when set.
|
||||||
|
- Empty `phone_number`, `email`, or `organization` fields are filled on create.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
All commands are called on the `actor` group.
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `actor:get` | `uid` | Actor JSON. If no actor exists, returns a default actor but does not persist it. |
|
||||||
|
| `actor:create` | `uid`, `actor_json` | Persisted actor JSON. |
|
||||||
|
| `actor:update` | `uid`, `patch_json` | Updated actor JSON. |
|
||||||
|
| `actor:exists` | `uid` | `true` or `false`. |
|
||||||
|
| `actor:delete` | `uid` | `OK`. |
|
||||||
|
|
||||||
|
## Create an Actor
|
||||||
|
|
||||||
|
The `uid` field in the JSON is overwritten with the command UID.
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _actor = createHashMapFromArray [
|
||||||
|
["uid", getPlayerUID player],
|
||||||
|
["name", name player],
|
||||||
|
["loadout", getUnitLoadout player],
|
||||||
|
["position", getPosATL player],
|
||||||
|
["direction", getDir player],
|
||||||
|
["stance", stance player],
|
||||||
|
["email", ""],
|
||||||
|
["phone_number", ""],
|
||||||
|
["state", "HEALTHY"],
|
||||||
|
["holster", true],
|
||||||
|
["organization", "default"]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["actor:create", [
|
||||||
|
getPlayerUID player,
|
||||||
|
toJSON _actor
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Update an Actor
|
||||||
|
|
||||||
|
`actor:update` accepts a JSON object containing only fields to change.
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _patch = createHashMapFromArray [
|
||||||
|
["position", getPosATL player],
|
||||||
|
["direction", getDir player],
|
||||||
|
["stance", stance player],
|
||||||
|
["loadout", getUnitLoadout player]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["actor:update", [
|
||||||
|
getPlayerUID player,
|
||||||
|
toJSON _patch
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported patch fields are `name`, `position`, `direction`, `stance`, `email`,
|
||||||
|
`phone_number`, `state`, `holster`, `rank`, `organization`, and `loadout`.
|
||||||
|
`uid` is ignored.
|
||||||
|
|
||||||
|
## Hot State
|
||||||
|
|
||||||
|
The `actor:hot:*` commands keep a runtime copy of actor data and write it back
|
||||||
|
only when `actor:hot:save` runs.
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `actor:hot:init` | `uid` | Actor JSON from durable storage. |
|
||||||
|
| `actor:hot:get` | `uid` | Actor JSON. |
|
||||||
|
| `actor:hot:keys` | none | JSON array of hot actor UIDs. |
|
||||||
|
| `actor:hot:override` | `uid`, `actor_json` | Actor JSON. |
|
||||||
|
| `actor:hot:save` | `uid` | Current hot actor JSON and async durable save. |
|
||||||
|
| `actor:hot:remove` | `uid` | `OK`. |
|
||||||
|
|
||||||
|
Use hot state for frequently updated session data such as position and loadout.
|
||||||
|
Use durable commands for account creation and administrative changes.
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _result = "forge_server" callExtension ["actor:get", [getPlayerUID player]];
|
||||||
|
private _payload = _result select 0;
|
||||||
|
|
||||||
|
if (_payload find "Error:" == 0) exitWith {
|
||||||
|
systemChat format ["Actor error: %1", _payload];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _actor = fromJSON _payload;
|
||||||
|
```
|
||||||
169
docs/BANK_USAGE_GUIDE.md
Normal file
169
docs/BANK_USAGE_GUIDE.md
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
# Bank Usage Guide
|
||||||
|
|
||||||
|
The bank module stores player account balances, earnings, PINs, and transaction
|
||||||
|
strings. The hot-state API also owns the active banking workflows used by the
|
||||||
|
UI: deposit, withdraw, transfer, checkout charge, and PIN validation.
|
||||||
|
|
||||||
|
## Storage Model
|
||||||
|
|
||||||
|
Bank data is persisted through SurrealDB by the server extension.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"uid": "76561198000000000",
|
||||||
|
"name": "Player Name",
|
||||||
|
"bank": 1000.0,
|
||||||
|
"cash": 250.0,
|
||||||
|
"earnings": 0.0,
|
||||||
|
"pin": 1234,
|
||||||
|
"transactions": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules validated by the Rust service:
|
||||||
|
|
||||||
|
- `uid` is authoritative from the command argument.
|
||||||
|
- `name` cannot be empty.
|
||||||
|
- `bank` and `cash` cannot be negative.
|
||||||
|
- `pin` must be a four-digit number.
|
||||||
|
- Durable `bank:get` requires an existing bank account.
|
||||||
|
|
||||||
|
## Durable Commands
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `bank:create` | `uid`, `bank_json` | Persisted bank JSON. |
|
||||||
|
| `bank:get` | `uid` | Bank JSON. |
|
||||||
|
| `bank:update` | `uid`, `patch_json` | Updated bank JSON. |
|
||||||
|
| `bank:exists` | `uid` | `true` or `false`. |
|
||||||
|
| `bank:delete` | `uid` | `OK`. |
|
||||||
|
|
||||||
|
## Create an Account
|
||||||
|
|
||||||
|
The `uid` field in the JSON is overwritten with the command UID.
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _account = createHashMapFromArray [
|
||||||
|
["uid", getPlayerUID player],
|
||||||
|
["name", name player],
|
||||||
|
["bank", 0],
|
||||||
|
["cash", 0],
|
||||||
|
["earnings", 0],
|
||||||
|
["pin", 1234],
|
||||||
|
["transactions", []]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["bank:create", [
|
||||||
|
getPlayerUID player,
|
||||||
|
toJSON _account
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hot-State Commands
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `bank:hot:init` | `uid` | Bank JSON loaded into hot state. |
|
||||||
|
| `bank:hot:get` | `uid` | Bank JSON. |
|
||||||
|
| `bank:hot:override` | `uid`, `bank_json` | Bank JSON. |
|
||||||
|
| `bank:hot:patch` | `uid`, `patch_json` | `{ account, patch }`. |
|
||||||
|
| `bank:hot:deposit` | `uid`, `amount`, `context_json` | `{ account, patch }`. |
|
||||||
|
| `bank:hot:withdraw` | `uid`, `amount`, `context_json` | `{ account, patch }`. |
|
||||||
|
| `bank:hot:deposit_earnings` | `uid`, `amount`, `context_json` | `{ account, patch }`. |
|
||||||
|
| `bank:hot:transfer` | `source_uid`, `target_uid`, `amount`, `context_json` | Transfer result JSON. |
|
||||||
|
| `bank:hot:charge_checkout` | `uid`, `amount`, `context_json` | `{ account, patch }`. |
|
||||||
|
| `bank:hot:validate_pin` | `uid`, `pin`, `context_json` | `{}` on success. |
|
||||||
|
| `bank:hot:save` | `uid` | Current hot bank JSON and async durable save. |
|
||||||
|
| `bank:hot:remove` | `uid` | `OK`. |
|
||||||
|
|
||||||
|
Use hot-state commands for UI workflows. They return patch objects so the UI can
|
||||||
|
update only changed fields.
|
||||||
|
|
||||||
|
## Deposit and Withdraw
|
||||||
|
|
||||||
|
ATM sessions require `atmAuthorized: true`. Full bank sessions can set
|
||||||
|
`mode: "bank"`.
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _context = createHashMapFromArray [
|
||||||
|
["mode", "atm"],
|
||||||
|
["atmAuthorized", true]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _deposit = "forge_server" callExtension ["bank:hot:deposit", [
|
||||||
|
getPlayerUID player,
|
||||||
|
"100",
|
||||||
|
toJSON _context
|
||||||
|
]];
|
||||||
|
|
||||||
|
private _withdraw = "forge_server" callExtension ["bank:hot:withdraw", [
|
||||||
|
getPlayerUID player,
|
||||||
|
"50",
|
||||||
|
toJSON _context
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transfer
|
||||||
|
|
||||||
|
Transfers are only available from the full bank interface. `fromField` can be
|
||||||
|
`bank` or `cash`.
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _context = createHashMapFromArray [
|
||||||
|
["mode", "bank"],
|
||||||
|
["atmAuthorized", false],
|
||||||
|
["fromField", "bank"]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["bank:hot:transfer", [
|
||||||
|
getPlayerUID player,
|
||||||
|
_targetUid,
|
||||||
|
"250",
|
||||||
|
toJSON _context
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checkout Charge
|
||||||
|
|
||||||
|
Checkout charging supports `sourceField: "cash"` or `sourceField: "bank"`.
|
||||||
|
Set `commit` to `false` to preview the patch without saving.
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _context = createHashMapFromArray [
|
||||||
|
["sourceField", "bank"],
|
||||||
|
["commit", true]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["bank:hot:charge_checkout", [
|
||||||
|
getPlayerUID player,
|
||||||
|
"125",
|
||||||
|
toJSON _context
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## PIN Validation
|
||||||
|
|
||||||
|
PIN entry is only valid in ATM mode.
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _context = createHashMapFromArray [["mode", "atm"]];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["bank:hot:validate_pin", [
|
||||||
|
getPlayerUID player,
|
||||||
|
"1234",
|
||||||
|
toJSON _context
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _result = "forge_server" callExtension ["bank:hot:get", [getPlayerUID player]];
|
||||||
|
private _payload = _result select 0;
|
||||||
|
|
||||||
|
if (_payload find "Error:" == 0) exitWith {
|
||||||
|
systemChat format ["Bank error: %1", _payload];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _bank = fromJSON _payload;
|
||||||
|
```
|
||||||
183
docs/CAD_USAGE_GUIDE.md
Normal file
183
docs/CAD_USAGE_GUIDE.md
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
# CAD Usage Guide
|
||||||
|
|
||||||
|
The CAD module stores transient operational state for dispatch activity,
|
||||||
|
assignments, dispatch orders, support requests, group profiles, grouped views,
|
||||||
|
and hydrated UI payloads. CAD state is in-memory and follows the active server
|
||||||
|
or mission lifecycle.
|
||||||
|
|
||||||
|
## Data Model
|
||||||
|
|
||||||
|
Most CAD records are flexible JSON objects. The service normalizes important
|
||||||
|
IDs and returns structured mutation results for higher-level workflows.
|
||||||
|
|
||||||
|
Common generated IDs:
|
||||||
|
|
||||||
|
- Orders: `cad-order:<sequence>`
|
||||||
|
- Requests: `cad-request:<sequence>`
|
||||||
|
- Assignments usually share a task ID or order ID.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### Activity
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `cad:activity:append` | `activity_json` | `OK`. |
|
||||||
|
| `cad:activity:recent` | `limit` | Recent activity array JSON. |
|
||||||
|
|
||||||
|
### Assignments
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `cad:assignments:list` | none | Assignment array JSON. |
|
||||||
|
| `cad:assignments:assign` | `entry_id`, `assignment_json` | Assignment mutation result JSON. |
|
||||||
|
| `cad:assignments:acknowledge` | `entry_id`, `patch_json` | Assignment mutation result JSON. |
|
||||||
|
| `cad:assignments:decline` | `entry_id`, `patch_json` | Assignment mutation result JSON and removes assignment. |
|
||||||
|
| `cad:assignments:upsert` | `entry_id`, `assignment_json` | `OK`. |
|
||||||
|
| `cad:assignments:delete` | `entry_id` | `OK`. |
|
||||||
|
|
||||||
|
### Orders
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `cad:orders:list` | none | Order array JSON. |
|
||||||
|
| `cad:orders:create` | `order_seed_json` | Dispatch order mutation result JSON. |
|
||||||
|
| `cad:orders:create_from_context` | `context_json` | Dispatch order mutation result JSON. |
|
||||||
|
| `cad:orders:close` | `entry_id` | Dispatch order mutation result JSON and removes order/assignment. |
|
||||||
|
| `cad:orders:upsert` | `entry_id`, `order_json` | `OK`. |
|
||||||
|
| `cad:orders:delete` | `entry_id` | `OK`. |
|
||||||
|
|
||||||
|
### Requests
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `cad:requests:list` | none | Request array JSON. |
|
||||||
|
| `cad:requests:submit` | `request_json` | Request mutation result JSON. |
|
||||||
|
| `cad:requests:submit_from_context` | `context_json` | Request mutation result JSON. |
|
||||||
|
| `cad:requests:close` | `entry_id` | Request mutation result JSON and removes request. |
|
||||||
|
| `cad:requests:upsert` | `entry_id`, `request_json` | `OK`. |
|
||||||
|
| `cad:requests:delete` | `entry_id` | `OK`. |
|
||||||
|
|
||||||
|
### Profiles and Views
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `cad:profiles:list` | none | Profile array JSON. |
|
||||||
|
| `cad:profiles:update_from_context` | `context_json` | Profile mutation result JSON. |
|
||||||
|
| `cad:profiles:upsert` | `entry_id`, `profile_json` | `OK`. |
|
||||||
|
| `cad:profiles:delete` | `entry_id` | `OK`. |
|
||||||
|
| `cad:groups:build` | `groups_seed_json` | Group array JSON. |
|
||||||
|
| `cad:view:hydrate` | `hydrate_seed_json` | Hydrated CAD payload JSON. |
|
||||||
|
|
||||||
|
## Submit a Support Request
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _fields = createHashMapFromArray [
|
||||||
|
["pickup_location", "Grid 123456"],
|
||||||
|
["precedence", "urgent"],
|
||||||
|
["security", "secure"]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _context = createHashMapFromArray [
|
||||||
|
["type", "medevac_9line"],
|
||||||
|
["fields", _fields],
|
||||||
|
["groupId", "alpha"],
|
||||||
|
["groupCallsign", "Alpha 1-1"],
|
||||||
|
["submittedByUid", getPlayerUID player],
|
||||||
|
["submittedByName", name player],
|
||||||
|
["priority", "emergency"],
|
||||||
|
["position", getPosATL player],
|
||||||
|
["createdAt", diag_tickTime]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["cad:requests:submit_from_context", [
|
||||||
|
toJSON _context
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported priority values are `routine`, `priority`, and `emergency`. Unknown
|
||||||
|
values normalize to `priority`.
|
||||||
|
|
||||||
|
## Create a Dispatch Order
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _context = createHashMapFromArray [
|
||||||
|
["assigneeGroupId", "bravo"],
|
||||||
|
["assigneeGroupCallsign", "Bravo 1-1"],
|
||||||
|
["targetGroupId", "alpha"],
|
||||||
|
["targetGroupCallsign", "Alpha 1-1"],
|
||||||
|
["targetPosition", getPosATL player],
|
||||||
|
["createdByUid", getPlayerUID player],
|
||||||
|
["createdByName", name player],
|
||||||
|
["requestId", "cad-request:1"],
|
||||||
|
["requestType", "logreq"],
|
||||||
|
["requestTitle", "LOGREQ | Alpha 1-1"],
|
||||||
|
["requestSummary", "Ammo resupply requested"],
|
||||||
|
["requestFields", createHashMap],
|
||||||
|
["note", "Support Alpha 1-1 at current position."],
|
||||||
|
["priority", "priority"],
|
||||||
|
["createdAt", diag_tickTime]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["cad:orders:create_from_context", [
|
||||||
|
toJSON _context
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Assignment Workflow
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _assignment = createHashMapFromArray [
|
||||||
|
["groupId", "bravo"],
|
||||||
|
["assigneeGroupCallsign", "Bravo 1-1"],
|
||||||
|
["assignedByUid", getPlayerUID player],
|
||||||
|
["assignedByName", name player],
|
||||||
|
["assignedAt", diag_tickTime],
|
||||||
|
["state", "assigned"]
|
||||||
|
];
|
||||||
|
|
||||||
|
"forge_server" callExtension ["cad:assignments:assign", [
|
||||||
|
"task-123",
|
||||||
|
toJSON _assignment
|
||||||
|
]];
|
||||||
|
|
||||||
|
private _ack = createHashMapFromArray [
|
||||||
|
["state", "acknowledged"],
|
||||||
|
["acknowledgedByUid", getPlayerUID player],
|
||||||
|
["acknowledgedAt", diag_tickTime]
|
||||||
|
];
|
||||||
|
|
||||||
|
"forge_server" callExtension ["cad:assignments:acknowledge", [
|
||||||
|
"task-123",
|
||||||
|
toJSON _ack
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hydrate the CAD UI
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _session = createHashMapFromArray [
|
||||||
|
["uid", getPlayerUID player],
|
||||||
|
["orgId", "default"],
|
||||||
|
["isDispatcher", true],
|
||||||
|
["groupId", "alpha"],
|
||||||
|
["isLeader", true]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _seed = createHashMapFromArray [
|
||||||
|
["groups", _liveGroups],
|
||||||
|
["activeTasks", _activeTasks],
|
||||||
|
["session", _session]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["cad:view:hydrate", [toJSON _seed]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _payload = _result select 0;
|
||||||
|
if (_payload find "Error:" == 0) exitWith {
|
||||||
|
systemChat format ["CAD error: %1", _payload];
|
||||||
|
};
|
||||||
|
```
|
||||||
@ -1,274 +1,212 @@
|
|||||||
# Garage System Integration Guide
|
# Garage Usage Guide
|
||||||
|
|
||||||
## Overview
|
The garage module stores physical player vehicles. Each record keeps the
|
||||||
|
vehicle classname, generated plate UUID, fuel, overall damage, and detailed hit
|
||||||
|
point damage.
|
||||||
|
|
||||||
The garage system provides complete vehicle storage, retrieval, and management for Arma 3 players. Each player can store multiple vehicles with full damage and hit point tracking.
|
## Storage Model
|
||||||
|
|
||||||
## Data Storage
|
Garage data is persisted through SurrealDB by the server extension.
|
||||||
|
|
||||||
- Each player's garage is persisted by the server extension through SurrealDB.
|
```json
|
||||||
- The map is keyed by the vehicle's unique plate (UUID)
|
{
|
||||||
- Each vehicle tracks: plate (UUID), classname, overall damage, fuel, and detailed hit points
|
"plate-uuid": {
|
||||||
- **Plates are auto-generated** when vehicles are added via `garage:add`
|
"plate": "plate-uuid",
|
||||||
- **Empty garages are auto-created** when a player first fetches their garage
|
"classname": "B_Quadbike_01_F",
|
||||||
|
"fuel": 1.0,
|
||||||
## Extension Commands
|
"damage": 0.0,
|
||||||
|
"hit_points": {
|
||||||
All commands are accessed via the `garage` group:
|
"names": ["hitengine"],
|
||||||
|
"selections": ["engine_hitpoint"],
|
||||||
### Create Garage
|
"values": [0.0]
|
||||||
|
}
|
||||||
Creates a new empty garage for a player. Should be called when initializing a new player.
|
}
|
||||||
|
}
|
||||||
```sqf
|
|
||||||
private _result = "forge_server" callExtension ["garage:create", [getPlayerUID player]];
|
|
||||||
private _emptyGarage = fromJSON (_result select 0);
|
|
||||||
// Returns: {} (empty map)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get Garage
|
Rules validated by the Rust service:
|
||||||
|
|
||||||
Retrieves all vehicles in a player's garage.
|
- A player garage can contain up to 5 vehicles.
|
||||||
|
- `garage:add` generates a UUID plate automatically.
|
||||||
|
- `fuel`, `damage`, and every hit point value must be between `0.0` and `1.0`.
|
||||||
|
- `hit_points.names`, `hit_points.selections`, and `hit_points.values` must have
|
||||||
|
the same length.
|
||||||
|
- `garage:get`, `garage:patch`, and `garage:remove` require an existing garage.
|
||||||
|
- `garage:add` creates an empty garage automatically when one does not exist.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
All commands are called on the `garage` group.
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `garage:create` | `uid` | Empty vehicle map as JSON. |
|
||||||
|
| `garage:get` | `uid` | Vehicle map as JSON. |
|
||||||
|
| `garage:add` | `uid`, `vehicle_json` | Updated vehicle map as JSON. |
|
||||||
|
| `garage:update` | `uid`, `vehicles_json` | Replaced vehicle map as JSON. |
|
||||||
|
| `garage:patch` | `uid`, `patch_json` | Updated vehicle map as JSON. |
|
||||||
|
| `garage:remove` | `uid`, `remove_json` | Updated vehicle map as JSON. |
|
||||||
|
| `garage:delete` | `uid` | `OK`. |
|
||||||
|
| `garage:exists` | `uid` | `true` or `false`. |
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Every command returns a string payload. Always check for the `Error:` prefix
|
||||||
|
before parsing JSON.
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
private _result = "forge_server" callExtension ["garage:get", [getPlayerUID player]];
|
private _result = "forge_server" callExtension ["garage:get", [getPlayerUID player]];
|
||||||
private _garageMap = fromJSON (_result select 0);
|
private _payload = _result select 0;
|
||||||
// Returns: {"plate_uuid": {"plate":"plate_uuid","classname":"...","damage":0.0,"hit_points":{...}}, ...}
|
|
||||||
|
if (_payload find "Error:" == 0) exitWith {
|
||||||
|
systemChat format ["Garage error: %1", _payload];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _garage = fromJSON _payload;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Add Vehicle
|
## Add a Vehicle
|
||||||
|
|
||||||
Adds a new vehicle to the garage. The system automatically generates a unique plate (UUID) for the vehicle.
|
`garage:add` requires `classname`, `fuel`, `damage`, and `hit_points`.
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
private _data = createHashMapFromArray [
|
private _hitPointData = getAllHitPointsDamage _vehicle;
|
||||||
["classname", "B_Quadbike_01_F"],
|
private _hitPoints = createHashMapFromArray [
|
||||||
["fuel", 1.0],
|
["names", _hitPointData select 0],
|
||||||
["damage", 0.0],
|
["selections", _hitPointData select 1],
|
||||||
["hit_points", createHashMap] // Optional hit points map
|
["values", _hitPointData select 2]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _vehicleData = createHashMapFromArray [
|
||||||
|
["classname", typeOf _vehicle],
|
||||||
|
["fuel", fuel _vehicle],
|
||||||
|
["damage", damage _vehicle],
|
||||||
|
["hit_points", _hitPoints]
|
||||||
];
|
];
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["garage:add", [
|
private _result = "forge_server" callExtension ["garage:add", [
|
||||||
getPlayerUID player,
|
getPlayerUID player,
|
||||||
toJSON _data
|
toJSON _vehicleData
|
||||||
]];
|
]];
|
||||||
|
|
||||||
private _updatedGarage = fromJSON (_result select 0);
|
private _payload = _result select 0;
|
||||||
// Returns updated garage map
|
if (_payload find "Error:" == 0) exitWith {
|
||||||
|
hint format ["Failed to store vehicle: %1", _payload];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _garage = fromJSON _payload;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Update Garage (Sync)
|
The returned value is a hash map keyed by generated plate. To find the newly
|
||||||
|
stored vehicle, compare returned keys before and after the add, or search by
|
||||||
Updates the entire garage state. Useful for syncing changes made locally.
|
classname if your workflow guarantees a unique pending vehicle.
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
// _garageMap is the local HashMap of vehicles
|
private _storedPlate = "";
|
||||||
private _result = "forge_server" callExtension ["garage:update", [
|
{
|
||||||
getPlayerUID player,
|
private _vehicleRecord = _garage get _x;
|
||||||
toJSON _garageMap
|
if ((_vehicleRecord get "classname") == typeOf _vehicle) then {
|
||||||
]];
|
_storedPlate = _x;
|
||||||
|
};
|
||||||
private _updatedGarage = fromJSON (_result select 0);
|
} forEach keys _garage;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Patch Vehicle
|
## Patch a Vehicle
|
||||||
|
|
||||||
Updates specific fields of a vehicle without sending the entire garage. Useful for frequent updates like fuel or damage.
|
`garage:patch` updates selected fields for one plate. The `plate` field is
|
||||||
|
required. `fuel`, `damage`, and `hit_points` are optional.
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
private _plate = "some-plate-uuid";
|
private _patch = createHashMapFromArray [
|
||||||
private _data = createHashMapFromArray [
|
["plate", _vehicle getVariable ["forge_garage_plate", ""]],
|
||||||
["plate", _plate],
|
["fuel", fuel _vehicle],
|
||||||
["fuel", 0.8],
|
["damage", damage _vehicle]
|
||||||
["damage", 0.1],
|
|
||||||
// "hit_points" is optional
|
|
||||||
];
|
];
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["garage:patch", [
|
private _result = "forge_server" callExtension ["garage:patch", [
|
||||||
getPlayerUID player,
|
getPlayerUID player,
|
||||||
toJSON _data
|
toJSON _patch
|
||||||
]];
|
]];
|
||||||
|
|
||||||
private _updatedGarage = fromJSON (_result select 0);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Remove Vehicle
|
## Remove a Vehicle
|
||||||
|
|
||||||
Removes a specific vehicle from the garage by plate number.
|
`garage:remove` expects JSON with a `plate` field.
|
||||||
Note: If using the GarageStore, removing a vehicle locally and saving/syncing will also remove it from the server.
|
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
private _plate = "some-plate-uuid";
|
private _remove = createHashMapFromArray [
|
||||||
|
|
||||||
private _data = createHashMapFromArray [
|
|
||||||
["plate", _plate]
|
["plate", _plate]
|
||||||
];
|
];
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["garage:remove", [
|
private _result = "forge_server" callExtension ["garage:remove", [
|
||||||
getPlayerUID player,
|
getPlayerUID player,
|
||||||
toJSON _data
|
toJSON _remove
|
||||||
]];
|
]];
|
||||||
|
|
||||||
private _updatedGarage = fromJSON (_result select 0);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Delete Garage
|
## Spawn a Stored Vehicle
|
||||||
|
|
||||||
Permanently deletes all vehicles from a player's garage.
|
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
private _result = "forge_server" callExtension ["garage:delete", [getPlayerUID player]];
|
fnc_spawnGarageVehicle = {
|
||||||
// Returns: "OK" or "Error: ..."
|
params ["_plate"];
|
||||||
```
|
|
||||||
|
|
||||||
### Check Existence
|
|
||||||
|
|
||||||
Checks if a player has any vehicles in their garage.
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _result = "forge_server" callExtension ["garage:exists", [getPlayerUID player]];
|
|
||||||
private _exists = (_result select 0) == "true";
|
|
||||||
```
|
|
||||||
|
|
||||||
## Complete Integration Example
|
|
||||||
|
|
||||||
### Storing a Vehicle
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
fnc_storeVehicle = {
|
|
||||||
params ["_vehicle"];
|
|
||||||
|
|
||||||
// Get vehicle data
|
|
||||||
private _hitPointsData = getAllHitPointsDamage _vehicle;
|
|
||||||
private _hitPoints = createHashMapFromArray [
|
|
||||||
["names", _hitPointsData select 0],
|
|
||||||
["selections", _hitPointsData select 1],
|
|
||||||
["values", _hitPointsData select 2]
|
|
||||||
];
|
|
||||||
|
|
||||||
// Create data hashMap
|
|
||||||
private _data = createHashMapFromArray [
|
|
||||||
["classname", typeOf _vehicle],
|
|
||||||
["damage", damage _vehicle],
|
|
||||||
["hit_points", _hitPoints]
|
|
||||||
];
|
|
||||||
|
|
||||||
// Add to garage (plate is auto-generated)
|
|
||||||
private _result = "forge_server" callExtension ["garage:add", [
|
|
||||||
getPlayerUID player,
|
|
||||||
toJSON _data
|
|
||||||
]];
|
|
||||||
|
|
||||||
// Check for error
|
|
||||||
if ((_result select 0) find "Error:" == 0) exitWith {
|
|
||||||
hint format ["Failed to store vehicle: %1", _result select 0];
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse result to get the new vehicle's plate
|
|
||||||
private _updatedGarage = fromJSON (_result select 0);
|
|
||||||
private _newVehicle = _updatedGarage select ((count _updatedGarage) - 1);
|
|
||||||
private _assignedPlate = _newVehicle get "plate";
|
|
||||||
|
|
||||||
// Delete the actual vehicle from game world
|
|
||||||
deleteVehicle _vehicle;
|
|
||||||
|
|
||||||
hint format ["Vehicle %1 stored with plate %2!", _data get "classname", _assignedPlate];
|
|
||||||
true
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Retrieving and Spawning a Vehicle
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
fnc_spawnVehicleFromGarage = {
|
|
||||||
params ["_vehicleIndex"];
|
|
||||||
|
|
||||||
// Get garage
|
|
||||||
private _result = "forge_server" callExtension ["garage:get", [getPlayerUID player]];
|
private _result = "forge_server" callExtension ["garage:get", [getPlayerUID player]];
|
||||||
private _garage = fromJSON (_result select 0);
|
private _payload = _result select 0;
|
||||||
|
|
||||||
// Validate index
|
if (_payload find "Error:" == 0) exitWith {
|
||||||
if (_vehicleIndex >= count _garage) exitWith {
|
hint format ["Failed to load garage: %1", _payload];
|
||||||
hint "Invalid vehicle index!";
|
|
||||||
objNull
|
objNull
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get vehicle data
|
private _garage = fromJSON _payload;
|
||||||
private _vehicleData = _garage select _vehicleIndex;
|
private _vehicleData = _garage getOrDefault [_plate, createHashMap];
|
||||||
private _classname = _vehicleData get "classname";
|
if (_vehicleData isEqualTo createHashMap) exitWith {
|
||||||
private _storedDamage = _vehicleData get "damage";
|
hint "Vehicle plate was not found in your garage.";
|
||||||
private _hitPoints = _vehicleData get "hit_points";
|
objNull
|
||||||
|
};
|
||||||
|
|
||||||
// Spawn vehicle
|
private _vehicle = (_vehicleData get "classname") createVehicle (player getPos [10, getDir player]);
|
||||||
private _spawnPos = player getPos [10, getDir player];
|
_vehicle setFuel (_vehicleData getOrDefault ["fuel", 1]);
|
||||||
private _vehicle = _classname createVehicle _spawnPos;
|
_vehicle setDamage (_vehicleData getOrDefault ["damage", 0]);
|
||||||
|
_vehicle setVariable ["forge_garage_plate", _plate, true];
|
||||||
|
|
||||||
// Apply damage
|
private _hitPoints = _vehicleData getOrDefault ["hit_points", createHashMap];
|
||||||
_vehicle setDamage _storedDamage;
|
private _names = _hitPoints getOrDefault ["names", []];
|
||||||
|
private _values = _hitPoints getOrDefault ["values", []];
|
||||||
// Apply hit point damage
|
|
||||||
private _names = _hitPoints get "names";
|
|
||||||
private _values = _hitPoints get "values";
|
|
||||||
|
|
||||||
{
|
{
|
||||||
_vehicle setHitPointDamage [_x, _values select _forEachIndex];
|
_vehicle setHitPointDamage [_x, _values select _forEachIndex];
|
||||||
} forEach _names;
|
} forEach _names;
|
||||||
|
|
||||||
// Store plate on vehicle for future updates
|
private _remove = createHashMapFromArray [["plate", _plate]];
|
||||||
private _plate = _vehicleData get "plate";
|
"forge_server" callExtension ["garage:remove", [getPlayerUID player, toJSON _remove]];
|
||||||
_vehicle setVariable ["garagePlate", _plate];
|
|
||||||
|
|
||||||
// Remove from garage
|
|
||||||
private _removeData = createHashMapFromArray [["plate", _plate]];
|
|
||||||
"forge_server" callExtension ["garage:remove", [getPlayerUID player, toJSON _removeData]];
|
|
||||||
|
|
||||||
hint format ["Vehicle %1 spawned with plate %2!", _classname, _plate];
|
|
||||||
_vehicle
|
_vehicle
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Hit Points Data Format
|
## Hot State
|
||||||
|
|
||||||
The hit points data matches Arma 3's `getAllHitPointsDamage` return format:
|
The `garage:hot:*` commands keep a runtime copy of a player's garage and write
|
||||||
|
it back only when `garage:hot:save` runs.
|
||||||
|
|
||||||
```json
|
| Command | Arguments | Returns |
|
||||||
{
|
| --- | --- | --- |
|
||||||
"names": ["hitlfwheel", "hitlf2wheel", "hitfuel", "hitengine", "hitbody", ...],
|
| `garage:hot:init` | `uid` | Vehicle map as JSON. |
|
||||||
"selections": ["wheel_1_1_steering", "wheel_1_2_steering", "fuel_hitpoint", "engine_hitpoint", "body_hitpoint", ...],
|
| `garage:hot:get` | `uid` | Vehicle map as JSON. |
|
||||||
"values": [0, 0, 0.1, 0.2, 0, ...]
|
| `garage:hot:override` | `uid`, `vehicles_json` | Vehicle map as JSON. |
|
||||||
}
|
| `garage:hot:add` | `uid`, `vehicle_json` | Vehicle map as JSON. |
|
||||||
```
|
| `garage:hot:remove_vehicle` | `uid`, `remove_json` | Vehicle map as JSON. |
|
||||||
|
| `garage:hot:save` | `uid` | Current hot vehicle map as JSON. |
|
||||||
|
| `garage:hot:remove` | `uid` | `OK`. |
|
||||||
|
|
||||||
- **names**: Hit point identifiers
|
Use hot state for session-heavy vehicle workflows. Use the durable commands for
|
||||||
- **selections**: Physical selection names (can be empty strings `""`)
|
simple store/retrieve operations.
|
||||||
- **values**: Damage values from 0.0 (no damage) to 1.0 (destroyed)
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
All commands return errors in the format `"Error: <message>"`. Always check for this:
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _result = "forge_server" callExtension ["garage:get", [getPlayerUID player]];
|
|
||||||
private _data = _result select 0;
|
|
||||||
|
|
||||||
if (_data find "Error:" == 0) then {
|
|
||||||
// Handle error
|
|
||||||
systemChat format ["Garage error: %1", _data];
|
|
||||||
} else {
|
|
||||||
// Parse and use data
|
|
||||||
private _garage = fromJSON _data;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|
||||||
1. **Track Vehicle Plates**: When spawning vehicles from the garage, store the plate (UUID) as a variable so you can update them later:
|
- Store the generated plate on spawned vehicles with `setVariable`.
|
||||||
```sqf
|
- Use `garage:patch` for frequent fuel and damage syncs.
|
||||||
_vehicle setVariable ["garagePlate", _plate];
|
- Use `garage:update` only when replacing the whole vehicle map intentionally.
|
||||||
```
|
- Do not delete the world vehicle until `garage:add` succeeds.
|
||||||
2. **Auto-Creation**: The system automatically creates an empty garage for new players on first access
|
- Treat vehicle maps as hash maps keyed by plate, not arrays.
|
||||||
3. **Validate Before Storage**: Check that vehicles are in good condition before allowing storage
|
|
||||||
4. **Limit Garage Size**: Implement a maximum number of vehicles per player
|
|
||||||
5. **Regular Updates**: Update vehicle damage periodically while in use
|
|
||||||
6. **Clean Deleted Vehicles**: Remove vehicles from garage when they're destroyed or sold
|
|
||||||
|
|||||||
@ -1,228 +1,203 @@
|
|||||||
# Locker System Integration Guide
|
# Locker Usage Guide
|
||||||
|
|
||||||
## Overview
|
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).
|
||||||
|
|
||||||
The locker system provides persistent item storage for Arma 3 players. Each player can store multiple items (weapons, magazines, equipment) with quantity tracking.
|
## Storage Model
|
||||||
|
|
||||||
## Data Storage
|
Locker data is persisted through SurrealDB by the server extension.
|
||||||
|
|
||||||
- Each player's locker is persisted by the server extension through SurrealDB.
|
```json
|
||||||
- The map is keyed by the item's **classname** (String)
|
{
|
||||||
- Each item tracks: `category`, `classname`, and `amount`
|
"arifle_MX_F": {
|
||||||
- **Maximum Capacity**: 25 unique items per locker
|
"category": "weapon",
|
||||||
- **Empty lockers are auto-created** when a player first fetches their locker if it doesn't exist
|
"classname": "arifle_MX_F",
|
||||||
|
"amount": 1
|
||||||
## Extension Commands
|
}
|
||||||
|
}
|
||||||
All commands are accessed via the `locker` group:
|
|
||||||
|
|
||||||
### Create Locker
|
|
||||||
|
|
||||||
Creates a new empty locker for a player. Should be called when initializing a new player if one doesn't exist.
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _result = "forge_server" callExtension ["locker:create", [getPlayerUID player]];
|
|
||||||
// Returns: {} (empty map) or Error message
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get Locker
|
Rules validated by the Rust service:
|
||||||
|
|
||||||
Retrieves all items in a player's locker.
|
- 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
|
```sqf
|
||||||
private _result = "forge_server" callExtension ["locker:get", [getPlayerUID player]];
|
private _result = "forge_server" callExtension ["locker:get", [getPlayerUID player]];
|
||||||
private _lockerMap = fromJSON (_result select 0);
|
private _payload = _result select 0;
|
||||||
// Returns: {"arifle_MX_F": {"classname":"arifle_MX_F","category":"Weapon","amount":1}, ...}
|
|
||||||
|
if (_payload find "Error:" == 0) exitWith {
|
||||||
|
systemChat format ["Locker error: %1", _payload];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _locker = fromJSON _payload;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Add Item
|
## Add an Item
|
||||||
|
|
||||||
Adds a new item to the locker or updates the amount if it already exists.
|
`locker:add` creates or overwrites one classname entry.
|
||||||
**Checks capacity limit (25 items).**
|
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
private _data = createHashMapFromArray [
|
private _item = createHashMapFromArray [
|
||||||
|
["category", "weapon"],
|
||||||
["classname", "arifle_MX_F"],
|
["classname", "arifle_MX_F"],
|
||||||
["category", "Weapon"],
|
|
||||||
["amount", 1]
|
["amount", 1]
|
||||||
];
|
];
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["locker:add", [
|
private _result = "forge_server" callExtension ["locker:add", [
|
||||||
getPlayerUID player,
|
getPlayerUID player,
|
||||||
toJSON _data
|
toJSON _item
|
||||||
]];
|
]];
|
||||||
|
|
||||||
private _updatedLocker = fromJSON (_result select 0);
|
private _payload = _result select 0;
|
||||||
// Returns updated locker map
|
if (_payload find "Error:" == 0) exitWith {
|
||||||
|
hint format ["Failed to store item: %1", _payload];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _locker = fromJSON _payload;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Update Locker (Sync)
|
## Patch an Amount
|
||||||
|
|
||||||
Updates the entire locker state. Useful for syncing changes made locally (e.g., bulk moves).
|
`locker:patch` currently patches the `amount` field for an existing classname.
|
||||||
**Replaces the entire locker content.**
|
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
// _lockerMap is the local HashMap of items
|
private _patch = createHashMapFromArray [
|
||||||
private _result = "forge_server" callExtension ["locker:update", [
|
["classname", "arifle_MX_F"],
|
||||||
getPlayerUID player,
|
["amount", 5]
|
||||||
toJSON _lockerMap
|
|
||||||
]];
|
|
||||||
|
|
||||||
private _updatedLocker = fromJSON (_result select 0);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Patch Item
|
|
||||||
|
|
||||||
Updates specific fields (currently `amount`) of an existing item without sending the entire locker.
|
|
||||||
**Efficient for quantity changes.**
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _classname = "arifle_MX_F";
|
|
||||||
private _data = createHashMapFromArray [
|
|
||||||
["classname", _classname],
|
|
||||||
["amount", 5] // New amount
|
|
||||||
];
|
];
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["locker:patch", [
|
private _result = "forge_server" callExtension ["locker:patch", [
|
||||||
getPlayerUID player,
|
getPlayerUID player,
|
||||||
toJSON _data
|
toJSON _patch
|
||||||
]];
|
]];
|
||||||
|
|
||||||
private _updatedLocker = fromJSON (_result select 0);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Remove Item
|
## Remove an Item
|
||||||
|
|
||||||
Removes a specific item from the locker by classname.
|
`locker:remove` takes the classname as the second argument.
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
private _classname = "arifle_MX_F";
|
|
||||||
private _data = createHashMapFromArray [
|
|
||||||
["classname", _classname]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["locker:remove", [
|
private _result = "forge_server" callExtension ["locker:remove", [
|
||||||
getPlayerUID player,
|
getPlayerUID player,
|
||||||
toJSON _data
|
"arifle_MX_F"
|
||||||
]];
|
]];
|
||||||
|
|
||||||
private _updatedLocker = fromJSON (_result select 0);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Delete Locker
|
## Retrieve an Item
|
||||||
|
|
||||||
Permanently deletes all items from a player's locker.
|
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
private _result = "forge_server" callExtension ["locker:delete", [getPlayerUID player]];
|
fnc_retrieveLockerItem = {
|
||||||
// Returns: "OK" or "Error: ..."
|
params ["_classname"];
|
||||||
```
|
|
||||||
|
|
||||||
### Check Existence
|
private _result = "forge_server" callExtension ["locker:get", [getPlayerUID player]];
|
||||||
|
private _payload = _result select 0;
|
||||||
|
|
||||||
Checks if a player has a locker created.
|
if (_payload find "Error:" == 0) exitWith {
|
||||||
|
hint format ["Failed to load locker: %1", _payload];
|
||||||
```sqf
|
|
||||||
private _result = "forge_server" callExtension ["locker:exists", [getPlayerUID player]];
|
|
||||||
private _exists = (_result select 0) == "true";
|
|
||||||
```
|
|
||||||
|
|
||||||
## Complete Integration Example
|
|
||||||
|
|
||||||
### Storing an Item
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
fnc_storeCurrentWeapon = {
|
|
||||||
private _weapon = currentWeapon player;
|
|
||||||
if (_weapon == "") exitWith { hint "No weapon in hand!"; };
|
|
||||||
|
|
||||||
// Create item data
|
|
||||||
private _data = createHashMapFromArray [
|
|
||||||
["classname", _weapon],
|
|
||||||
["category", "Weapon"],
|
|
||||||
["amount", 1]
|
|
||||||
];
|
|
||||||
|
|
||||||
// Add to locker
|
|
||||||
private _result = "forge_server" callExtension ["locker:add", [
|
|
||||||
getPlayerUID player,
|
|
||||||
toJSON _data
|
|
||||||
]];
|
|
||||||
|
|
||||||
// Check for error (e.g., full locker)
|
|
||||||
if ((_result select 0) find "Error:" == 0) exitWith {
|
|
||||||
hint format ["Failed to store item: %1", _result select 0];
|
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
// Remove weapon from player
|
private _locker = fromJSON _payload;
|
||||||
player removeWeapon _weapon;
|
private _item = _locker getOrDefault [_classname, createHashMap];
|
||||||
hint format ["Stored %1 in locker!", _weapon];
|
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
|
true
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Retrieving an Item
|
## Replace the Whole Locker
|
||||||
|
|
||||||
|
`locker:update` replaces the whole item map. Use it for explicit bulk syncs,
|
||||||
|
not single-item changes.
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
fnc_retrieveItem = {
|
private _items = createHashMapFromArray [
|
||||||
params ["_classname"];
|
["arifle_MX_F", createHashMapFromArray [
|
||||||
|
["category", "weapon"],
|
||||||
|
["classname", "arifle_MX_F"],
|
||||||
|
["amount", 1]
|
||||||
|
]]
|
||||||
|
];
|
||||||
|
|
||||||
// Get locker
|
private _result = "forge_server" callExtension ["locker:update", [
|
||||||
private _result = "forge_server" callExtension ["locker:get", [getPlayerUID player]];
|
getPlayerUID player,
|
||||||
private _locker = fromJSON (_result select 0);
|
toJSON _items
|
||||||
|
]];
|
||||||
// Check if item exists
|
|
||||||
private _item = _locker getOrDefault [_classname, locationNull];
|
|
||||||
if (_item isEqualTo locationNull) exitWith {
|
|
||||||
hint "Item not found in locker!";
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get amount
|
|
||||||
private _amount = _item get "amount";
|
|
||||||
if (_amount <= 0) exitWith {
|
|
||||||
// Should not happen, but safe to handle
|
|
||||||
hint "Item out of stock!";
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add to player
|
|
||||||
if (player canAdd _classname) then {
|
|
||||||
player addItem _classname;
|
|
||||||
|
|
||||||
// Decrement amount or remove if 0
|
|
||||||
if (_amount > 1) then {
|
|
||||||
private _patchData = createHashMapFromArray [
|
|
||||||
["classname", _classname],
|
|
||||||
["amount", _amount - 1]
|
|
||||||
];
|
|
||||||
"forge_server" callExtension ["locker:patch", [getPlayerUID player, toJSON _patchData]];
|
|
||||||
} else {
|
|
||||||
private _removeData = createHashMapFromArray [
|
|
||||||
["classname", _classname]
|
|
||||||
];
|
|
||||||
"forge_server" callExtension ["locker:remove", [getPlayerUID player, toJSON _removeData]];
|
|
||||||
};
|
|
||||||
|
|
||||||
hint format ["Retrieved %1 from locker!", _classname];
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
hint "Not enough space in inventory!";
|
|
||||||
false
|
|
||||||
};
|
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Error Handling
|
## Hot State
|
||||||
|
|
||||||
All commands return errors in the format `"Error: <message>"`.
|
The `locker:hot:*` commands keep a runtime copy of a player's locker and write
|
||||||
|
it back only when `locker:hot:save` runs.
|
||||||
|
|
||||||
```sqf
|
| Command | Arguments | Returns |
|
||||||
private _result = "forge_server" callExtension ["locker:add", ...];
|
| --- | --- | --- |
|
||||||
private _data = _result select 0;
|
| `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`. |
|
||||||
|
|
||||||
if (_data find "Error:" == 0) then {
|
Use hot state for session-heavy locker workflows. Use the durable commands for
|
||||||
systemChat format ["Locker error: %1", _data];
|
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.
|
||||||
|
|||||||
@ -30,8 +30,20 @@ docs/ Framework-level documentation
|
|||||||
| Phone | Contacts, messages, and email state. | `arma/client/addons/phone` | `arma/server/addons/phone` | `lib/models/src/phone.rs`, `lib/services/src/phone.rs` | `phone:*` |
|
| Phone | Contacts, messages, and email state. | `arma/client/addons/phone` | `arma/server/addons/phone` | `lib/models/src/phone.rs`, `lib/services/src/phone.rs` | `phone:*` |
|
||||||
| Store | Store catalog checkout workflows and checkout charging integration. | `arma/client/addons/store` | `arma/server/addons/store` | `lib/models/src/store.rs`, `lib/services/src/store.rs` | `store:checkout` |
|
| Store | Store catalog checkout workflows and checkout charging integration. | `arma/client/addons/store` | `arma/server/addons/store` | `lib/models/src/store.rs`, `lib/services/src/store.rs` | `store:checkout` |
|
||||||
| Task | Mission/task catalog, ownership, status, reward context, and task counters. | none | `arma/server/addons/task` | `lib/models/src/task.rs`, `lib/services/src/task.rs` | `task:*` |
|
| Task | Mission/task catalog, ownership, status, reward context, and task counters. | none | `arma/server/addons/task` | `lib/models/src/task.rs`, `lib/services/src/task.rs` | `task:*` |
|
||||||
| Owned Garage | Organization or owner-scoped vehicle storage. | via garage/org UI | server extension only | `lib/models/src/v_garage.rs`, `lib/services/src/v_garage.rs` | `owned:garage:*` |
|
| Owned Garage | Organization or owner-scoped vehicle unlock storage. | via garage/org UI | server extension only | `lib/models/src/v_garage.rs`, `lib/services/src/v_garage.rs` | `owned:garage:*` |
|
||||||
| Owned Locker | Organization or owner-scoped item storage. | via locker/org UI | server extension only | `lib/models/src/v_locker.rs`, `lib/services/src/v_locker.rs` | `owned:locker:*` |
|
| Owned Locker | Organization or owner-scoped arsenal unlock storage. | via locker/org UI | server extension only | `lib/models/src/v_locker.rs`, `lib/services/src/v_locker.rs` | `owned:locker:*` |
|
||||||
|
|
||||||
|
Guides:
|
||||||
|
[Actor](./ACTOR_USAGE_GUIDE.md),
|
||||||
|
[Bank](./BANK_USAGE_GUIDE.md),
|
||||||
|
[CAD](./CAD_USAGE_GUIDE.md),
|
||||||
|
[Garage](./GARAGE_USAGE_GUIDE.md),
|
||||||
|
[Locker](./LOCKER_USAGE_GUIDE.md),
|
||||||
|
[Organization](./ORG_USAGE_GUIDE.md),
|
||||||
|
[Owned Storage](./OWNED_STORAGE_USAGE_GUIDE.md),
|
||||||
|
[Phone](./PHONE_USAGE_GUIDE.md),
|
||||||
|
[Store](./STORE_USAGE_GUIDE.md),
|
||||||
|
[Task](./TASK_USAGE_GUIDE.md).
|
||||||
|
|
||||||
## Infrastructure Modules
|
## Infrastructure Modules
|
||||||
|
|
||||||
|
|||||||
232
docs/ORG_USAGE_GUIDE.md
Normal file
232
docs/ORG_USAGE_GUIDE.md
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
# Organization Usage Guide
|
||||||
|
|
||||||
|
The organization module stores organization records, members, assets, fleet
|
||||||
|
entries, and credit lines. Durable commands manage persisted records directly.
|
||||||
|
Hot-state commands support the active organization UI workflows.
|
||||||
|
|
||||||
|
## Storage Model
|
||||||
|
|
||||||
|
Core organization:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "default",
|
||||||
|
"owner": "server",
|
||||||
|
"name": "Default Organization",
|
||||||
|
"funds": 0.0,
|
||||||
|
"reputation": 0,
|
||||||
|
"credit_lines": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Hot organization:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "default",
|
||||||
|
"owner": "server",
|
||||||
|
"name": "Default Organization",
|
||||||
|
"funds": 0.0,
|
||||||
|
"reputation": 0,
|
||||||
|
"credit_lines": {},
|
||||||
|
"assets": {},
|
||||||
|
"fleet": {},
|
||||||
|
"members": {},
|
||||||
|
"pending_invites": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules validated by the Rust service:
|
||||||
|
|
||||||
|
- `id` must be non-empty and contain only alphanumeric characters or `_`.
|
||||||
|
- `owner` must be `server` or a 17-digit Steam UID.
|
||||||
|
- `name` cannot be empty, cannot exceed 100 characters, and cannot contain
|
||||||
|
control characters.
|
||||||
|
- `funds`, reputation, and credit line amounts cannot be negative.
|
||||||
|
- Player registration is rejected when the player already belongs to a
|
||||||
|
non-default organization.
|
||||||
|
|
||||||
|
## Durable Commands
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `org:create` | `org_id`, `org_json` | Organization JSON. |
|
||||||
|
| `org:get` | `org_id` | Organization JSON. |
|
||||||
|
| `org:update` | `org_id`, `patch_json` | Updated organization JSON. |
|
||||||
|
| `org:exists` | `org_id` | `true` or `false`. |
|
||||||
|
| `org:delete` | `org_id` | `OK`. |
|
||||||
|
| `org:assets:get` | `org_id` | Asset map JSON. |
|
||||||
|
| `org:assets:update` | `org_id`, `assets_json` | Updated asset map JSON. |
|
||||||
|
| `org:fleet:get` | `org_id` | Fleet map JSON. |
|
||||||
|
| `org:fleet:update` | `org_id`, `fleet_json` | Updated fleet map JSON. |
|
||||||
|
| `org:members:get` | `org_id` | Member array JSON. |
|
||||||
|
| `org:members:add` | `org_id`, `member_uid` | `OK`. |
|
||||||
|
| `org:members:remove` | `org_id`, `member_uid` | `OK`. |
|
||||||
|
|
||||||
|
## Create an Organization
|
||||||
|
|
||||||
|
The command key is authoritative for `id`.
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _org = createHashMapFromArray [
|
||||||
|
["id", _orgId],
|
||||||
|
["owner", getPlayerUID player],
|
||||||
|
["name", "Spearnet Logistics"],
|
||||||
|
["funds", 0],
|
||||||
|
["reputation", 0],
|
||||||
|
["credit_lines", createHashMap]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["org:create", [
|
||||||
|
_orgId,
|
||||||
|
toJSON _org
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Update Organization Funds
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _patch = createHashMapFromArray [
|
||||||
|
["funds", 5000],
|
||||||
|
["reputation", 10]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["org:update", [
|
||||||
|
_orgId,
|
||||||
|
toJSON _patch
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported durable patch fields are `id`, `owner`, `name`, `funds`,
|
||||||
|
`reputation`, and `credit_lines`.
|
||||||
|
|
||||||
|
## Assets and Fleet
|
||||||
|
|
||||||
|
Assets are grouped by category, then classname.
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _assets = createHashMapFromArray [
|
||||||
|
["ammo", createHashMapFromArray [
|
||||||
|
["ACE_30Rnd_65x39_caseless_mag", createHashMapFromArray [
|
||||||
|
["classname", "ACE_30Rnd_65x39_caseless_mag"],
|
||||||
|
["type", "ammo"],
|
||||||
|
["quantity", 20]
|
||||||
|
]]
|
||||||
|
]]
|
||||||
|
];
|
||||||
|
|
||||||
|
"forge_server" callExtension ["org:assets:update", [_orgId, toJSON _assets]];
|
||||||
|
```
|
||||||
|
|
||||||
|
Fleet is keyed by an internal fleet entry ID.
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _fleet = createHashMapFromArray [
|
||||||
|
["B_Truck_01_transport_F_0", createHashMapFromArray [
|
||||||
|
["classname", "B_Truck_01_transport_F"],
|
||||||
|
["name", "Transport Truck"],
|
||||||
|
["type", "cars"],
|
||||||
|
["status", "Ready"],
|
||||||
|
["damage", "0%"]
|
||||||
|
]]
|
||||||
|
];
|
||||||
|
|
||||||
|
"forge_server" callExtension ["org:fleet:update", [_orgId, toJSON _fleet]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hot-State Commands
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `org:hot:init` | `org_id` | Hot organization JSON. |
|
||||||
|
| `org:hot:get` | `org_id` | Hot organization JSON. |
|
||||||
|
| `org:hot:override` | `org_id`, `hot_org_json` | Hot organization JSON. |
|
||||||
|
| `org:hot:ensure_member` | `context_json` | Hot organization JSON. |
|
||||||
|
| `org:hot:member_invites` | `member_uid` | Invite array JSON. |
|
||||||
|
| `org:hot:register` | `context_json` | Register result JSON. |
|
||||||
|
| `org:hot:invite_member` | `context_json` | Invite result JSON. |
|
||||||
|
| `org:hot:accept_invite` | `context_json` | Invite decision result JSON. |
|
||||||
|
| `org:hot:decline_invite` | `context_json` | Invite decision result JSON. |
|
||||||
|
| `org:hot:assign_credit_line` | `context_json` | Mutation result JSON. |
|
||||||
|
| `org:hot:repay_credit_line` | `context_json` | Repayment result JSON. |
|
||||||
|
| `org:hot:charge_checkout` | `context_json` | Mutation result JSON. |
|
||||||
|
| `org:hot:add_assets` | `context_json`, `assets_json` | Mutation result JSON. |
|
||||||
|
| `org:hot:add_fleet` | `context_json`, `fleet_json` | Mutation result JSON. |
|
||||||
|
| `org:hot:leave` | `context_json` | Leave result JSON. |
|
||||||
|
| `org:hot:disband` | `context_json` | Disband result JSON. |
|
||||||
|
| `org:hot:save` | `org_id` | Current hot organization JSON and async durable save. |
|
||||||
|
| `org:hot:remove` | `org_id` | `OK`. |
|
||||||
|
|
||||||
|
## Register from UI Context
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _context = createHashMapFromArray [
|
||||||
|
["requesterUid", getPlayerUID player],
|
||||||
|
["requesterName", name player],
|
||||||
|
["orgId", _orgId],
|
||||||
|
["orgName", "Spearnet Logistics"],
|
||||||
|
["existingOrgId", "default"]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["org:hot:register", [toJSON _context]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Invite and Accept
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _invite = createHashMapFromArray [
|
||||||
|
["requesterUid", getPlayerUID player],
|
||||||
|
["requesterName", name player],
|
||||||
|
["orgId", _orgId],
|
||||||
|
["requesterIsDefaultOrgCeo", false],
|
||||||
|
["targetUid", _targetUid],
|
||||||
|
["targetName", _targetName],
|
||||||
|
["targetOrgId", "default"]
|
||||||
|
];
|
||||||
|
|
||||||
|
"forge_server" callExtension ["org:hot:invite_member", [toJSON _invite]];
|
||||||
|
|
||||||
|
private _decision = createHashMapFromArray [
|
||||||
|
["requesterUid", _targetUid],
|
||||||
|
["requesterName", _targetName],
|
||||||
|
["orgId", _orgId],
|
||||||
|
["existingOrgId", "default"]
|
||||||
|
];
|
||||||
|
|
||||||
|
"forge_server" callExtension ["org:hot:accept_invite", [toJSON _decision]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Credit Line Checkout
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _credit = createHashMapFromArray [
|
||||||
|
["requesterUid", getPlayerUID player],
|
||||||
|
["orgId", _orgId],
|
||||||
|
["requesterIsDefaultOrgCeo", false],
|
||||||
|
["memberUid", _memberUid],
|
||||||
|
["memberName", _memberName],
|
||||||
|
["amount", 1000]
|
||||||
|
];
|
||||||
|
|
||||||
|
"forge_server" callExtension ["org:hot:assign_credit_line", [toJSON _credit]];
|
||||||
|
|
||||||
|
private _charge = createHashMapFromArray [
|
||||||
|
["requesterUid", _memberUid],
|
||||||
|
["orgId", _orgId],
|
||||||
|
["requesterIsDefaultOrgCeo", false],
|
||||||
|
["source", "credit_line"],
|
||||||
|
["amount", 250],
|
||||||
|
["commit", true]
|
||||||
|
];
|
||||||
|
|
||||||
|
"forge_server" callExtension ["org:hot:charge_checkout", [toJSON _charge]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _payload = _result select 0;
|
||||||
|
if (_payload find "Error:" == 0) exitWith {
|
||||||
|
systemChat format ["Organization error: %1", _payload];
|
||||||
|
};
|
||||||
|
```
|
||||||
158
docs/OWNED_STORAGE_USAGE_GUIDE.md
Normal file
158
docs/OWNED_STORAGE_USAGE_GUIDE.md
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
# Owned Storage Usage Guide
|
||||||
|
|
||||||
|
Owned storage covers the `owned:locker` and `owned:garage` extension command
|
||||||
|
groups. These modules store unlock lists rather than physical item or vehicle
|
||||||
|
instances.
|
||||||
|
|
||||||
|
Use these modules for virtual arsenal and virtual garage unlocks. Use
|
||||||
|
[Locker Usage Guide](./LOCKER_USAGE_GUIDE.md) and
|
||||||
|
[Garage Usage Guide](./GARAGE_USAGE_GUIDE.md) for physical inventory and stored
|
||||||
|
vehicle instances.
|
||||||
|
|
||||||
|
## Owned Locker Model
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": ["FirstAidKit"],
|
||||||
|
"weapons": ["arifle_MX_F"],
|
||||||
|
"magazines": ["30Rnd_65x39_caseless_black_mag"],
|
||||||
|
"backpacks": ["B_AssaultPack_rgr"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported owned locker categories:
|
||||||
|
|
||||||
|
- `items`
|
||||||
|
- `weapons`
|
||||||
|
- `magazines`
|
||||||
|
- `backpacks`
|
||||||
|
|
||||||
|
New owned lockers are created with default unlocks from the Rust model.
|
||||||
|
|
||||||
|
## Owned Garage Model
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cars": ["B_Quadbike_01_F"],
|
||||||
|
"armor": [],
|
||||||
|
"helis": [],
|
||||||
|
"planes": [],
|
||||||
|
"naval": [],
|
||||||
|
"other": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported owned garage categories:
|
||||||
|
|
||||||
|
- `cars`
|
||||||
|
- `armor`
|
||||||
|
- `helis`
|
||||||
|
- `planes`
|
||||||
|
- `naval`
|
||||||
|
- `other`
|
||||||
|
|
||||||
|
The durable `owned:garage:remove` command currently accepts `heli` for the
|
||||||
|
helicopter category. Add, get, and hot remove accept `helis`.
|
||||||
|
|
||||||
|
New owned garages are created with default unlocks from the Rust model.
|
||||||
|
|
||||||
|
## Owned Locker Commands
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `owned:locker:create` | `uid` | Full owned locker JSON. |
|
||||||
|
| `owned:locker:fetch` | `uid` | Full owned locker JSON. |
|
||||||
|
| `owned:locker:get` | `uid`, `category` | Category classname array JSON. |
|
||||||
|
| `owned:locker:add` | `uid`, `category`, `classnames_json` | Updated category array JSON. |
|
||||||
|
| `owned:locker:remove` | `uid`, `category`, `classname` | Updated category array JSON. |
|
||||||
|
| `owned:locker:delete` | `uid` | `OK`. |
|
||||||
|
| `owned:locker:exists` | `uid` | `true` or `false`. |
|
||||||
|
|
||||||
|
## Owned Garage Commands
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `owned:garage:create` | `uid` | Full owned garage JSON. |
|
||||||
|
| `owned:garage:fetch` | `uid` | Full owned garage JSON. |
|
||||||
|
| `owned:garage:get` | `uid`, `category` | Category classname array JSON. |
|
||||||
|
| `owned:garage:add` | `uid`, `category`, `classnames_json` | Updated category array JSON. |
|
||||||
|
| `owned:garage:remove` | `uid`, `category`, `classname` | Updated category array JSON. |
|
||||||
|
| `owned:garage:delete` | `uid` | `OK`. |
|
||||||
|
| `owned:garage:exists` | `uid` | `true` or `false`. |
|
||||||
|
|
||||||
|
## Add Virtual Arsenal Unlocks
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _classes = ["arifle_MX_F", "hgun_P07_F"];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["owned:locker:add", [
|
||||||
|
getPlayerUID player,
|
||||||
|
"weapons",
|
||||||
|
toJSON _classes
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add Virtual Garage Unlocks
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _classes = ["B_Quadbike_01_F", "B_MRAP_01_F"];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["owned:garage:add", [
|
||||||
|
getPlayerUID player,
|
||||||
|
"cars",
|
||||||
|
toJSON _classes
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Remove an Unlock
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
"forge_server" callExtension ["owned:locker:remove", [
|
||||||
|
getPlayerUID player,
|
||||||
|
"weapons",
|
||||||
|
"arifle_MX_F"
|
||||||
|
]];
|
||||||
|
|
||||||
|
"forge_server" callExtension ["owned:garage:remove", [
|
||||||
|
getPlayerUID player,
|
||||||
|
"cars",
|
||||||
|
"B_Quadbike_01_F"
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hot-State Commands
|
||||||
|
|
||||||
|
Both owned storage modules support hot state.
|
||||||
|
|
||||||
|
Owned locker:
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `owned:locker:hot:init` | `uid` | Full owned locker JSON. |
|
||||||
|
| `owned:locker:hot:fetch` | `uid` | Full owned locker JSON. |
|
||||||
|
| `owned:locker:hot:get` | `uid`, `category` | Category array JSON. |
|
||||||
|
| `owned:locker:hot:override` | `uid`, `locker_json` | Full owned locker JSON. |
|
||||||
|
| `owned:locker:hot:save` | `uid` | Current hot owned locker JSON and async durable save. |
|
||||||
|
| `owned:locker:hot:remove` | `uid` | `OK`. |
|
||||||
|
|
||||||
|
Owned garage:
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `owned:garage:hot:init` | `uid` | Full owned garage JSON. |
|
||||||
|
| `owned:garage:hot:fetch` | `uid` | Full owned garage JSON. |
|
||||||
|
| `owned:garage:hot:get` | `uid`, `category` | Category array JSON. |
|
||||||
|
| `owned:garage:hot:override` | `uid`, `garage_json` | Full owned garage JSON. |
|
||||||
|
| `owned:garage:hot:add` | `uid`, `category`, `classnames_json` | Updated category array JSON. |
|
||||||
|
| `owned:garage:hot:remove_item` | `uid`, `category`, `classname` | Updated category array JSON. |
|
||||||
|
| `owned:garage:hot:save` | `uid` | Current hot owned garage JSON and async durable save. |
|
||||||
|
| `owned:garage:hot:remove` | `uid` | `OK`. |
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _payload = _result select 0;
|
||||||
|
if (_payload find "Error:" == 0) exitWith {
|
||||||
|
systemChat format ["Owned storage error: %1", _payload];
|
||||||
|
};
|
||||||
|
```
|
||||||
136
docs/PHONE_USAGE_GUIDE.md
Normal file
136
docs/PHONE_USAGE_GUIDE.md
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
# Phone Usage Guide
|
||||||
|
|
||||||
|
The phone module stores contacts, messages, and emails for each UID. It is a
|
||||||
|
server-extension state module backed by SurrealDB.
|
||||||
|
|
||||||
|
## Storage Model
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"contacts": ["76561198000000000", "field_commander"],
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"id": "phone-message:sender:receiver:1",
|
||||||
|
"from": "sender",
|
||||||
|
"to": "receiver",
|
||||||
|
"message": "Text body",
|
||||||
|
"timestamp": 123.45,
|
||||||
|
"read": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"emails": [
|
||||||
|
{
|
||||||
|
"id": "phone-email:sender:receiver:2",
|
||||||
|
"from": "sender",
|
||||||
|
"to": "receiver",
|
||||||
|
"subject": "Subject",
|
||||||
|
"body": "Email body",
|
||||||
|
"timestamp": 123.45,
|
||||||
|
"read": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules validated by the Rust service:
|
||||||
|
|
||||||
|
- UID arguments cannot be empty.
|
||||||
|
- Message and email bodies cannot be empty.
|
||||||
|
- Empty email subjects become `No subject`.
|
||||||
|
- Player messages and emails cannot target `field_commander`.
|
||||||
|
- `field_commander` can send messages or emails to players.
|
||||||
|
- Deleting a message or email removes it only from the requesting UID's index.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `phone:init` | `uid` | Full phone payload. |
|
||||||
|
| `phone:contacts:list` | `uid` | Contact UID array. |
|
||||||
|
| `phone:contacts:add` | `uid`, `contact_uid` | `true` or `false`. |
|
||||||
|
| `phone:contacts:remove` | `uid`, `contact_uid` | `true` or `false`. |
|
||||||
|
| `phone:messages:list` | `uid` | Message array. |
|
||||||
|
| `phone:messages:thread` | `uid`, `other_uid` | Message array for both participants. |
|
||||||
|
| `phone:messages:send` | `from_uid`, `to_uid`, `message`, `timestamp` | Message JSON. |
|
||||||
|
| `phone:messages:mark_read` | `uid`, `message_id` | `true` or `false`. |
|
||||||
|
| `phone:messages:delete` | `uid`, `message_id` | `true` or `false`. |
|
||||||
|
| `phone:emails:list` | `uid` | Email array. |
|
||||||
|
| `phone:emails:send` | `from_uid`, `to_uid`, `subject`, `body`, `timestamp` | Email JSON. |
|
||||||
|
| `phone:emails:mark_read` | `uid`, `email_id` | `true` or `false`. |
|
||||||
|
| `phone:emails:delete` | `uid`, `email_id` | `true` or `false`. |
|
||||||
|
| `phone:remove` | `uid` | `OK`. |
|
||||||
|
|
||||||
|
## Initialize Phone State
|
||||||
|
|
||||||
|
`phone:init` creates phone state if needed and seeds self-contact plus
|
||||||
|
`field_commander`.
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _result = "forge_server" callExtension ["phone:init", [getPlayerUID player]];
|
||||||
|
private _payload = _result select 0;
|
||||||
|
|
||||||
|
if (_payload find "Error:" == 0) exitWith {
|
||||||
|
systemChat format ["Phone init failed: %1", _payload];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _phone = fromJSON _payload;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Send a Message
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _timestamp = str diag_tickTime;
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["phone:messages:send", [
|
||||||
|
getPlayerUID player,
|
||||||
|
_targetUid,
|
||||||
|
"Move to checkpoint Alpha.",
|
||||||
|
_timestamp
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Read a Conversation
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _result = "forge_server" callExtension ["phone:messages:thread", [
|
||||||
|
getPlayerUID player,
|
||||||
|
_otherUid
|
||||||
|
]];
|
||||||
|
|
||||||
|
private _messages = fromJSON (_result select 0);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Send an Email
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _result = "forge_server" callExtension ["phone:emails:send", [
|
||||||
|
getPlayerUID player,
|
||||||
|
_targetUid,
|
||||||
|
"Supply Request",
|
||||||
|
"Requesting resupply at grid 123456.",
|
||||||
|
str diag_tickTime
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mark and Delete Records
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
"forge_server" callExtension ["phone:messages:mark_read", [
|
||||||
|
getPlayerUID player,
|
||||||
|
_messageId
|
||||||
|
]];
|
||||||
|
|
||||||
|
"forge_server" callExtension ["phone:emails:delete", [
|
||||||
|
getPlayerUID player,
|
||||||
|
_emailId
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _payload = (_result select 0);
|
||||||
|
if (_payload find "Error:" == 0) then {
|
||||||
|
systemChat format ["Phone error: %1", _payload];
|
||||||
|
};
|
||||||
|
```
|
||||||
@ -16,8 +16,16 @@ collects framework-level documentation for those pieces.
|
|||||||
|
|
||||||
## Existing Usage Guides
|
## Existing Usage Guides
|
||||||
|
|
||||||
|
- [Actor Usage Guide](./ACTOR_USAGE_GUIDE.md)
|
||||||
|
- [Bank Usage Guide](./BANK_USAGE_GUIDE.md)
|
||||||
|
- [CAD Usage Guide](./CAD_USAGE_GUIDE.md)
|
||||||
- [Garage Usage Guide](./GARAGE_USAGE_GUIDE.md)
|
- [Garage Usage Guide](./GARAGE_USAGE_GUIDE.md)
|
||||||
- [Locker Usage Guide](./LOCKER_USAGE_GUIDE.md)
|
- [Locker Usage Guide](./LOCKER_USAGE_GUIDE.md)
|
||||||
|
- [Organization Usage Guide](./ORG_USAGE_GUIDE.md)
|
||||||
|
- [Owned Storage Usage Guide](./OWNED_STORAGE_USAGE_GUIDE.md)
|
||||||
|
- [Phone Usage Guide](./PHONE_USAGE_GUIDE.md)
|
||||||
|
- [Store Usage Guide](./STORE_USAGE_GUIDE.md)
|
||||||
|
- [Task Usage Guide](./TASK_USAGE_GUIDE.md)
|
||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
|
|||||||
135
docs/STORE_USAGE_GUIDE.md
Normal file
135
docs/STORE_USAGE_GUIDE.md
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
# Store Usage Guide
|
||||||
|
|
||||||
|
The store module processes checkout requests. It charges a payment source and
|
||||||
|
grants purchased items to the player locker, virtual arsenal locker, and
|
||||||
|
virtual garage unlocks.
|
||||||
|
|
||||||
|
## Checkout Model
|
||||||
|
|
||||||
|
`store:checkout` accepts one JSON context.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"requesterUid": "76561198000000000",
|
||||||
|
"requesterName": "Player Name",
|
||||||
|
"orgId": "default",
|
||||||
|
"requesterIsDefaultOrgCeo": false,
|
||||||
|
"paymentMethod": "bank",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"classname": "arifle_MX_F",
|
||||||
|
"category": "weapon",
|
||||||
|
"priceValue": 500,
|
||||||
|
"quantity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"vehicles": [
|
||||||
|
{
|
||||||
|
"classname": "B_Quadbike_01_F",
|
||||||
|
"category": "cars",
|
||||||
|
"priceValue": 1500
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules validated by the Rust service:
|
||||||
|
|
||||||
|
- `requesterUid` is required.
|
||||||
|
- At least one item or vehicle is required.
|
||||||
|
- The checkout total must be greater than zero.
|
||||||
|
- Item categories must be `item`, `attachment`, `weapon`, `magazine`, or
|
||||||
|
`backpack`.
|
||||||
|
- Vehicle categories must be `cars`, `armor`, `helis`, `planes`, `naval`, or
|
||||||
|
`other`.
|
||||||
|
- Payment method must be `cash`, `bank`, `org_funds`, or `credit_line`.
|
||||||
|
- Player locker capacity cannot exceed 25 unique items after checkout.
|
||||||
|
- Organization funds can only be charged by the org owner or the default org
|
||||||
|
CEO flag.
|
||||||
|
|
||||||
|
## Command
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `store:checkout` | `checkout_json` | Checkout result JSON. |
|
||||||
|
|
||||||
|
## Result Model
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"chargedTotal": 2000.0,
|
||||||
|
"paymentMethod": "bank",
|
||||||
|
"message": "Checkout completed. $2,000 charged, 1 locker grant(s), 1 vehicle unlock(s).",
|
||||||
|
"lockerGranted": [],
|
||||||
|
"vehicleGranted": [],
|
||||||
|
"lockerPatch": {},
|
||||||
|
"vaPatch": {},
|
||||||
|
"vgaragePatch": {},
|
||||||
|
"bankPatch": {},
|
||||||
|
"orgPatch": {},
|
||||||
|
"orgTargetUids": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Patch fields are intended for UI updates after checkout. The service commits
|
||||||
|
all grants and payment changes together, and attempts rollback if a later write
|
||||||
|
fails.
|
||||||
|
|
||||||
|
## Player Bank Checkout
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _item = createHashMapFromArray [
|
||||||
|
["classname", "arifle_MX_F"],
|
||||||
|
["category", "weapon"],
|
||||||
|
["priceValue", 500],
|
||||||
|
["quantity", 1]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _checkout = createHashMapFromArray [
|
||||||
|
["requesterUid", getPlayerUID player],
|
||||||
|
["requesterName", name player],
|
||||||
|
["orgId", "default"],
|
||||||
|
["requesterIsDefaultOrgCeo", false],
|
||||||
|
["paymentMethod", "bank"],
|
||||||
|
["items", [_item]],
|
||||||
|
["vehicles", []]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["store:checkout", [toJSON _checkout]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Organization Funds Checkout
|
||||||
|
|
||||||
|
When `paymentMethod` is `org_funds`, vehicles are also added to the
|
||||||
|
organization fleet patch.
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _vehicle = createHashMapFromArray [
|
||||||
|
["classname", "B_Quadbike_01_F"],
|
||||||
|
["category", "cars"],
|
||||||
|
["priceValue", 1500]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _checkout = createHashMapFromArray [
|
||||||
|
["requesterUid", getPlayerUID player],
|
||||||
|
["requesterName", name player],
|
||||||
|
["orgId", _orgId],
|
||||||
|
["requesterIsDefaultOrgCeo", false],
|
||||||
|
["paymentMethod", "org_funds"],
|
||||||
|
["items", []],
|
||||||
|
["vehicles", [_vehicle]]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["store:checkout", [toJSON _checkout]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _payload = _result select 0;
|
||||||
|
if (_payload find "Error:" == 0) exitWith {
|
||||||
|
hint format ["Checkout failed: %1", _payload];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _checkoutResult = fromJSON _payload;
|
||||||
|
```
|
||||||
119
docs/TASK_USAGE_GUIDE.md
Normal file
119
docs/TASK_USAGE_GUIDE.md
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
# Task Usage Guide
|
||||||
|
|
||||||
|
The task module stores transient mission task metadata for active server or
|
||||||
|
mission lifecycle workflows. SQF still owns Arma-only runtime state such as
|
||||||
|
objects and participants.
|
||||||
|
|
||||||
|
## Data Model
|
||||||
|
|
||||||
|
Catalog entries are flexible JSON objects. The service normalizes these fields
|
||||||
|
when a catalog entry is inserted or ownership changes:
|
||||||
|
|
||||||
|
- `taskId`
|
||||||
|
- `taskID`
|
||||||
|
- `accepted`
|
||||||
|
- `requesterUid`
|
||||||
|
- `orgID`
|
||||||
|
|
||||||
|
Ownership context:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"requesterUid": "76561198000000000",
|
||||||
|
"orgId": "default"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Arguments | Returns |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `task:reset` | none | `true`. |
|
||||||
|
| `task:catalog:active` | none | Active catalog entry array JSON. |
|
||||||
|
| `task:catalog:get` | `task_id` | Catalog entry JSON or `null`. |
|
||||||
|
| `task:catalog:upsert` | `task_id`, `entry_json` | Stored catalog entry JSON. |
|
||||||
|
| `task:catalog:delete` | `task_id` | `true`. |
|
||||||
|
| `task:ownership:bind` | `task_id`, `ownership_json` | Ownership mutation result JSON. |
|
||||||
|
| `task:ownership:release` | `task_id` | Ownership mutation result JSON. |
|
||||||
|
| `task:ownership:accept` | `task_id`, `ownership_json` | Ownership mutation result JSON. |
|
||||||
|
| `task:ownership:reward_context` | `task_id` | Reward context JSON. |
|
||||||
|
| `task:status:set` | `task_id`, `status` | `true`. |
|
||||||
|
| `task:status:get` | `task_id` | Status string JSON. |
|
||||||
|
| `task:status:clear` | `task_id` | `true`. |
|
||||||
|
| `task:defuse:increment` | `task_id` | New counter value JSON. |
|
||||||
|
| `task:defuse:get` | `task_id` | Counter value JSON. |
|
||||||
|
| `task:clear` | `task_id` | `true`. |
|
||||||
|
|
||||||
|
## Upsert a Catalog Entry
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _entry = createHashMapFromArray [
|
||||||
|
["title", "Destroy Cache"],
|
||||||
|
["description", "Destroy the enemy supply cache."],
|
||||||
|
["reward", 1500]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["task:catalog:upsert", [
|
||||||
|
"task-cache-1",
|
||||||
|
toJSON _entry
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mark a Task Active
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
"forge_server" callExtension ["task:status:set", [
|
||||||
|
"task-cache-1",
|
||||||
|
"active"
|
||||||
|
]];
|
||||||
|
|
||||||
|
private _active = "forge_server" callExtension ["task:catalog:active", []];
|
||||||
|
```
|
||||||
|
|
||||||
|
Completed statuses `succeeded` and `failed` are also stored as completed status
|
||||||
|
fallbacks. Clearing status removes active and completed state.
|
||||||
|
|
||||||
|
## Accept a Task
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _ownership = createHashMapFromArray [
|
||||||
|
["requesterUid", getPlayerUID player],
|
||||||
|
["orgId", "default"]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = "forge_server" callExtension ["task:ownership:accept", [
|
||||||
|
"task-cache-1",
|
||||||
|
toJSON _ownership
|
||||||
|
]];
|
||||||
|
```
|
||||||
|
|
||||||
|
`task:ownership:accept` fails if the task is not active or another requester
|
||||||
|
already accepted it.
|
||||||
|
|
||||||
|
## Rewards
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _result = "forge_server" callExtension ["task:ownership:reward_context", [
|
||||||
|
"task-cache-1"
|
||||||
|
]];
|
||||||
|
|
||||||
|
private _context = fromJSON (_result select 0);
|
||||||
|
```
|
||||||
|
|
||||||
|
The reward context contains `requesterUid` and `orgId`.
|
||||||
|
|
||||||
|
## Defuse Counter
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
"forge_server" callExtension ["task:defuse:increment", ["task-cache-1"]];
|
||||||
|
private _count = "forge_server" callExtension ["task:defuse:get", ["task-cache-1"]];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
private _payload = _result select 0;
|
||||||
|
if (_payload find "Error:" == 0) exitWith {
|
||||||
|
systemChat format ["Task error: %1", _payload];
|
||||||
|
};
|
||||||
|
```
|
||||||
Loading…
x
Reference in New Issue
Block a user