Compare commits
No commits in common. "b0b53eb28b3dc8333437f36c7a233b70e90d7060" and "9d17d6845396a060639191519ce65490f33047f2" have entirely different histories.
b0b53eb28b
...
9d17d68453
@ -37,4 +37,3 @@ 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,16 +33,3 @@ 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)
|
|
||||||
|
|||||||
@ -1,127 +0,0 @@
|
|||||||
# 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;
|
|
||||||
```
|
|
||||||
@ -1,169 +0,0 @@
|
|||||||
# 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;
|
|
||||||
```
|
|
||||||
@ -1,183 +0,0 @@
|
|||||||
# 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,212 +1,274 @@
|
|||||||
# Garage Usage Guide
|
# Garage System Integration Guide
|
||||||
|
|
||||||
The garage module stores physical player vehicles. Each record keeps the
|
## Overview
|
||||||
vehicle classname, generated plate UUID, fuel, overall damage, and detailed hit
|
|
||||||
point damage.
|
|
||||||
|
|
||||||
## Storage Model
|
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.
|
||||||
|
|
||||||
Garage data is persisted through SurrealDB by the server extension.
|
## Data Storage
|
||||||
|
|
||||||
```json
|
- Each player's garage is persisted by the server extension through SurrealDB.
|
||||||
{
|
- The map is keyed by the vehicle's unique plate (UUID)
|
||||||
"plate-uuid": {
|
- Each vehicle tracks: plate (UUID), classname, overall damage, fuel, and detailed hit points
|
||||||
"plate": "plate-uuid",
|
- **Plates are auto-generated** when vehicles are added via `garage:add`
|
||||||
"classname": "B_Quadbike_01_F",
|
- **Empty garages are auto-created** when a player first fetches their garage
|
||||||
"fuel": 1.0,
|
|
||||||
"damage": 0.0,
|
## Extension Commands
|
||||||
"hit_points": {
|
|
||||||
"names": ["hitengine"],
|
All commands are accessed via the `garage` group:
|
||||||
"selections": ["engine_hitpoint"],
|
|
||||||
"values": [0.0]
|
### Create Garage
|
||||||
}
|
|
||||||
}
|
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)
|
||||||
```
|
```
|
||||||
|
|
||||||
Rules validated by the Rust service:
|
### Get Garage
|
||||||
|
|
||||||
- A player garage can contain up to 5 vehicles.
|
Retrieves all vehicles in a player's garage.
|
||||||
- `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 _payload = _result select 0;
|
private _garageMap = fromJSON (_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 a Vehicle
|
### Add Vehicle
|
||||||
|
|
||||||
`garage:add` requires `classname`, `fuel`, `damage`, and `hit_points`.
|
Adds a new vehicle to the garage. The system automatically generates a unique plate (UUID) for the vehicle.
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
private _hitPointData = getAllHitPointsDamage _vehicle;
|
private _data = createHashMapFromArray [
|
||||||
private _hitPoints = createHashMapFromArray [
|
["classname", "B_Quadbike_01_F"],
|
||||||
["names", _hitPointData select 0],
|
["fuel", 1.0],
|
||||||
["selections", _hitPointData select 1],
|
["damage", 0.0],
|
||||||
["values", _hitPointData select 2]
|
["hit_points", createHashMap] // Optional hit points map
|
||||||
];
|
|
||||||
|
|
||||||
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 _vehicleData
|
toJSON _data
|
||||||
]];
|
]];
|
||||||
|
|
||||||
private _payload = _result select 0;
|
private _updatedGarage = fromJSON (_result select 0);
|
||||||
if (_payload find "Error:" == 0) exitWith {
|
// Returns updated garage map
|
||||||
hint format ["Failed to store vehicle: %1", _payload];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _garage = fromJSON _payload;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The returned value is a hash map keyed by generated plate. To find the newly
|
### Update Garage (Sync)
|
||||||
stored vehicle, compare returned keys before and after the add, or search by
|
|
||||||
classname if your workflow guarantees a unique pending vehicle.
|
Updates the entire garage state. Useful for syncing changes made locally.
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
private _storedPlate = "";
|
// _garageMap is the local HashMap of vehicles
|
||||||
{
|
private _result = "forge_server" callExtension ["garage:update", [
|
||||||
private _vehicleRecord = _garage get _x;
|
getPlayerUID player,
|
||||||
if ((_vehicleRecord get "classname") == typeOf _vehicle) then {
|
toJSON _garageMap
|
||||||
_storedPlate = _x;
|
]];
|
||||||
};
|
|
||||||
} forEach keys _garage;
|
private _updatedGarage = fromJSON (_result select 0);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Patch a Vehicle
|
### Patch Vehicle
|
||||||
|
|
||||||
`garage:patch` updates selected fields for one plate. The `plate` field is
|
Updates specific fields of a vehicle without sending the entire garage. Useful for frequent updates like fuel or damage.
|
||||||
required. `fuel`, `damage`, and `hit_points` are optional.
|
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
private _patch = createHashMapFromArray [
|
private _plate = "some-plate-uuid";
|
||||||
["plate", _vehicle getVariable ["forge_garage_plate", ""]],
|
private _data = createHashMapFromArray [
|
||||||
["fuel", fuel _vehicle],
|
["plate", _plate],
|
||||||
["damage", damage _vehicle]
|
["fuel", 0.8],
|
||||||
|
["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 _patch
|
toJSON _data
|
||||||
]];
|
]];
|
||||||
|
|
||||||
|
private _updatedGarage = fromJSON (_result select 0);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Remove a Vehicle
|
### Remove Vehicle
|
||||||
|
|
||||||
`garage:remove` expects JSON with a `plate` field.
|
Removes a specific vehicle from the garage by plate number.
|
||||||
|
Note: If using the GarageStore, removing a vehicle locally and saving/syncing will also remove it from the server.
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
private _remove = createHashMapFromArray [
|
private _plate = "some-plate-uuid";
|
||||||
|
|
||||||
|
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 _remove
|
toJSON _data
|
||||||
]];
|
]];
|
||||||
|
|
||||||
|
private _updatedGarage = fromJSON (_result select 0);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Spawn a Stored Vehicle
|
### Delete Garage
|
||||||
|
|
||||||
|
Permanently deletes all vehicles from a player's garage.
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
fnc_spawnGarageVehicle = {
|
private _result = "forge_server" callExtension ["garage:delete", [getPlayerUID player]];
|
||||||
params ["_plate"];
|
// Returns: "OK" or "Error: ..."
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 _payload = _result select 0;
|
private _garage = fromJSON (_result select 0);
|
||||||
|
|
||||||
if (_payload find "Error:" == 0) exitWith {
|
// Validate index
|
||||||
hint format ["Failed to load garage: %1", _payload];
|
if (_vehicleIndex >= count _garage) exitWith {
|
||||||
|
hint "Invalid vehicle index!";
|
||||||
objNull
|
objNull
|
||||||
};
|
};
|
||||||
|
|
||||||
private _garage = fromJSON _payload;
|
// Get vehicle data
|
||||||
private _vehicleData = _garage getOrDefault [_plate, createHashMap];
|
private _vehicleData = _garage select _vehicleIndex;
|
||||||
if (_vehicleData isEqualTo createHashMap) exitWith {
|
private _classname = _vehicleData get "classname";
|
||||||
hint "Vehicle plate was not found in your garage.";
|
private _storedDamage = _vehicleData get "damage";
|
||||||
objNull
|
private _hitPoints = _vehicleData get "hit_points";
|
||||||
};
|
|
||||||
|
|
||||||
private _vehicle = (_vehicleData get "classname") createVehicle (player getPos [10, getDir player]);
|
// Spawn vehicle
|
||||||
_vehicle setFuel (_vehicleData getOrDefault ["fuel", 1]);
|
private _spawnPos = player getPos [10, getDir player];
|
||||||
_vehicle setDamage (_vehicleData getOrDefault ["damage", 0]);
|
private _vehicle = _classname createVehicle _spawnPos;
|
||||||
_vehicle setVariable ["forge_garage_plate", _plate, true];
|
|
||||||
|
|
||||||
private _hitPoints = _vehicleData getOrDefault ["hit_points", createHashMap];
|
// Apply damage
|
||||||
private _names = _hitPoints getOrDefault ["names", []];
|
_vehicle setDamage _storedDamage;
|
||||||
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;
|
||||||
|
|
||||||
private _remove = createHashMapFromArray [["plate", _plate]];
|
// Store plate on vehicle for future updates
|
||||||
"forge_server" callExtension ["garage:remove", [getPlayerUID player, toJSON _remove]];
|
private _plate = _vehicleData get "plate";
|
||||||
|
_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
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Hot State
|
## Hit Points Data Format
|
||||||
|
|
||||||
The `garage:hot:*` commands keep a runtime copy of a player's garage and write
|
The hit points data matches Arma 3's `getAllHitPointsDamage` return format:
|
||||||
it back only when `garage:hot:save` runs.
|
|
||||||
|
|
||||||
| Command | Arguments | Returns |
|
```json
|
||||||
| --- | --- | --- |
|
{
|
||||||
| `garage:hot:init` | `uid` | Vehicle map as JSON. |
|
"names": ["hitlfwheel", "hitlf2wheel", "hitfuel", "hitengine", "hitbody", ...],
|
||||||
| `garage:hot:get` | `uid` | Vehicle map as JSON. |
|
"selections": ["wheel_1_1_steering", "wheel_1_2_steering", "fuel_hitpoint", "engine_hitpoint", "body_hitpoint", ...],
|
||||||
| `garage:hot:override` | `uid`, `vehicles_json` | Vehicle map as JSON. |
|
"values": [0, 0, 0.1, 0.2, 0, ...]
|
||||||
| `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`. |
|
|
||||||
|
|
||||||
Use hot state for session-heavy vehicle workflows. Use the durable commands for
|
- **names**: Hit point identifiers
|
||||||
simple store/retrieve operations.
|
- **selections**: Physical selection names (can be empty strings `""`)
|
||||||
|
- **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
|
||||||
|
|
||||||
- Store the generated plate on spawned vehicles with `setVariable`.
|
1. **Track Vehicle Plates**: When spawning vehicles from the garage, store the plate (UUID) as a variable so you can update them later:
|
||||||
- Use `garage:patch` for frequent fuel and damage syncs.
|
```sqf
|
||||||
- Use `garage:update` only when replacing the whole vehicle map intentionally.
|
_vehicle setVariable ["garagePlate", _plate];
|
||||||
- Do not delete the world vehicle until `garage:add` succeeds.
|
```
|
||||||
- Treat vehicle maps as hash maps keyed by plate, not arrays.
|
2. **Auto-Creation**: The system automatically creates an empty garage for new players on first access
|
||||||
|
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,203 +1,228 @@
|
|||||||
# Locker Usage Guide
|
# Locker System Integration Guide
|
||||||
|
|
||||||
The locker module stores physical player inventory items by classname. It is
|
## Overview
|
||||||
separate from the virtual arsenal unlock module documented in
|
|
||||||
[Owned Storage Usage Guide](./OWNED_STORAGE_USAGE_GUIDE.md).
|
|
||||||
|
|
||||||
## Storage Model
|
The locker system provides persistent item storage for Arma 3 players. Each player can store multiple items (weapons, magazines, equipment) with quantity tracking.
|
||||||
|
|
||||||
Locker data is persisted through SurrealDB by the server extension.
|
## Data Storage
|
||||||
|
|
||||||
```json
|
- Each player's locker is persisted by the server extension through SurrealDB.
|
||||||
{
|
- The map is keyed by the item's **classname** (String)
|
||||||
"arifle_MX_F": {
|
- Each item tracks: `category`, `classname`, and `amount`
|
||||||
"category": "weapon",
|
- **Maximum Capacity**: 25 unique items per locker
|
||||||
"classname": "arifle_MX_F",
|
- **Empty lockers are auto-created** when a player first fetches their locker if it doesn't exist
|
||||||
"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
|
||||||
```
|
```
|
||||||
|
|
||||||
Rules validated by the Rust service:
|
### Get Locker
|
||||||
|
|
||||||
- A locker can contain up to 25 unique classnames.
|
Retrieves all items in a player's locker.
|
||||||
- `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 _payload = _result select 0;
|
private _lockerMap = fromJSON (_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 an Item
|
### Add Item
|
||||||
|
|
||||||
`locker:add` creates or overwrites one classname entry.
|
Adds a new item to the locker or updates the amount if it already exists.
|
||||||
|
**Checks capacity limit (25 items).**
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
private _item = createHashMapFromArray [
|
private _data = 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 _item
|
toJSON _data
|
||||||
]];
|
]];
|
||||||
|
|
||||||
private _payload = _result select 0;
|
private _updatedLocker = fromJSON (_result select 0);
|
||||||
if (_payload find "Error:" == 0) exitWith {
|
// Returns updated locker map
|
||||||
hint format ["Failed to store item: %1", _payload];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _locker = fromJSON _payload;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Patch an Amount
|
### Update Locker (Sync)
|
||||||
|
|
||||||
`locker:patch` currently patches the `amount` field for an existing classname.
|
Updates the entire locker state. Useful for syncing changes made locally (e.g., bulk moves).
|
||||||
|
**Replaces the entire locker content.**
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
private _patch = createHashMapFromArray [
|
// _lockerMap is the local HashMap of items
|
||||||
["classname", "arifle_MX_F"],
|
private _result = "forge_server" callExtension ["locker:update", [
|
||||||
["amount", 5]
|
getPlayerUID player,
|
||||||
|
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 _patch
|
toJSON _data
|
||||||
]];
|
]];
|
||||||
|
|
||||||
|
private _updatedLocker = fromJSON (_result select 0);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Remove an Item
|
### Remove Item
|
||||||
|
|
||||||
`locker:remove` takes the classname as the second argument.
|
Removes a specific item from the locker by classname.
|
||||||
|
|
||||||
```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,
|
||||||
"arifle_MX_F"
|
toJSON _data
|
||||||
]];
|
]];
|
||||||
|
|
||||||
|
private _updatedLocker = fromJSON (_result select 0);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Retrieve an Item
|
### Delete Locker
|
||||||
|
|
||||||
|
Permanently deletes all items from a player's locker.
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
fnc_retrieveLockerItem = {
|
private _result = "forge_server" callExtension ["locker:delete", [getPlayerUID player]];
|
||||||
params ["_classname"];
|
// Returns: "OK" or "Error: ..."
|
||||||
|
```
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["locker:get", [getPlayerUID player]];
|
### Check Existence
|
||||||
private _payload = _result select 0;
|
|
||||||
|
|
||||||
if (_payload find "Error:" == 0) exitWith {
|
Checks if a player has a locker created.
|
||||||
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
|
||||||
};
|
};
|
||||||
|
|
||||||
private _locker = fromJSON _payload;
|
// Remove weapon from player
|
||||||
private _item = _locker getOrDefault [_classname, createHashMap];
|
player removeWeapon _weapon;
|
||||||
if (_item isEqualTo createHashMap) exitWith {
|
hint format ["Stored %1 in locker!", _weapon];
|
||||||
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
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Replace the Whole Locker
|
### Retrieving an Item
|
||||||
|
|
||||||
`locker:update` replaces the whole item map. Use it for explicit bulk syncs,
|
|
||||||
not single-item changes.
|
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
private _items = createHashMapFromArray [
|
fnc_retrieveItem = {
|
||||||
["arifle_MX_F", createHashMapFromArray [
|
params ["_classname"];
|
||||||
["category", "weapon"],
|
|
||||||
["classname", "arifle_MX_F"],
|
|
||||||
["amount", 1]
|
|
||||||
]]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["locker:update", [
|
// Get locker
|
||||||
getPlayerUID player,
|
private _result = "forge_server" callExtension ["locker:get", [getPlayerUID player]];
|
||||||
toJSON _items
|
private _locker = fromJSON (_result select 0);
|
||||||
]];
|
|
||||||
|
// 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
|
||||||
|
};
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Hot State
|
## Error Handling
|
||||||
|
|
||||||
The `locker:hot:*` commands keep a runtime copy of a player's locker and write
|
All commands return errors in the format `"Error: <message>"`.
|
||||||
it back only when `locker:hot:save` runs.
|
|
||||||
|
|
||||||
| Command | Arguments | Returns |
|
```sqf
|
||||||
| --- | --- | --- |
|
private _result = "forge_server" callExtension ["locker:add", ...];
|
||||||
| `locker:hot:init` | `uid` | Item map as JSON. |
|
private _data = _result select 0;
|
||||||
| `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
|
if (_data find "Error:" == 0) then {
|
||||||
simple item deposits and withdrawals.
|
systemChat format ["Locker error: %1", _data];
|
||||||
|
};
|
||||||
## 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,20 +30,8 @@ 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 unlock 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 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 arsenal unlock 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 item 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
|
||||||
|
|
||||||
|
|||||||
@ -1,232 +0,0 @@
|
|||||||
# 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];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
@ -1,158 +0,0 @@
|
|||||||
# 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];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
@ -1,136 +0,0 @@
|
|||||||
# 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,16 +16,8 @@ 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
|
||||||
|
|
||||||
|
|||||||
@ -1,135 +0,0 @@
|
|||||||
# 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;
|
|
||||||
```
|
|
||||||
@ -1,119 +0,0 @@
|
|||||||
# 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