feat(garage): resolve virtual garage spawns by lane and selected session vehicle #11

Merged
J.Schmidt92 merged 4 commits from docus into master 2026-04-22 17:59:41 -05:00
317 changed files with 46482 additions and 82 deletions

60
.github/workflows/deploy-docs.yml vendored Normal file
View File

@ -0,0 +1,60 @@
name: Deploy Docs
on:
push:
branches:
- master
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: github-pages
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: docus/package-lock.json
- name: Install docs dependencies
run: npm ci --prefix docus
- name: Generate docs content
run: node tools/sync-docus-docs.mjs
- name: Build static docs
run: npm run build --prefix docus
env:
DOCS_BASE_URL: /${{ github.event.repository.name }}/
DOCS_SITE_URL: https://${{ github.repository_owner }}.github.io
DOCS_REPO_URL: https://github.com/${{ github.repository }}
DOCS_REPO_BRANCH: ${{ github.ref_name }}
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: docus/.output/public
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

4
.gitignore vendored
View File

@ -23,6 +23,10 @@ target/
# Misc
node_modules/
docus/.nuxt/
docus/.output/
docus/.data/
docus/.nitro/
# OS
.DS_Store

View File

@ -37,6 +37,11 @@ the client.
The client builds vehicle context and sends requests. The server garage addon
and extension own stored vehicle state.
Virtual garage spawning resolves the active garage context and category lane,
then finalizes only the vehicle selected in that BIS garage session. Nearby
world vehicles are ignored as spawn candidates and are only used for the spawn
blocking check at the resolved lane.
Refuel and repair buttons are available from the selected vehicle detail panel
for nearby world vehicles. Stored records must be retrieved before they can be
serviced because fuel and repair operate on live vehicle objects. Service

View File

@ -88,9 +88,21 @@ GVAR(GarageActionServiceBaseClass) = compileFinal createHashMapFromArray [
GVAR(GarageUIBridge) call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [["message", "Stored vehicle record could not be found."]]]];
};
private _className = _vehicleData getOrDefault ["classname", ""];
if (_className isEqualTo "") exitWith {
GVAR(GarageUIBridge) call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [["message", "Stored vehicle record is missing a classname."]]]];
};
private _context = GVAR(GarageContextService) call ["getContext", []];
private _spawnPosition = _context getOrDefault ["spawnPosition", getPosATL player];
private _spawnHeading = _context getOrDefault ["spawnHeading", getDir player];
private _vehicleCategory = GVAR(GarageHelperService) call ["resolveVGCategory", [_className]];
private _spawnLane = GVAR(GarageContextService) call ["getExactSpawnLane", [_vehicleCategory, _context]];
if (_spawnLane isEqualTo createHashMap) exitWith {
private _categoryLabel = GVAR(GarageHelperService) call ["resolveGarageCategoryLabel", [_vehicleCategory]];
GVAR(GarageUIBridge) call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [["message", format ["This garage does not support spawning %1.", _categoryLabel]]]]];
};
private _spawnPosition = _spawnLane getOrDefault ["spawnPosition", _context getOrDefault ["spawnPosition", getPosATL player]];
private _spawnHeading = _spawnLane getOrDefault ["spawnHeading", _context getOrDefault ["spawnHeading", getDir player]];
private _spawnRadius = _context getOrDefault ["spawnRadius", 6];
private _blockingVehicles = [];
{ _blockingVehicles pushBackUnique _x; } forEach (_spawnPosition nearEntities [["Car", "Tank", "Air", "Ship"], _spawnRadius]);
@ -99,11 +111,6 @@ GVAR(GarageActionServiceBaseClass) = compileFinal createHashMapFromArray [
GVAR(GarageUIBridge) call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [["message", "The garage spawn area is blocked."]]]];
};
private _className = _vehicleData getOrDefault ["classname", ""];
if (_className isEqualTo "") exitWith {
GVAR(GarageUIBridge) call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [["message", "Stored vehicle record is missing a classname."]]]];
};
private _vehicle = createVehicle [_className, _spawnPosition, [], 0, "CAN_COLLIDE"];
_vehicle setDir _spawnHeading;
_vehicle setFuel (_vehicleData getOrDefault ["fuel", 0]);

View File

@ -29,81 +29,189 @@ GVAR(GarageContextServiceBaseClass) = compileFinal createHashMapFromArray [
["name", "Vehicle Garage"],
["anchorPosition", getPosATL player],
["sourceObject", objNull],
["garageType", ""],
["spawnHeading", getDir player],
["spawnPosition", player getPos [8, getDir player]],
["spawnLanes", createHashMap],
["spawnRadius", 6],
["nearbyRadius", 30]
["nearbyRadius", 30],
["laneRadius", 150]
]
}],
["scanEntryValues", compileFinal {
params [["_values", [], [[]]], ["_state", createHashMap, [createHashMap]]];
["findNearbyGarageObject", compileFinal {
private _nearestGarage = objNull;
private _nearestDistance = 1e10;
{
if (_x isEqualType "" && { (_state getOrDefault ["name", "Vehicle Garage"]) isEqualTo "Vehicle Garage" }) then { _state set ["name", _x]; };
if (_x isEqualType "") then {
private _resolvedObject = _state getOrDefault ["sourceObject", objNull];
if (isNull _resolvedObject) then {
private _namedObject = missionNamespace getVariable [_x, objNull];
if (!isNull _namedObject) then { _state set ["sourceObject", _namedObject]; };
};
if ((_state getOrDefault ["anchorPosition", []]) isEqualTo [] && { _x in allMapMarkers }) then { _state set ["anchorPosition", markerPos _x]; };
continue;
if (isNull _x || { !(_x getVariable ["isGarage", false]) }) then { continue; };
private _distance = player distance2D _x;
if (_distance < _nearestDistance) then {
_nearestDistance = _distance;
_nearestGarage = _x;
};
if (_x isEqualType objNull && { isNull (_state getOrDefault ["sourceObject", objNull]) }) then {
_state set ["sourceObject", _x];
if ((_state getOrDefault ["anchorPosition", []]) isEqualTo []) then { _state set ["anchorPosition", getPosATL _x]; };
continue;
};
if (_x isEqualType 0 && { (_state getOrDefault ["spawnHeading", -1]) < 0 }) then { _state set ["spawnHeading", _x]; continue; };
if (_x isEqualType [] && { count _x > 0 }) then {
if ({ _x isEqualType 0 } count _x >= 2 && { ((_state getOrDefault ["offset", []]) isEqualTo []) || ((_state getOrDefault ["anchorPosition", []]) isEqualTo []) }) then {
if ((_state getOrDefault ["anchorPosition", []]) isEqualTo []) then { _state set ["anchorPosition", _x]; } else { _state set ["offset", _x]; };
continue;
};
_self call ["scanEntryValues", [_x, _state]];
};
} forEach _values;
_state
} forEach (player nearObjects 12);
_nearestGarage
}],
["resolveEntry", compileFinal {
params [["_entry", [], [[]]]];
private _state = createHashMapFromArray [["name", "Vehicle Garage"], ["anchorPosition", []], ["sourceObject", objNull], ["offset", []], ["spawnHeading", -1]];
_self call ["scanEntryValues", [_entry, _state]];
private _anchorPosition = _state getOrDefault ["anchorPosition", []];
private _offset = _state getOrDefault ["offset", []];
private _spawnPosition = if (_anchorPosition isEqualTo []) then { [] } else { if (_offset isEqualTo []) then { _anchorPosition } else { _anchorPosition vectorAdd _offset } };
createHashMapFromArray [["name", _state getOrDefault ["name", "Vehicle Garage"]], ["anchorPosition", _anchorPosition], ["sourceObject", _state getOrDefault ["sourceObject", objNull]], ["spawnHeading", _state getOrDefault ["spawnHeading", -1]], ["spawnPosition", _spawnPosition]]
["resolveGarageName", compileFinal {
params [["_garageObject", objNull, [objNull]]];
if (isNull _garageObject) exitWith { "Vehicle Garage" };
private _displayName = _garageObject getVariable ["garageName", ""];
if (_displayName isNotEqualTo "") exitWith { _displayName };
private _varName = vehicleVarName _garageObject;
if (_varName isEqualTo "") exitWith { "Vehicle Garage" };
_varName
}],
["buildMarkerLane", compileFinal {
params [["_markerName", "", [""]], ["_garageObject", objNull, [objNull]]];
if (_markerName isEqualTo "" || { markerShape _markerName isEqualTo "" }) exitWith { createHashMap };
private _spawnCategory = GVAR(GarageHelperService) call ["inferGarageCategory", [_markerName]];
if (_spawnCategory isEqualTo "") exitWith { createHashMap };
private _spawnPosition = markerPos _markerName;
private _interactionPosition = if (isNull _garageObject) then { _spawnPosition } else { getPosATL _garageObject };
private _markerDistance = if (isNull _garageObject) then { player distance2D _spawnPosition } else { _garageObject distance2D _spawnPosition };
private _garageVarName = if (isNull _garageObject) then { "" } else { toLowerANSI (vehicleVarName _garageObject) };
private _markerKey = toLowerANSI _markerName;
private _nameScore = 0;
if (_garageVarName isNotEqualTo "" && { (_markerKey find _garageVarName) >= 0 }) then {
_nameScore = -50;
};
createHashMapFromArray [
["name", _markerName],
["interactionPosition", _interactionPosition],
["sourceObject", _garageObject],
["spawnCategory", _spawnCategory],
["spawnHeading", markerDir _markerName],
["spawnPosition", _spawnPosition],
["score", _markerDistance + _nameScore]
]
}],
["discoverSpawnLanes", compileFinal {
params [["_garageObject", objNull, [objNull]]];
private _laneRadius = (_self call ["createDefaultContext", []]) getOrDefault ["laneRadius", 150];
private _lanes = createHashMap;
{
private _markerName = _x;
if ((toLowerANSI _markerName find "garage") < 0) then { continue; };
private _entry = _self call ["buildMarkerLane", [_markerName, _garageObject]];
if (_entry isEqualTo createHashMap) then { continue; };
private _spawnPosition = _entry getOrDefault ["spawnPosition", []];
if (_spawnPosition isEqualTo []) then { continue; };
private _distance = if (isNull _garageObject) then { player distance2D _spawnPosition } else { _garageObject distance2D _spawnPosition };
if (_distance > _laneRadius) then { continue; };
private _spawnCategory = _entry getOrDefault ["spawnCategory", ""];
private _currentEntry = _lanes getOrDefault [_spawnCategory, createHashMap];
if (_currentEntry isEqualTo createHashMap || { (_entry getOrDefault ["score", 1e10]) < (_currentEntry getOrDefault ["score", 1e10]) }) then {
_lanes set [_spawnCategory, _entry];
};
} forEach allMapMarkers;
_lanes
}],
["selectSpawnLane", compileFinal {
params [
["_lanes", createHashMap, [createHashMap]],
["_preferredCategory", "", [""]],
["_defaultPosition", [], [[]]],
["_defaultHeading", 0, [0]]
];
private _normalizedCategory = GVAR(GarageHelperService) call ["normalizeGarageCategory", [_preferredCategory]];
private _lane = createHashMap;
if (_normalizedCategory isNotEqualTo "") then {
_lane = _lanes getOrDefault [_normalizedCategory, createHashMap];
};
if (_lane isEqualTo createHashMap) then {
{
private _candidate = _lanes getOrDefault [_x, createHashMap];
if (_candidate isNotEqualTo createHashMap) exitWith { _lane = _candidate; };
} forEach ["cars", "armor", "helis", "planes", "naval", "other"];
};
if (_lane isEqualTo createHashMap) then {
_lane = createHashMapFromArray [
["spawnCategory", _normalizedCategory],
["spawnHeading", _defaultHeading],
["spawnPosition", _defaultPosition]
];
};
_lane
}],
["getSpawnLane", compileFinal {
params [["_category", "", [""]], ["_context", createHashMap, [createHashMap]]];
private _resolvedContext = _context;
if (_resolvedContext isEqualTo createHashMap) then {
_resolvedContext = _self call ["getContext", []];
};
private _spawnLanes = _resolvedContext getOrDefault ["spawnLanes", createHashMap];
private _defaultPosition = _resolvedContext getOrDefault ["spawnPosition", getPosATL player];
private _defaultHeading = _resolvedContext getOrDefault ["spawnHeading", getDir player];
_self call ["selectSpawnLane", [_spawnLanes, _category, _defaultPosition, _defaultHeading]]
}],
["getExactSpawnLane", compileFinal {
params [["_category", "", [""]], ["_context", createHashMap, [createHashMap]]];
private _resolvedContext = _context;
if (_resolvedContext isEqualTo createHashMap) then {
_resolvedContext = _self call ["getContext", []];
};
private _normalizedCategory = GVAR(GarageHelperService) call ["normalizeGarageCategory", [_category]];
if (_normalizedCategory isEqualTo "") exitWith { createHashMap };
private _spawnLanes = _resolvedContext getOrDefault ["spawnLanes", createHashMap];
_spawnLanes getOrDefault [_normalizedCategory, createHashMap]
}],
["resolveContext", compileFinal {
private _context = _self call ["createDefaultContext", []];
private _locations = (missionConfigFile >> "FORGE_CfgGarages" >> "locations") call BFUNC(getCfgData);
if !(_locations isEqualType []) exitWith { _self set ["lastContext", _context]; _context };
private _garageObject = _self call ["findNearbyGarageObject", []];
private _garageName = _self call ["resolveGarageName", [_garageObject]];
private _garageType = "";
private _anchorPosition = getPosATL player;
private _spawnHeading = getDir player;
private _spawnPosition = player getPos [8, _spawnHeading];
private _spawnLanes = createHashMap;
private _nearestEntry = [];
private _nearestDistance = 1e10;
{
private _entry = _self call ["resolveEntry", [_x]];
private _anchorPosition = _entry getOrDefault ["anchorPosition", []];
if (_anchorPosition isEqualTo []) then { continue; };
private _distance = player distance2D _anchorPosition;
if (_distance < _nearestDistance) then { _nearestDistance = _distance; _nearestEntry = _entry; };
} forEach _locations;
if (!isNull _garageObject) then {
_garageType = GVAR(GarageHelperService) call ["normalizeGarageCategory", [_garageObject getVariable ["garageType", ""]]];
_anchorPosition = getPosATL _garageObject;
_spawnHeading = getDir _garageObject;
_spawnPosition = _garageObject getPos [8, _spawnHeading];
_spawnLanes = _self call ["discoverSpawnLanes", [_garageObject]];
};
if (_nearestEntry isEqualTo []) exitWith { _self set ["lastContext", _context]; _context };
private _anchorPosition = _nearestEntry getOrDefault ["anchorPosition", []];
private _garageObject = _nearestEntry getOrDefault ["sourceObject", objNull];
private _garageName = _nearestEntry getOrDefault ["name", "Vehicle Garage"];
private _spawnHeading = _nearestEntry getOrDefault ["spawnHeading", getDir player];
if (_spawnHeading < 0) then { _spawnHeading = if (!isNull _garageObject) then { getDir _garageObject } else { getDir player }; };
private _spawnPosition = _nearestEntry getOrDefault ["spawnPosition", []];
if (_spawnPosition isEqualTo []) then { _spawnPosition = if (_anchorPosition isEqualTo []) then { player getPos [8, _spawnHeading] } else { _anchorPosition }; };
private _selectedLane = _self call ["selectSpawnLane", [_spawnLanes, _garageType, _spawnPosition, _spawnHeading]];
_spawnHeading = _selectedLane getOrDefault ["spawnHeading", _spawnHeading];
_spawnPosition = _selectedLane getOrDefault ["spawnPosition", _spawnPosition];
_context set ["name", _garageName];
_context set ["anchorPosition", _anchorPosition];
_context set ["sourceObject", _garageObject];
_context set ["garageType", _garageType];
_context set ["spawnHeading", _spawnHeading];
_context set ["spawnPosition", _spawnPosition];
_context set ["spawnLanes", _spawnLanes];
_self set ["lastContext", _context];
_context
}],

View File

@ -22,6 +22,33 @@
#pragma hemtt ignore_variables ["_self"]
GVAR(GarageHelperServiceBaseClass) = compileFinal createHashMapFromArray [
["#type", "GarageHelperServiceBaseClass"],
["normalizeGarageCategory", compileFinal {
params [["_value", "", [""]]];
private _normalized = toLowerANSI (trim _value);
if (_normalized isEqualTo "") exitWith { "" };
if (_normalized in ["cars", "armor", "helis", "planes", "naval", "other"]) exitWith { _normalized };
""
}],
["inferGarageCategory", compileFinal {
params [["_value", "", [""]]];
private _normalized = toLowerANSI (trim _value);
if (_normalized isEqualTo "") exitWith { "" };
private _resolvedCategory = _self call ["normalizeGarageCategory", [_normalized]];
if (_resolvedCategory isNotEqualTo "") exitWith { _resolvedCategory };
switch (true) do {
case ((_normalized find "cars") >= 0): { "cars" };
case ((_normalized find "armor") >= 0): { "armor" };
case ((_normalized find "helis") >= 0): { "helis" };
case ((_normalized find "planes") >= 0): { "planes" };
case ((_normalized find "naval") >= 0): { "naval" };
case ((_normalized find "other") >= 0): { "other" };
default { "" };
}
}],
["resolveCategory", compileFinal {
params [["_className", "", [""]]];
@ -36,6 +63,33 @@ GVAR(GarageHelperServiceBaseClass) = compileFinal createHashMapFromArray [
default { "other" };
}
}],
["resolveVGCategory", compileFinal {
params [["_className", "", [""]]];
if (_className isEqualTo "") exitWith { "other" };
switch (true) do {
case (_className isKindOf ["Car", configFile >> "CfgVehicles"]): { "cars" };
case (_className isKindOf ["Tank", configFile >> "CfgVehicles"]): { "armor" };
case (_className isKindOf ["Helicopter", configFile >> "CfgVehicles"]): { "helis" };
case (_className isKindOf ["Plane", configFile >> "CfgVehicles"]): { "planes" };
case (_className isKindOf ["Ship", configFile >> "CfgVehicles"]): { "naval" };
default { "other" };
}
}],
["resolveGarageCategoryLabel", compileFinal {
params [["_category", "", [""]]];
switch (_category) do {
case "cars": { "cars" };
case "armor": { "armored vehicles" };
case "helis": { "helicopters" };
case "planes": { "planes" };
case "naval": { "naval vehicles" };
case "other": { "other vehicles" };
default { "this vehicle type" };
}
}],
["resolveDisplayName", compileFinal {
params [["_className", "", [""]]];

View File

@ -4,7 +4,7 @@
* File: fnc_openVG.sqf
* Author: IDSolutions
* Date: 2025-12-16
* Last Update: 2026-01-30
* Last Update: 2026-04-22
* Public: No
*
* Description:
@ -20,11 +20,12 @@
* call forge_client_garage_fnc_openVG
*/
private _locations = (missionConfigFile >> "FORGE_CfgGarages" >> "locations") call BFUNC(getCfgData);
{
FORGE_VehSpawnPos = (_x select 1) getPos [5, (_x select 2)];
true;
} count _locations;
private _context = GVAR(GarageContextService) call ["getContext", []];
private _spawnLane = GVAR(GarageContextService) call ["getSpawnLane", [_context getOrDefault ["garageType", ""], _context]];
FORGE_VehSpawnPos = _spawnLane getOrDefault ["spawnPosition", player getPos [8, getDir player]];
missionNamespace setVariable [QGVAR(activeVGContext), _context];
missionNamespace setVariable [QGVAR(activeVGNearbyVehicles), + (FORGE_VehSpawnPos nearEntities [["Car", "Tank", "Air", "Ship"], 15])];
BIS_fnc_garage_center = createVehicle ["Land_HelipadEmpty_F", FORGE_VehSpawnPos, [], 0, "NONE"];
BIS_fnc_garage_centerType = getText (configFile >> "CfgVehicles" >> "B_Quadbike_01_F" >> "model");
@ -53,16 +54,41 @@ if !(GVAR(isPreLoaded)) then {
}] call BFUNC(addScriptedEventHandler);
[missionNamespace, "garageClosed", {
private _nearestObjects = BIS_fnc_garage_center nearEntities [["Car","Tank","Air","Ship"], 15];
private _nearbyVehicles = BIS_fnc_garage_center nearEntities [["Car", "Tank", "Air", "Ship"], 15];
private _preExistingVehicles = missionNamespace getVariable [QGVAR(activeVGNearbyVehicles), []];
private _spawnedVehicles = _nearbyVehicles select { !(_x in _preExistingVehicles) };
if (_spawnedVehicles isNotEqualTo []) then {
private _spawnedVehiclePairs = _spawnedVehicles apply { [_x distance2D BIS_fnc_garage_center, _x] };
_spawnedVehiclePairs sort true;
private _obj = (_spawnedVehiclePairs select 0) param [1, objNull];
if (isNull _obj) exitWith {
missionNamespace setVariable [QGVAR(activeVGNearbyVehicles), nil];
missionNamespace setVariable [QGVAR(activeVGContext), nil];
};
if (!isNil "_nearestObjects") then {
private _obj = _nearestObjects select 0;
private _veh = typeOf _obj;
private _textures = getObjectTextures _obj;
private _animationNames = animationNames _obj;
private _context = missionNamespace getVariable [QGVAR(activeVGContext), createHashMap];
private _spawnCategory = GVAR(GarageHelperService) call ["resolveVGCategory", [_veh]];
private _spawnLane = GVAR(GarageContextService) call ["getExactSpawnLane", [_spawnCategory, _context]];
private _spawnLabel = GVAR(GarageHelperService) call ["resolveGarageCategoryLabel", [_spawnCategory]];
{ deleteVehicle _x } forEach _nearestObjects;
private _createVehicle = createVehicle [_veh, FORGE_VehSpawnPos, [], 0, "CAN_COLLIDE"];
{ deleteVehicle _x } forEach _spawnedVehicles;
if (_spawnLane isEqualTo createHashMap) exitWith {
missionNamespace setVariable [QGVAR(activeVGNearbyVehicles), nil];
missionNamespace setVariable [QGVAR(activeVGContext), nil];
private _params = ["warning", "Virtual Garage", format ["This garage does not support spawning %1.", _spawnLabel], 4000];
EGVAR(notifications,NotificationService) call ["create", _params];
};
private _spawnPosition = _spawnLane getOrDefault ["spawnPosition", FORGE_VehSpawnPos];
private _spawnHeading = _spawnLane getOrDefault ["spawnHeading", getDir _obj];
private _createVehicle = createVehicle [_veh, _spawnPosition, [], 0, "CAN_COLLIDE"];
_createVehicle setDir _spawnHeading;
if (_textures isNotEqualTo []) then {
private _count = 0;
@ -81,6 +107,9 @@ if !(GVAR(isPreLoaded)) then {
};
};
};
missionNamespace setVariable [QGVAR(activeVGNearbyVehicles), nil];
missionNamespace setVariable [QGVAR(activeVGContext), nil];
}] call BFUNC(addScriptedEventHandler);
GVAR(isPreLoaded) = true;

View File

@ -20,14 +20,37 @@
* call forge_server_garage_fnc_initGarage
*/
private _resolveGarageType = {
params [["_value", "", [""]]];
private _normalized = toLowerANSI (trim _value);
switch (true) do {
case ((_normalized find "cars") >= 0): { "cars" };
case ((_normalized find "armor") >= 0): { "armor" };
case ((_normalized find "helis") >= 0): { "helis" };
case ((_normalized find "planes") >= 0): { "planes" };
case ((_normalized find "naval") >= 0): { "naval" };
case ((_normalized find "other") >= 0): { "other" };
default { "" };
}
};
private _garages = (allVariables missionNamespace) select {
private _var = missionNamespace getVariable _x;
("garage" in _x) && { _var isEqualType objNull } && { !isNull _var }
((toLowerANSI _x) find "garage") >= 0 && { _var isEqualType objNull } && { !isNull _var }
};
if (_garages isEqualTo []) exitWith { ["INFO", "No editor-placed garages found."] call EFUNC(common,log) };
{
private _garage = missionNamespace getVariable _x;
private _garageName = _x;
private _garage = missionNamespace getVariable _garageName;
SETPVAR(_garage,isGarage,true);
if ((_garage getVariable ["garageType", ""]) isEqualTo "") then {
private _garageType = _garageName call _resolveGarageType;
if (_garageType isNotEqualTo "") then {
SETPVAR(_garage,garageType,_garageType);
};
};
} forEach _garages;

View File

@ -33,6 +33,9 @@ password = "root"
connect_timeout_ms = 5000
```
For install links and Forge-specific setup steps, see
[SurrealDB Setup](../../../docs/surrealdb-setup.md).
## References
- [API Reference](./api-reference.md)

View File

@ -19,9 +19,12 @@ browser events through `forge_client_garage_fnc_handleUIEvents`.
call forge_client_garage_fnc_openVG;
```
The virtual garage uses mission-configured `FORGE_CfgGarages` locations to set
the spawn/preview position, opens the BIS garage interface, and restricts the
available vehicle lists from the virtual garage repository.
The virtual garage resolves the active interaction object near the player,
discovers nearby `garage*` markers placed in Eden, chooses the matching spawn
lane for the selected vehicle type, opens the BIS garage interface, and
restricts the available vehicle lists from the virtual garage repository. When
the BIS garage closes, only the vehicle selected in that virtual garage session
is finalized and spawned onto the resolved lane.
## Client Services
@ -79,8 +82,24 @@ _object setVariable ["isGarage", true, true];
_object setVariable ["garageType", "cars", true];
```
Virtual garage access also requires configured garage locations in mission
config so the preview/spawn position can be resolved.
When using the server garage auto-init flow, editor-placed objects whose
variable names contain `garage` are marked as garage interaction points and
their `garageType` can be inferred from the name.
Virtual garage spawn lanes are resolved from empty markers placed in Eden. The
marker name should contain `garage` and one of the six supported category names:
`cars`, `armor`, `helis`, `planes`, `naval`, or `other`. Markers are matched to
the nearby interaction object by proximity, and names that include the garage
object's variable name are preferred when multiple garages exist.
Vehicle spawning is strict by category. If the active garage site does not have
a matching local marker for the vehicle category being retrieved or spawned from
the virtual garage, the request is blocked and the player is shown a message.
Nearby world vehicles are not used as virtual garage spawn candidates. They are
only checked to determine whether the resolved spawn position is blocked. If
any vehicle is within 5 meters of the spawn marker when the virtual garage is
opened, the session is blocked and the player is shown a warning.
## Authoritative State

View File

@ -4,6 +4,9 @@ This guide covers the usual path for adding or changing a Forge module.
## Local Checks
Before running storage-backed workflows locally, complete
[SurrealDB Setup](./surrealdb-setup.md).
Run these before pushing Rust or extension changes:
```powershell

View File

@ -125,6 +125,9 @@ password = "root"
connect_timeout_ms = 5000
```
For install links and role-based setup guidance, see
[SurrealDB Setup](./surrealdb-setup.md).
Check persistence readiness before issuing commands that require storage:
```sqf

View File

@ -13,6 +13,8 @@ collects framework-level documentation for those pieces.
crates.
- [Development Guide](./DEVELOPMENT_GUIDE.md): how to add or change a module
without breaking the framework boundaries.
- [SurrealDB Setup](./surrealdb-setup.md): where to get SurrealDB or
Surrealist and how to connect Forge to it for local or live use.
## Server and Extension Usage Guides

101
docs/surrealdb-setup.md Normal file
View File

@ -0,0 +1,101 @@
# SurrealDB Setup
Forge uses SurrealDB for durable storage. The Rust server extension connects to
SurrealDB on startup and applies Forge schema modules automatically, so setup
comes down to running a reachable database and matching the Forge config.
## Choose the Right Path
### Developer or Server Operator
Use this path if you are building Forge, running a local test server, or
hosting the live Arma server.
Official SurrealDB resources:
- [SurrealDB install page](https://surrealdb.com/install)
- [SurrealDB CLI `start` reference](https://surrealdb.com/docs/reference/cli/surrealdb-cli/commands/start)
Install SurrealDB with the official method for your platform:
```powershell
# Windows
iwr https://windows.surrealdb.com -useb | iex
```
```bash
# macOS
brew install surrealdb/tap/surreal
```
```bash
# Linux
curl -sSf https://install.surrealdb.com | sh
```
For Forge, start a persistent local database instead of the default in-memory
mode:
```powershell
surreal start surrealkv://forge.db --bind 127.0.0.1:8000 --user root --pass root
```
Then copy `arma/server/extension/config.example.toml` to `config.toml` next to
`forge_server_x64.dll` and keep the values aligned with the database you
started:
```toml
[surreal]
endpoint = "127.0.0.1:8000"
namespace = "forge"
database = "main"
username = "root"
password = "root"
connect_timeout_ms = 5000
```
After that:
1. Start the Arma server with the Forge extension enabled.
2. Let the extension connect and apply the Forge schema modules.
3. Verify the connection state:
```sqf
"forge_server" callExtension ["status", []];
"forge_server" callExtension ["surreal:status", []];
```
If you change the endpoint, namespace, database, username, or password in
SurrealDB, change the same values in Forge's `config.toml`.
### Mission Designer or Community Manager/Leader
Use this path if you mostly need to inspect, query, or adjust data for a test
or live server and you are not changing Forge source code.
Official SurrealDB resources:
- [Surrealist installation](https://surrealdb.com/docs/explore/surrealist/installation)
- [Surrealist web app](https://app.surrealdb.com)
- [Surrealist local database serving](https://surrealdb.com/docs/explore/surrealist/concepts/local-database-serving)
Recommended approach:
1. Install **Surrealist Desktop**. It is the better fit for Forge because the
official docs note that the web app can be limited when connecting to
`localhost` or non-HTTPS endpoints.
2. Connect Surrealist to the same database Forge uses.
3. Use the values from the server's `config.toml`:
```text
Endpoint: http://127.0.0.1:8000
Namespace: forge
Database: main
Username: root
Password: root
```
If you need your own local sandbox instead of connecting to an existing Forge
server, install SurrealDB first and follow the developer/server-operator path
above. Surrealist Desktop can also launch a local database for you after the
`surreal` executable is installed and available on your `PATH`.

5
docus/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.nuxt
.output
.data
.nitro
node_modules

37
docus/README.md Normal file
View File

@ -0,0 +1,37 @@
# Forge Docs
This directory contains the online documentation site for the Forge framework.
The site is built with Nuxt and Docus, and its content is generated from the
repository's source markdown files.
## Local Development
Install dependencies:
```powershell
npm install
```
Start the docs site:
```powershell
npm run dev
```
The content tree is refreshed automatically from:
- `docs/`
- `arma/server/docs/`
## Production Build
```powershell
npm run build
```
Use these environment variables when deploying to a custom host:
- `DOCS_BASE_URL`
- `DOCS_SITE_URL`
- `DOCS_REPO_URL`
- `DOCS_REPO_BRANCH`

34
docus/app.config.ts Normal file
View File

@ -0,0 +1,34 @@
const repoUrl =
process.env.DOCS_REPO_URL ||
'https://github.com/InnovativeDevSolutions/forge';
const repoBranch = process.env.DOCS_REPO_BRANCH || 'master';
const siteUrl =
process.env.DOCS_SITE_URL ||
'https://innovativedevsolutions.github.io';
export default defineAppConfig({
site: {
name: 'Forge Framework',
description:
'Persistent Arma 3 framework with Rust services, SurrealDB storage, and browser-backed client UIs.',
url: siteUrl,
socials: {
github: 'InnovativeDevSolutions/forge'
}
},
github: {
url: repoUrl,
branch: repoBranch,
rootDir: 'docus'
},
footer: {
credits: 'Copyright © 2025-2026 Forge Framework',
links: [
{
icon: 'simple-icons:github',
href: repoUrl,
target: '_blank'
}
]
}
});

View File

@ -0,0 +1,2 @@
title: Getting Started
icon: i-lucide-rocket

View File

@ -0,0 +1,88 @@
# Getting Started
Use this section as the main entry point for the Forge framework.
Forge combines:
- Arma 3 client addons for UX and browser-hosted interfaces
- Arma 3 server addons for mission integration and authoritative flow control
- a Rust server extension for command routing and persistence
- shared Rust crates for models, repositories, and services
- SurrealDB for durable storage
## Common Commands
```powershell
cargo test
npm run build:webui
.\build-arma.ps1
```
## Start Here
::u-page-grid
:::u-page-card
---
icon: i-lucide-network
title: Architecture
to: /getting-started/architecture
---
Understand how SQF, Rust services, SurrealDB, and browser UIs fit together.
:::
:::u-page-card
---
icon: i-lucide-boxes
title: Module Reference
to: /getting-started/module-reference
---
Review gameplay domains, infrastructure modules, and extension command groups.
:::
:::u-page-card
---
icon: i-lucide-wrench
title: Development Guide
to: /getting-started/development
---
See the rules for adding modules and changing boundaries without regressions.
:::
:::u-page-card
---
icon: i-lucide-database
title: SurrealDB Setup
to: /getting-started/surrealdb-setup
---
Install SurrealDB, match Forge config values, and choose the right setup path
for developers or admin-facing roles.
:::
:::u-page-card
---
icon: i-lucide-server-cog
title: Server Extension
to: /server-extension
---
Follow the extension architecture, API surface, and SQF usage examples.
:::
:::u-page-card
---
icon: i-lucide-layers-3
title: Server Modules
to: /server-modules
---
Dive into the actor, bank, CAD, garage, locker, organization, phone, store,
task, and owned-storage guides.
:::
:::u-page-card
---
icon: i-lucide-monitor-smartphone
title: Client Addons
to: /client-addons
---
Explore the client bridge model and addon-specific browser integration rules.
:::
::

View File

@ -0,0 +1,136 @@
# Framework Architecture
Forge is organized around domain modules. A domain usually has SQF addon
entry points, Rust models, repository traits, service logic, extension command
handlers, and optional browser UI.
## Runtime Flow
```text
Arma client UI or SQF action
-> client addon bridge
-> server addon function
-> forge_server callExtension command
-> extension command group
-> forge-services domain service
-> forge-repositories trait
-> SurrealDB repository implementation
-> SurrealDB
```
For small payloads, server SQF calls `forge_server` directly through the
extension bridge. For large payloads, `arma/server/addons/extension` stages
request and response chunks through the extension transport module.
## Main Layers
### Client Addons
Client addons live under `arma/client/addons`. They own local player UX,
keybinds, browser UI dialogs, and UI-to-SQF event handling. When a client needs
durable or authoritative state, it routes work to the matching server addon
instead of touching persistence directly.
### Server Addons
Server addons live under `arma/server/addons`. They own server-side SQF
initialization, game-object integration, validation near the Arma runtime, and
calls into the Rust extension. The `extension` addon is the shared bridge for
`callExtension` and transport handling.
### Rust Extension
The server extension lives under `arma/server/extension`. It registers the
`forge_server` command groups, loads configuration, initializes SurrealDB, and
maps SQF command inputs into service calls.
The extension should stay thin:
- Parse and validate command arguments that arrive from SQF.
- Resolve Arma-specific context such as player UID when required.
- Call the matching service.
- Serialize the service result back to JSON or a simple string.
### Shared Rust Crates
The `lib` workspace contains reusable Rust crates:
- `forge-models`: shared domain structs and serialization rules.
- `forge-repositories`: storage-agnostic repository traits and in-memory
implementations used by tests and hot-state services.
- `forge-services`: domain behavior, validation, and mutation workflows.
- `forge-shared`: cross-crate helpers.
### Persistence
Durable storage is SurrealDB. Schema modules live under
`arma/server/extension/src/schema`, and concrete SurrealDB repository
implementations live under `arma/server/extension/src/storage`.
Repository traits stay in `lib/repositories` so service logic remains testable
without a database.
## Hot State
Several domains have `hot` command groups. Hot state keeps a runtime copy of
frequently accessed data in memory, then saves it back to durable storage when
requested. This is useful for player state that changes often during a session.
Typical hot-state flow:
```text
actor:hot:init
actor:hot:get
actor:hot:override
actor:hot:save
actor:hot:remove
```
Use hot state for session workflows. Use normal domain commands for direct
durable CRUD operations.
## Transport Layer
The transport layer exists because Arma extension calls have practical payload
size limits. It provides chunked request and response handling while still
routing to the same domain command groups.
Common direct command:
```sqf
"forge_server" callExtension ["status", []];
```
Common transport path:
```text
server addon fnc_extCall
-> transport:request:append
-> transport:invoke_stored
-> transport:response:get
```
## Configuration
The server extension reads `config.toml` next to the extension DLL. The current
persistence section is:
```toml
[surreal]
endpoint = "127.0.0.1:8000"
namespace = "forge"
database = "main"
username = "root"
password = "root"
connect_timeout_ms = 5000
```
For install links and role-based setup guidance, see
[SurrealDB Setup](/getting-started/surrealdb-setup).
Check persistence readiness before issuing commands that require storage:
```sqf
"forge_server" callExtension ["status", []];
"forge_server" callExtension ["surreal:status", []];
```

View File

@ -0,0 +1,221 @@
# Module Reference
This reference lists the main Forge modules and where each layer lives.
## Directory Map
```text
arma/client/addons/ Client-side Arma addons and browser UIs
arma/server/addons/ Server-side Arma addons and extension bridge
arma/server/extension/ Rust arma-rs extension and SurrealDB adapters
bin/icom/ Interprocess communication helper
lib/models/ Shared domain data models
lib/repositories/ Repository traits and in-memory stores
lib/services/ Domain services and workflow logic
lib/shared/ Cross-crate helpers
tools/ Web UI build tooling
docs/ Framework-level documentation
```
## Gameplay Domains
| Domain | Purpose | Client addon | Server addon | Service/model layer | Extension group |
| --- | --- | --- | --- | --- | --- |
| Actor | Player identity, loadout, position, status, contact identifiers, and persistent character data. | `arma/client/addons/actor` | `arma/server/addons/actor` | `lib/models/src/actor.rs`, `lib/services/src/actor.rs` | `actor:*` |
| Bank | Player accounts, cash/bank balances, PIN validation, transfers, checkout charging, and transaction context. | `arma/client/addons/bank` | `arma/server/addons/bank` | `lib/models/src/bank.rs`, `lib/services/src/bank.rs` | `bank:*`, `bank:hot:*` |
| CAD | Dispatch requests, assignments, orders, activity stream, profiles, groups, and hydrated dispatcher views. | `arma/client/addons/cad` | `arma/server/addons/cad` | `lib/models/src/cad.rs`, `lib/services/src/cad.rs` | `cad:*` |
| Garage | Player vehicle storage with plate IDs, fuel, damage, and hit point state. | `arma/client/addons/garage` | `arma/server/addons/garage` | `lib/models/src/garage.rs`, `lib/services/src/garage.rs` | `garage:*`, `garage:hot:*` |
| Locker | Player item storage keyed by classname with category and amount. | `arma/client/addons/locker` | `arma/server/addons/locker` | `lib/models/src/locker.rs`, `lib/services/src/locker.rs` | `locker:*`, `locker:hot:*` |
| Organization | Player organizations, membership, treasury, credit lines, shared assets, and fleet data. | `arma/client/addons/org` | `arma/server/addons/org` | `lib/models/src/org.rs`, `lib/services/src/org.rs` | `org:*`, `org:hot:*` |
| 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 | Storefront entity setup, catalog hydration, 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 | Server-owned mission/task flows, catalog, ownership, status, participant tracking, rewards, and defuse 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 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:*` |
Server and extension guides:
[Actor](/server-modules/actor),
[Bank](/server-modules/bank),
[CAD](/server-modules/cad),
[Economy](/server-modules/economy),
[Garage](/server-modules/garage),
[Locker](/server-modules/locker),
[Organization](/server-modules/organization),
[Owned Storage](/server-modules/owned-storage),
[Phone](/server-modules/phone),
[Store](/server-modules/store),
[Task](/server-modules/task).
Client guides:
[Client Overview](/client-addons),
[Main](/client-addons/main),
[Common](/client-addons/common),
[Actor](/client-addons/actor),
[Bank](/client-addons/bank),
[CAD](/client-addons/cad),
[Garage](/client-addons/garage),
[Locker](/client-addons/locker),
[Notifications](/client-addons/notifications),
[Organization](/client-addons/organization),
[Phone](/client-addons/phone),
[Store](/client-addons/store).
## Infrastructure Modules
| Module | Purpose | Location |
| --- | --- | --- |
| `common` | Shared SQF helpers, base stores, utility functions, and shared UI bridge pieces. | `arma/client/addons/common`, `arma/server/addons/common` |
| `extension` | Server SQF bridge around `forge_server` extension calls and chunked transport. | `arma/server/addons/extension` |
| `main` | Mod-level configuration, pre-init wiring, and server/client startup glue. | `arma/client/addons/main`, `arma/server/addons/main` |
| `economy` | Server-side fuel, medical, and service economy helpers. Fuel and repair charge organization hot state; medical charges player bank/cash first, then organization funds with repayable member debt when personal funds cannot cover the bill. | `arma/server/addons/economy` |
| `notifications` | Client notification UI, sounds, and UI event handling. | `arma/client/addons/notifications` |
| `icom` | Rust helper for interprocess communication and event broadcasting. | `bin/icom`, `arma/server/extension/src/icom.rs` |
| `terrain` | Extension-side terrain export helper. | `arma/server/extension/src/terrain.rs` |
| `transport` | Chunked request/response handling for large extension payloads. | `arma/server/extension/src/transport.rs` |
| `surreal` | SurrealDB connection lifecycle and status reporting. | `arma/server/extension/src/surreal.rs` |
## Extension Command Groups
Commands are invoked with:
```sqf
"forge_server" callExtension ["group:command", [_arg1, _arg2]];
```
Nested groups use additional `:` separators, for example
`bank:hot:deposit`.
### Core
| Command | Purpose |
| --- | --- |
| `version` | Return the extension version string. |
| `status` | Return SurrealDB connection state. |
| `surreal:status` | Return SurrealDB connection state directly from the Surreal module. |
### Actor
| Command | Purpose |
| --- | --- |
| `actor:get` | Fetch actor data for a resolved player UID. |
| `actor:create` | Create actor data from JSON. |
| `actor:update` | Apply actor JSON updates. |
| `actor:exists` | Return `true` or `false`. |
| `actor:delete` | Delete actor data. |
| `actor:hot:init`, `actor:hot:get`, `actor:hot:keys`, `actor:hot:override`, `actor:hot:save`, `actor:hot:remove` | Manage actor hot state. |
See [Actor Usage Guide](/server-modules/actor) for examples.
### Bank
| Command | Purpose |
| --- | --- |
| `bank:get`, `bank:create`, `bank:update`, `bank:exists`, `bank:delete` | Durable bank CRUD. |
| `bank:hot:init`, `bank:hot:get`, `bank:hot:override`, `bank:hot:patch`, `bank:hot:save`, `bank:hot:remove` | Manage bank hot state. |
| `bank:hot:deposit`, `bank:hot:withdraw`, `bank:hot:deposit_earnings`, `bank:hot:transfer` | Mutate hot bank balances with operation context. |
| `bank:hot:charge_checkout` | Charge a checkout against hot bank state. |
| `bank:hot:validate_pin` | Validate a PIN for bank operations. |
See [Bank Usage Guide](/server-modules/bank) for examples.
### Garage
| Command | Purpose |
| --- | --- |
| `garage:create`, `garage:get`, `garage:add`, `garage:update`, `garage:patch`, `garage:remove`, `garage:delete`, `garage:exists` | Durable player garage operations. |
| `garage:hot:init`, `garage:hot:get`, `garage:hot:override`, `garage:hot:add`, `garage:hot:remove_vehicle`, `garage:hot:save`, `garage:hot:remove` | Manage player garage hot state. |
See [Garage Usage Guide](/server-modules/garage) for examples.
### Locker
| Command | Purpose |
| --- | --- |
| `locker:create`, `locker:get`, `locker:add`, `locker:update`, `locker:patch`, `locker:remove`, `locker:delete`, `locker:exists` | Durable player locker operations. |
| `locker:hot:init`, `locker:hot:get`, `locker:hot:override`, `locker:hot:save`, `locker:hot:remove` | Manage player locker hot state. |
See [Locker Usage Guide](/server-modules/locker) for examples.
### Organization
| Command | Purpose |
| --- | --- |
| `org:get`, `org:create`, `org:update`, `org:exists`, `org:delete` | Durable organization CRUD. |
| `org:assets:get`, `org:assets:update` | Manage organization assets. |
| `org:fleet:get`, `org:fleet:update` | Manage organization fleet entries. |
| `org:members:get`, `org:members:add`, `org:members:remove` | Manage organization membership. |
| `org:hot:*` | Runtime organization workflows including registration, invites, credit lines, checkout charging, assets, fleet, leave, disband, save, and remove. |
See [Org Usage Guide](/server-modules/organization) for examples.
### Phone
| Command | Purpose |
| --- | --- |
| `phone:init` | Initialize phone state for a UID. |
| `phone:contacts:list`, `phone:contacts:add`, `phone:contacts:remove` | Manage contacts. |
| `phone:messages:list`, `phone:messages:thread`, `phone:messages:send`, `phone:messages:mark_read`, `phone:messages:delete` | Manage messages. |
| `phone:emails:list`, `phone:emails:send`, `phone:emails:mark_read`, `phone:emails:delete` | Manage emails. |
| `phone:remove` | Remove phone state for a UID. |
See [Phone Usage Guide](/server-modules/phone) for examples.
### CAD
| Command Group | Purpose |
| --- | --- |
| `cad:activity:append`, `cad:activity:recent` | Append and read recent activity. |
| `cad:assignments:list`, `cad:assignments:assign`, `cad:assignments:acknowledge`, `cad:assignments:decline`, `cad:assignments:upsert`, `cad:assignments:delete` | Manage dispatch assignments. |
| `cad:orders:list`, `cad:orders:create`, `cad:orders:create_from_context`, `cad:orders:close`, `cad:orders:upsert`, `cad:orders:delete` | Manage orders. |
| `cad:requests:list`, `cad:requests:submit`, `cad:requests:submit_from_context`, `cad:requests:close`, `cad:requests:upsert`, `cad:requests:delete` | Manage requests. |
| `cad:profiles:list`, `cad:profiles:update_from_context`, `cad:profiles:upsert`, `cad:profiles:delete` | Manage profiles. |
| `cad:groups:build` | Build grouped CAD state. |
| `cad:view:hydrate` | Build the dispatcher view model. |
See [CAD Usage Guide](/server-modules/cad) for examples.
### Task
| Command Group | Purpose |
| --- | --- |
| `task:reset` | Reset task state. |
| `task:catalog:active`, `task:catalog:get`, `task:catalog:upsert`, `task:catalog:delete` | Manage task catalog entries. |
| `task:ownership:bind`, `task:ownership:release`, `task:ownership:accept`, `task:ownership:reward_context` | Manage task ownership and rewards. |
| `task:status:set`, `task:status:get`, `task:status:clear` | Manage task status. |
| `task:defuse:increment`, `task:defuse:get` | Manage defuse counters. |
| `task:clear` | Clear task state. |
See [Task Usage Guide](/server-modules/task) for examples.
### Owned Storage
| Command Group | Purpose |
| --- | --- |
| `owned:garage:create`, `owned:garage:fetch`, `owned:garage:get`, `owned:garage:add`, `owned:garage:remove`, `owned:garage:delete`, `owned:garage:exists` | Owner-scoped vehicle storage. |
| `owned:garage:hot:*` | Owner-scoped vehicle hot state. |
| `owned:locker:create`, `owned:locker:fetch`, `owned:locker:get`, `owned:locker:add`, `owned:locker:remove`, `owned:locker:delete`, `owned:locker:exists` | Owner-scoped item storage. |
| `owned:locker:hot:*` | Owner-scoped item hot state. |
See [Owned Storage Usage Guide](/server-modules/owned-storage) for examples.
### Other Extension Groups
| Command Group | Purpose |
| --- | --- |
| `store:checkout` | Run store checkout behavior. |
| `icom:connect`, `icom:broadcast`, `icom:send_event` | ICom connection and event forwarding. |
| `terrain:exportSVG` | Export terrain data as SVG. |
| `transport:invoke`, `transport:invoke_stored` | Invoke commands through transport. |
| `transport:request:append`, `transport:request:clear` | Manage stored request chunks. |
| `transport:response:get`, `transport:response:clear` | Manage stored response chunks. |
## Rust Crates
| Crate | Role |
| --- | --- |
| `forge-models` | Domain models and validation. Keep these serializable and free of persistence details. |
| `forge-repositories` | Repository traits and in-memory implementations. Keep these storage-agnostic. |
| `forge-services` | Business rules and workflows. Depend on repository traits, not concrete databases. |
| `forge-shared` | Cross-crate helpers. Keep dependencies light. |
| `forge-server` | Arma extension crate. Owns command registration, SurrealDB runtime wiring, and concrete storage adapters. |
| `forge-icom` | ICom helper binary and client library. |

View File

@ -0,0 +1,132 @@
# Development Guide
This guide covers the usual path for adding or changing a Forge module.
## Local Checks
Before running storage-backed workflows locally, complete
[SurrealDB Setup](/getting-started/surrealdb-setup).
Run these before pushing Rust or extension changes:
```powershell
cargo fmt --check
cargo check
cargo test
cargo build
cargo clippy --all-targets --all-features -- -D warnings
```
Run this after changing browser UI sources:
```powershell
npm run build:webui
```
Build Arma packages with:
```powershell
.\build-arma.ps1
```
## Module Boundaries
Keep each layer responsible for one kind of work:
| Layer | Owns | Avoid |
| --- | --- | --- |
| `lib/models` | Data structures, serde defaults, validation helpers. | Database calls, SQF-specific context. |
| `lib/repositories` | Repository traits and in-memory stores. | SurrealDB-specific code. |
| `lib/services` | Business rules, workflow orchestration, structured results. | Arma engine calls, extension transport details. |
| `arma/server/extension` | Command parsing, context resolution, SurrealDB implementations, serialization to SQF. | Business rules that belong in services. |
| `arma/server/addons` | Server SQF lifecycle, game-object integration, calls into `forge_server`. | Direct database logic. |
| `arma/client/addons` | Client UI, keybinds, local UI events. | Authoritative persistence. |
## Adding a Domain Module
1. Add the model in `lib/models/src/<module>.rs`.
2. Export the model from `lib/models/src/lib.rs`.
3. Add repository traits in `lib/repositories/src/<module>.rs`.
4. Add in-memory repository support if the service needs tests or hot state.
5. Export the traits from `lib/repositories/src/lib.rs`.
6. Add service logic in `lib/services/src/<module>.rs`.
7. Add focused unit tests for service behavior.
8. Export the service from `lib/services/src/lib.rs`.
9. Add a SurrealDB schema module under `arma/server/extension/src/schema`.
10. Add the concrete storage adapter under `arma/server/extension/src/storage`.
11. Register the storage adapter in `arma/server/extension/src/storage.rs`.
12. Add an extension command group under `arma/server/extension/src/<module>.rs`.
13. Register the command group in `arma/server/extension/src/lib.rs`.
14. Add server addon functions under `arma/server/addons/<module>` if SQF needs a module-level API.
15. Add client addon or browser UI files under `arma/client/addons/<module>` if the module has player-facing UI.
16. Add documentation in `docs/` and module-level READMEs.
## Extension Command Rules
Commands should return one of these forms:
- JSON string for structured results.
- `"true"` or `"false"` for simple existence and boolean operations.
- `"OK"` for successful destructive operations with no response body.
- `"Error: <message>"` for failures.
Prefer stable JSON shapes over ad hoc strings. SQF callers should always check
for the `"Error:"` prefix before parsing JSON.
Example:
```sqf
private _result = "forge_server" callExtension ["actor:get", [getPlayerUID player]];
private _payload = _result select 0;
if (_payload find "Error:" == 0) exitWith {
systemChat format ["Actor request failed: %1", _payload];
};
private _actor = fromJSON _payload;
```
## Persistence Rules
SurrealDB is the durable store. Keep database-specific mapping in the extension
storage adapters, not in services or repository traits.
When changing persisted data:
- Update or add the matching `.surql` schema module.
- Update the concrete storage adapter.
- Preserve existing records when possible through serde defaults or migration
logic.
- Add tests at the service level for behavior, and add storage tests only when
database mapping is the risk.
## Hot-State Rules
Use hot state for data that is read or mutated frequently during a player
session. Hot-state modules usually provide:
- `init` to load durable state into memory.
- `get` to read the runtime copy.
- `override` or focused mutation commands to update the runtime copy.
- `save` to write the runtime copy back to SurrealDB.
- `remove` to evict the runtime copy.
Do not assume hot state is durable until `save` succeeds.
## Web UI Rules
Browser UI source files live under each client addon. Built assets usually land
under that addon's `ui/_site` directory.
Use the existing common bridge in `arma/client/addons/common` when a UI needs
to send events back to SQF. Keep UI state and rendering in JavaScript, and keep
server-authoritative decisions in server SQF or Rust services.
## Documentation Checklist
When adding or changing a module, update:
- `docs/MODULE_REFERENCE.md` for framework-level inventory.
- A module-specific README in the addon directory when SQF or UI usage changes.
- `arma/server/docs/api-reference.md` when extension commands change.
- Existing usage guides when payload shapes or workflows change.

View File

@ -0,0 +1,101 @@
# SurrealDB Setup
Forge uses SurrealDB for durable storage. The Rust server extension connects to
SurrealDB on startup and applies Forge schema modules automatically, so setup
comes down to running a reachable database and matching the Forge config.
## Choose the Right Path
### Developer or Server Operator
Use this path if you are building Forge, running a local test server, or
hosting the live Arma server.
Official SurrealDB resources:
- [SurrealDB install page](https://surrealdb.com/install)
- [SurrealDB CLI `start` reference](https://surrealdb.com/docs/reference/cli/surrealdb-cli/commands/start)
Install SurrealDB with the official method for your platform:
```powershell
# Windows
iwr https://windows.surrealdb.com -useb | iex
```
```bash
# macOS
brew install surrealdb/tap/surreal
```
```bash
# Linux
curl -sSf https://install.surrealdb.com | sh
```
For Forge, start a persistent local database instead of the default in-memory
mode:
```powershell
surreal start surrealkv://forge.db --bind 127.0.0.1:8000 --user root --pass root
```
Then copy `arma/server/extension/config.example.toml` to `config.toml` next to
`forge_server_x64.dll` and keep the values aligned with the database you
started:
```toml
[surreal]
endpoint = "127.0.0.1:8000"
namespace = "forge"
database = "main"
username = "root"
password = "root"
connect_timeout_ms = 5000
```
After that:
1. Start the Arma server with the Forge extension enabled.
2. Let the extension connect and apply the Forge schema modules.
3. Verify the connection state:
```sqf
"forge_server" callExtension ["status", []];
"forge_server" callExtension ["surreal:status", []];
```
If you change the endpoint, namespace, database, username, or password in
SurrealDB, change the same values in Forge's `config.toml`.
### Mission Designer or Community Manager/Leader
Use this path if you mostly need to inspect, query, or adjust data for a test
or live server and you are not changing Forge source code.
Official SurrealDB resources:
- [Surrealist installation](https://surrealdb.com/docs/explore/surrealist/installation)
- [Surrealist web app](https://app.surrealdb.com)
- [Surrealist local database serving](https://surrealdb.com/docs/explore/surrealist/concepts/local-database-serving)
Recommended approach:
1. Install **Surrealist Desktop**. It is the better fit for Forge because the
official docs note that the web app can be limited when connecting to
`localhost` or non-HTTPS endpoints.
2. Connect Surrealist to the same database Forge uses.
3. Use the values from the server's `config.toml`:
```text
Endpoint: http://127.0.0.1:8000
Namespace: forge
Database: main
Username: root
Password: root
```
If you need your own local sandbox instead of connecting to an existing Forge
server, install SurrealDB first and follow the developer/server-operator path
above. Surrealist Desktop can also launch a local database for you after the
`surreal` executable is installed and available on your `PATH`.

View File

@ -0,0 +1,43 @@
# Forge Server Extension
Forge Server is an arma-rs extension for Arma 3 server-side persistence and
domain services. It exposes game-facing commands and stores durable state in
SurrealDB.
## Architecture
SQF modules call `forge_server` through `fnc_extCall`. Small requests use the
direct `callExtension` path, while large payloads are staged through the
transport layer.
```text
SQF module
-> extension bridge
-> domain command
-> service layer
-> repository
-> SurrealDB
```
## Configuration
Copy `config.example.toml` to `config.toml` next to the extension DLL.
```toml
[surreal]
endpoint = "127.0.0.1:8000"
namespace = "forge"
database = "main"
username = "root"
password = "root"
connect_timeout_ms = 5000
```
For install links and Forge-specific setup steps, see
[SurrealDB Setup](/getting-started/surrealdb-setup).
## References
- [API Reference](/server-extension/api-reference)
- [Usage Examples](/server-extension/usage-examples)
- [Framework Module Guides](/getting-started)

View File

@ -0,0 +1,48 @@
# Forge Server API Reference
The Forge server extension exposes domain-oriented commands through
`callExtension`. Persistent data is stored through the configured SurrealDB
connection and schema modules.
## Core Commands
```sqf
"forge_server" callExtension ["version", []];
"forge_server" callExtension ["status", []];
"forge_server" callExtension ["surreal:status", []];
```
`status` and `surreal:status` return `initializing`, `connected`, or `failed`.
## Domain Commands
Game systems should call the domain APIs instead of raw database operations:
- `actor:*`
- `bank:*`
- `garage:*`
- `locker:*`
- `org:*`
- `phone:*`
- `store:*`
- `task:*`
- `cad:*`
- `owned:garage:*`
- `owned:locker:*`
- `transport:*`
Large request and response payloads are routed through the transport layer when
needed by `forge_server_addons_extension_fnc_extCall`.
## Module Guides
- [Actor](/server-modules/actor)
- [Bank](/server-modules/bank)
- [CAD](/server-modules/cad)
- [Garage](/server-modules/garage)
- [Locker](/server-modules/locker)
- [Organization](/server-modules/organization)
- [Owned Storage](/server-modules/owned-storage)
- [Phone](/server-modules/phone)
- [Store](/server-modules/store)
- [Task](/server-modules/task)

View File

@ -0,0 +1,47 @@
# Forge Server Usage Examples
These examples use the domain command surface exposed by the extension.
Persistence is handled by the server through SurrealDB.
## Status Check
```sqf
["status", []] call forge_server_extension_fnc_extCall params ["_status", "_ok"];
if (_ok && {_status isEqualTo "connected"}) then {
systemChat "Forge persistence is online.";
};
```
## Actor Fetch
```sqf
private _uid = getPlayerUID player;
["actor:get", [_uid]] call forge_server_extension_fnc_extCall params ["_payload", "_ok"];
if (_ok) then {
private _actor = fromJSON _payload;
systemChat format ["Loaded actor %1", _actor getOrDefault ["uid", _uid]];
};
```
## Store Checkout
```sqf
private _checkout = createHashMapFromArray [
["requesterUid", getPlayerUID player],
["requesterName", name player],
["orgId", "default"],
["requesterIsDefaultOrgCeo", false],
["paymentMethod", "bank"],
["items", [
createHashMapFromArray [
["classname", "FirstAidKit"],
["category", "item"],
["priceValue", 50],
["quantity", 2]
]
]],
["vehicles", []]
];
["store:checkout", [toJSON _checkout]] call forge_server_extension_fnc_extCall;
```

View File

@ -0,0 +1,2 @@
title: Server Modules
icon: i-lucide-layers-3

View File

@ -0,0 +1,113 @@
# Server Module Guides
These pages document the authoritative server-side workflows in Forge.
Most modules follow the same shape:
1. Server SQF gathers game context and validates mission/runtime assumptions.
2. The `forge_server` extension routes the request into the matching command group.
3. Services apply business rules through storage-agnostic repository traits.
4. The extension persists durable state through SurrealDB adapters when needed.
## Gameplay Domains
::u-page-grid
:::u-page-card
---
icon: i-lucide-user-round
title: Actor
to: /server-modules/actor
---
Persistent player identity, position, loadout, contact fields, and hot state.
:::
:::u-page-card
---
icon: i-lucide-wallet
title: Bank
to: /server-modules/bank
---
Player funds, transfers, PIN validation, checkout charging, and bank hot state.
:::
:::u-page-card
---
icon: i-lucide-map
title: CAD
to: /server-modules/cad
---
Dispatch requests, assignments, profiles, grouped state, and hydrated views.
:::
:::u-page-card
---
icon: i-lucide-ambulance
title: Economy
to: /server-modules/economy
---
Fuel, service, and medical charging rules across player and organization funds.
:::
:::u-page-card
---
icon: i-lucide-car-front
title: Garage
to: /server-modules/garage
---
Vehicle storage, hot-state updates, and persistence of vehicle condition.
:::
:::u-page-card
---
icon: i-lucide-package
title: Locker
to: /server-modules/locker
---
Player inventory storage, unique item limits, and locker hot-state behavior.
:::
:::u-page-card
---
icon: i-lucide-building-2
title: Organization
to: /server-modules/organization
---
Membership, treasury, shared assets, fleet, and organization hot workflows.
:::
:::u-page-card
---
icon: i-lucide-key-round
title: Owned Storage
to: /server-modules/owned-storage
---
Owner-scoped locker and vehicle unlock storage used by org-linked features.
:::
:::u-page-card
---
icon: i-lucide-smartphone
title: Phone
to: /server-modules/phone
---
Contacts, message threads, and email state for in-game phone workflows.
:::
:::u-page-card
---
icon: i-lucide-shopping-cart
title: Store
to: /server-modules/store
---
Checkout orchestration across pricing, grants, payment sources, and rollback.
:::
:::u-page-card
---
icon: i-lucide-flag
title: Task
to: /server-modules/task
---
Task catalog, ownership, status transitions, defuse counters, and rewards.
:::
::

View 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;
```

View File

@ -0,0 +1,151 @@
# 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.
## Server SQF Module
The server addon uses two long-lived module objects:
- `StorefrontStore` is the storefront workflow facade. It builds hydrate
payloads, validates checkout requests, calls the Rust `store:checkout`
command, syncs UI patches, and asks related module stores to save hot state.
- `StoreCatalogService` scans configured item and vehicle categories, builds
catalog responses, resolves checkout entries, and calculates authoritative
prices.
Editor-placed store entities are initialized by `fnc_initStore` during store
post-init. The initializer matches non-null mission namespace objects whose
variable names contain `store` and sets `isStore = true`, following the same
pattern used by garage entities.
## 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;
```

View File

@ -0,0 +1,289 @@
# 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.
The server addon at `arma/server/addons/task` also owns task execution:
creating BIS tasks, registering task entities, tracking participants, binding
task ownership, applying player/org rewards, and clearing task state when a
task completes.
Runtime dependencies:
- `forge_server_extension`
- `forge_server_common`
- `forge_server_actor`
- `forge_server_bank`
- `forge_server_org`
- `forge_client_notifications`
## 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`.
## Server Task Flows
The task addon provides these server-owned task flows:
- `attack`
- `defend`
- `defuse`
- `delivery`
- `destroy`
- `hostage`
- `hvt`
Mission designers can create tasks in four ways:
- Eden modules for editor-authored tasks.
- `forge_server_task_fnc_startTask` for script-authored tasks.
- `forge_server_task_fnc_handler` for pre-registered entities with reputation
gating and ownership binding. This path expects the BIS task and catalog
entry to already exist if map-task and CAD visibility are required.
- Direct task function calls for server-owned or mission-authored flows that
intentionally fall back to the `default` org. This path expects the BIS task
to already exist if map-task visibility is required.
The dynamic mission manager can also generate attack tasks from config. That is
system-generated content rather than a hand-authored task creation path.
## CAD Compatibility
CAD hydrates assignable tasks from `TaskStore.getActiveTaskCatalog`. A task must
have a catalog entry and active task status before CAD can show and assign it.
CAD-compatible creation paths:
- Eden modules: compatible because they delegate to
`forge_server_task_fnc_startTask`.
- `forge_server_task_fnc_startTask`: compatible because it registers the
catalog entry, creates the BIS task, and dispatches through the handler.
- Dynamic mission manager attack tasks: compatible because the mission manager
uses `forge_server_task_fnc_startTask`.
Limited or incompatible paths:
- `forge_server_task_fnc_handler`: only compatible if a catalog entry was
already registered elsewhere. The handler sets active status and ownership,
but it does not create the BIS task shown in the map task tab or upsert the
catalog entry.
- Direct task function calls: not CAD-compatible by default. They bypass
`startTask` and usually do not register the task catalog entry or active
status that CAD hydrates from. They also only call `BIS_fnc_taskSetState` at
completion/failure; they do not create the BIS task first.
## BIS Map Task Prerequisite
Only the Eden task modules and `forge_server_task_fnc_startTask` create the BIS
task automatically through `BIS_fnc_taskCreate`.
If a mission uses `forge_server_task_fnc_handler` directly or calls a task flow
function such as `forge_server_task_fnc_attack`, the mission must create a BIS
task with the same task ID before the Forge task completes. Otherwise the
success/failure `BIS_fnc_taskSetState` call has no visible map task to update.
That prerequisite can be satisfied with a vanilla Eden task creation module or
a scripted `BIS_fnc_taskCreate` call. `forge_server_task_fnc_startTask` is the
preferred Forge path because it handles BIS task creation, Forge catalog
registration, entity registration, and handler dispatch together.
## Eden Modules
Eden task modules are the normal designer-facing path. Place the module,
configure its attributes, and sync it to the relevant entities or grouping
modules.
Available task modules:
- `FORGE_Module_Attack`: sync directly to target units or vehicles.
- `FORGE_Module_Destroy`: sync directly to objects, vehicles, or units.
- `FORGE_Module_Defuse`: sync to `FORGE_Module_Explosives` and optionally
`FORGE_Module_Protected`.
- `FORGE_Module_Delivery`: sync to `FORGE_Module_Cargo`; the cargo module syncs
to cargo objects.
- `FORGE_Module_Hostage`: sync to `FORGE_Module_Hostages` and
`FORGE_Module_Shooters`.
- `FORGE_Module_HVT`: sync directly to HVT units.
- `FORGE_Module_Defend`: configure the defense marker and wave settings.
These modules delegate to `forge_server_task_fnc_startTask`.
## Scripted Start Task
Use `forge_server_task_fnc_startTask` when creating tasks from modules,
mission scripts, or generated mission-manager content. It registers task
entities, creates the BIS task, stores the catalog entry, then dispatches
through `forge_server_task_fnc_handler`.
```sqf
[
"attack",
"compound_attack_01",
getPosATL leader1,
"Attack: East Compound",
"Eliminate all hostile forces.",
createHashMapFromArray [["targets", [unit1, unit2, unit3]]],
createHashMapFromArray [
["limitFail", 0],
["limitSuccess", 3],
["funds", 50000],
["ratingFail", -10],
["ratingSuccess", 20],
["timeLimit", 900]
],
0,
getPlayerUID player,
"script"
] call forge_server_task_fnc_startTask;
```
## Handler Calls
Use `forge_server_task_fnc_handler` directly when the task entities are already
registered and you want reputation gating plus ownership binding. Create the
BIS task and catalog entry separately if this task should appear in the map
task tab or CAD:
```sqf
[
"delivery",
["delivery_1", 1, 3, "delivery_zone", 250000, -75, 300, false, false, 900],
250,
getPlayerUID player
] call forge_server_task_fnc_handler;
```
## Direct Task Calls
Direct task function calls still work for mission-authored or server-owned
tasks, but they do not provide a requester UID. Ownership falls back to the
`default` org. Create the BIS task separately if this task should appear in the
map task tab.
## Timer Semantics
Task time limits use `0` for no limit:
- attack `timeLimit`
- destroy `timeLimit`
- delivery `timeLimit`
- hostage `timeLimit`
- HVT `timeLimit`
Positive values are measured in seconds. Do not pass `-1` as a no-limit value;
the task runtime treats any non-zero task time limit as active.
Defuse IED timers are different. `iedTimer` must be greater than `0`, because
IEDs are expected to have an active countdown. The Eden defuse module defaults
to `300` seconds.
## 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];
};
```

View 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;
```

View 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];
};
```

View File

@ -0,0 +1,77 @@
# Economy Usage Guide
The economy server addon owns Arma-world service behavior for fuel, medical,
and repair interactions. It does not own money state. Money mutations go
through extension-backed bank and organization hot state before the world
effect is applied.
## Dependencies
- `forge_server_common` for logging, formatting, and player lookup.
- `forge_server_bank` for personal medical billing.
- `forge_server_org` for organization-funded services and medical fallback
debt.
- `forge_client_actor` and `forge_client_notifications` for targeted client
responses.
## Fuel
Fuel is organization-funded.
When refueling stops, `fnc_initFEconomyStore.sqf` calculates the fuel delta and
cost, charges the player's organization through `OrgStore chargeCheckout`, and
syncs the organization patch to online members. If organization funds cannot
cover the refuel, the vehicle is rolled back to the fuel level it had when the
session started.
Garage UI refuel requests use the server `RefuelService` event. The fuel store
calculates missing fuel from the vehicle config `fuelCapacity`, charges the
player's organization, and fills the vehicle only after the organization charge
succeeds.
## Repair
Repair is organization-funded.
Use the repair service event:
```sqf
[QEGVAR(economy,RepairService), [_target, _unit, _cost]] call CBA_fnc_serverEvent;
```
`_cost` is optional. Passing `-1` uses the configured service repair cost.
The target is only repaired after the organization charge succeeds.
The client garage UI forwards selected nearby vehicle repair requests through
the same event.
## Medical
Medical is player-funded first.
When a heal is requested, `fnc_initMEconomyStore.sqf` uses this billing order:
1. Charge the player's bank balance when it can cover the medical fee.
2. Otherwise charge the player's cash when it can cover the fee.
3. If neither personal balance can cover the fee, charge organization funds.
4. When organization funds cover the fallback charge, record the same amount as
debt on the player's organization credit line.
The heal only completes after one of those charges succeeds. If personal
billing is unavailable, the heal does not fall back to organization funds
because the server cannot verify that the player is unable to cover the fee.
## Medical Debt Repayment
Medical fallback debt uses the existing organization credit-line repayment
flow. The organization treasury is reduced when the service is rendered, and
the player's credit-line `amount_due` increases by the medical fee. When the
player repays through the bank credit-line repayment action, player bank funds
are moved back into the organization treasury.
## Hot-Cache Boundary
The economy addon should stay server-authoritative for world effects such as
vehicle fuel, vehicle repair, healing, respawn placement, and death inventory
movement. Bank and organization balances should continue to mutate through the
extension-backed hot-cache services.

View File

@ -0,0 +1,212 @@
# Garage Usage Guide
The garage module stores physical player vehicles. Each record keeps the
vehicle classname, generated plate UUID, fuel, overall damage, and detailed hit
point damage.
## Storage Model
Garage data is persisted through SurrealDB by the server extension.
```json
{
"plate-uuid": {
"plate": "plate-uuid",
"classname": "B_Quadbike_01_F",
"fuel": 1.0,
"damage": 0.0,
"hit_points": {
"names": ["hitengine"],
"selections": ["engine_hitpoint"],
"values": [0.0]
}
}
}
```
Rules validated by the Rust service:
- 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
private _result = "forge_server" callExtension ["garage:get", [getPlayerUID player]];
private _payload = _result select 0;
if (_payload find "Error:" == 0) exitWith {
systemChat format ["Garage error: %1", _payload];
};
private _garage = fromJSON _payload;
```
## Add a Vehicle
`garage:add` requires `classname`, `fuel`, `damage`, and `hit_points`.
```sqf
private _hitPointData = getAllHitPointsDamage _vehicle;
private _hitPoints = createHashMapFromArray [
["names", _hitPointData select 0],
["selections", _hitPointData select 1],
["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", [
getPlayerUID player,
toJSON _vehicleData
]];
private _payload = _result select 0;
if (_payload find "Error:" == 0) exitWith {
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
stored vehicle, compare returned keys before and after the add, or search by
classname if your workflow guarantees a unique pending vehicle.
```sqf
private _storedPlate = "";
{
private _vehicleRecord = _garage get _x;
if ((_vehicleRecord get "classname") == typeOf _vehicle) then {
_storedPlate = _x;
};
} forEach keys _garage;
```
## Patch a Vehicle
`garage:patch` updates selected fields for one plate. The `plate` field is
required. `fuel`, `damage`, and `hit_points` are optional.
```sqf
private _patch = createHashMapFromArray [
["plate", _vehicle getVariable ["forge_garage_plate", ""]],
["fuel", fuel _vehicle],
["damage", damage _vehicle]
];
private _result = "forge_server" callExtension ["garage:patch", [
getPlayerUID player,
toJSON _patch
]];
```
## Remove a Vehicle
`garage:remove` expects JSON with a `plate` field.
```sqf
private _remove = createHashMapFromArray [
["plate", _plate]
];
private _result = "forge_server" callExtension ["garage:remove", [
getPlayerUID player,
toJSON _remove
]];
```
## Spawn a Stored Vehicle
```sqf
fnc_spawnGarageVehicle = {
params ["_plate"];
private _result = "forge_server" callExtension ["garage:get", [getPlayerUID player]];
private _payload = _result select 0;
if (_payload find "Error:" == 0) exitWith {
hint format ["Failed to load garage: %1", _payload];
objNull
};
private _garage = fromJSON _payload;
private _vehicleData = _garage getOrDefault [_plate, createHashMap];
if (_vehicleData isEqualTo createHashMap) exitWith {
hint "Vehicle plate was not found in your garage.";
objNull
};
private _vehicle = (_vehicleData get "classname") createVehicle (player getPos [10, getDir player]);
_vehicle setFuel (_vehicleData getOrDefault ["fuel", 1]);
_vehicle setDamage (_vehicleData getOrDefault ["damage", 0]);
_vehicle setVariable ["forge_garage_plate", _plate, true];
private _hitPoints = _vehicleData getOrDefault ["hit_points", createHashMap];
private _names = _hitPoints getOrDefault ["names", []];
private _values = _hitPoints getOrDefault ["values", []];
{
_vehicle setHitPointDamage [_x, _values select _forEachIndex];
} forEach _names;
private _remove = createHashMapFromArray [["plate", _plate]];
"forge_server" callExtension ["garage:remove", [getPlayerUID player, toJSON _remove]];
_vehicle
};
```
## Hot State
The `garage:hot:*` commands keep a runtime copy of a player's garage and write
it back only when `garage:hot:save` runs.
| Command | Arguments | Returns |
| --- | --- | --- |
| `garage:hot:init` | `uid` | Vehicle map as JSON. |
| `garage:hot:get` | `uid` | Vehicle map as JSON. |
| `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`. |
Use hot state for session-heavy vehicle workflows. Use the durable commands for
simple store/retrieve operations.
## Best Practices
- Store the generated plate on spawned vehicles with `setVariable`.
- Use `garage:patch` for frequent fuel and damage syncs.
- Use `garage:update` only when replacing the whole vehicle map intentionally.
- Do not delete the world vehicle until `garage:add` succeeds.
- Treat vehicle maps as hash maps keyed by plate, not arrays.

View File

@ -0,0 +1,203 @@
# Locker Usage Guide
The locker module stores physical player inventory items by classname. It is
separate from the virtual arsenal unlock module documented in
[Owned Storage Usage Guide](/server-modules/owned-storage).
## Storage Model
Locker data is persisted through SurrealDB by the server extension.
```json
{
"arifle_MX_F": {
"category": "weapon",
"classname": "arifle_MX_F",
"amount": 1
}
}
```
Rules validated by the Rust service:
- A locker can contain up to 25 unique classnames.
- `category` and `classname` cannot be empty.
- `amount` must be greater than `0`.
- `locker:add` creates an empty locker automatically when one does not exist.
- `locker:get`, `locker:patch`, and `locker:remove` require an existing locker.
- `locker:remove` takes the classname directly, not a JSON object.
## Commands
All commands are called on the `locker` group.
| Command | Arguments | Returns |
| --- | --- | --- |
| `locker:create` | `uid` | Empty item map as JSON. |
| `locker:get` | `uid` | Item map as JSON. |
| `locker:add` | `uid`, `item_json` | Updated item map as JSON. |
| `locker:update` | `uid`, `items_json` | Replaced item map as JSON. |
| `locker:patch` | `uid`, `patch_json` | Updated item map as JSON. |
| `locker:remove` | `uid`, `classname` | Updated item map as JSON. |
| `locker:delete` | `uid` | `OK`. |
| `locker:exists` | `uid` | `true` or `false`. |
## Error Handling
Every command returns a string payload. Always check for the `Error:` prefix
before parsing JSON.
```sqf
private _result = "forge_server" callExtension ["locker:get", [getPlayerUID player]];
private _payload = _result select 0;
if (_payload find "Error:" == 0) exitWith {
systemChat format ["Locker error: %1", _payload];
};
private _locker = fromJSON _payload;
```
## Add an Item
`locker:add` creates or overwrites one classname entry.
```sqf
private _item = createHashMapFromArray [
["category", "weapon"],
["classname", "arifle_MX_F"],
["amount", 1]
];
private _result = "forge_server" callExtension ["locker:add", [
getPlayerUID player,
toJSON _item
]];
private _payload = _result select 0;
if (_payload find "Error:" == 0) exitWith {
hint format ["Failed to store item: %1", _payload];
};
private _locker = fromJSON _payload;
```
## Patch an Amount
`locker:patch` currently patches the `amount` field for an existing classname.
```sqf
private _patch = createHashMapFromArray [
["classname", "arifle_MX_F"],
["amount", 5]
];
private _result = "forge_server" callExtension ["locker:patch", [
getPlayerUID player,
toJSON _patch
]];
```
## Remove an Item
`locker:remove` takes the classname as the second argument.
```sqf
private _result = "forge_server" callExtension ["locker:remove", [
getPlayerUID player,
"arifle_MX_F"
]];
```
## Retrieve an Item
```sqf
fnc_retrieveLockerItem = {
params ["_classname"];
private _result = "forge_server" callExtension ["locker:get", [getPlayerUID player]];
private _payload = _result select 0;
if (_payload find "Error:" == 0) exitWith {
hint format ["Failed to load locker: %1", _payload];
false
};
private _locker = fromJSON _payload;
private _item = _locker getOrDefault [_classname, createHashMap];
if (_item isEqualTo createHashMap) exitWith {
hint "Item was not found in your locker.";
false
};
private _amount = _item getOrDefault ["amount", 0];
if (_amount <= 0) exitWith {
hint "Item is out of stock.";
false
};
if !(player canAdd _classname) exitWith {
hint "Not enough inventory space.";
false
};
player addItem _classname;
if (_amount > 1) then {
private _patch = createHashMapFromArray [
["classname", _classname],
["amount", _amount - 1]
];
"forge_server" callExtension ["locker:patch", [getPlayerUID player, toJSON _patch]];
} else {
"forge_server" callExtension ["locker:remove", [getPlayerUID player, _classname]];
};
true
};
```
## Replace the Whole Locker
`locker:update` replaces the whole item map. Use it for explicit bulk syncs,
not single-item changes.
```sqf
private _items = createHashMapFromArray [
["arifle_MX_F", createHashMapFromArray [
["category", "weapon"],
["classname", "arifle_MX_F"],
["amount", 1]
]]
];
private _result = "forge_server" callExtension ["locker:update", [
getPlayerUID player,
toJSON _items
]];
```
## Hot State
The `locker:hot:*` commands keep a runtime copy of a player's locker and write
it back only when `locker:hot:save` runs.
| Command | Arguments | Returns |
| --- | --- | --- |
| `locker:hot:init` | `uid` | Item map as JSON. |
| `locker:hot:get` | `uid` | Item map as JSON. |
| `locker:hot:override` | `uid`, `items_json` | Item map as JSON. |
| `locker:hot:save` | `uid` | Current hot item map as JSON. |
| `locker:hot:remove` | `uid` | `OK`. |
Use hot state for session-heavy locker workflows. Use the durable commands for
simple item deposits and withdrawals.
## Best Practices
- Keep categories normalized, for example `weapon`, `magazine`, `item`, or
`backpack`.
- Use `locker:patch` for quantity changes.
- Use `locker:remove` when quantity reaches zero.
- Treat the locker response as a hash map keyed by classname.
- Check capacity before bulk operations that may exceed 25 unique items.

View 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];
};
```

View 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](/server-modules/locker) and
[Garage Usage Guide](/server-modules/garage) 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];
};
```

View 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];
};
```

View File

@ -0,0 +1,2 @@
title: Client Addons
icon: i-lucide-monitor-smartphone

View File

@ -0,0 +1,125 @@
# Client Usage Guide
Forge Client contains the Arma client-side addons that open player interfaces,
handle browser events, cache client-visible state, and forward authoritative
requests to the server addons.
Use this guide as the entry point for client-side integration. Domain data,
validation, persistence, rewards, ownership, and checkout behavior remain
server-side responsibilities.
## Client Responsibilities
- Open Arma displays and `CT_WEBBROWSER` controls.
- Load browser UI assets from each addon's `ui/_site` folder.
- Receive browser alerts through `JSDialog` handlers.
- Translate browser events into local actions or CBA server events.
- Cache display state in client repositories.
- Push server responses back into browser UIs with `ExecJS`.
- Provide local-only utility state where the feature is intentionally local.
## Authoritative Boundaries
Client repositories are view state. They are useful for rendering, local UI
decisions, and short-lived session behavior, but they should not be treated as
durable state.
Authoritative state lives in:
- server SQF addons for mission and player workflow ownership
- the `forge_server` extension for durable and hot-state domain logic
- SurrealDB where the extension persists durable domain records
## Common Runtime Flow
Most browser-backed client addons follow this shape:
1. The addon creates a display, finds a browser control, and registers a
`JSDialog` event handler.
2. The browser loads an HTML entrypoint from `ui/_site`.
3. The browser sends JSON alerts with an `event` name and `data` payload.
4. `fnc_handleUIEvents.sqf` parses the alert and routes the event.
5. A bridge object or repository sends a CBA server event when server data is
needed.
6. Server responses are caught in `XEH_postInitClient.sqf`.
7. The bridge sends browser update events back through `ExecJS`.
Browser alert payload:
```json
{
"event": "module::action",
"data": {}
}
```
## Open UI Entry Points
| UI | Entry point |
| --- | --- |
| Actor menu | `call forge_client_actor_fnc_openUI;` |
| Bank | `call forge_client_bank_fnc_openUI;` |
| ATM | `[true] call forge_client_bank_fnc_openUI;` |
| CAD | `call forge_client_cad_fnc_openUI;` |
| Garage | `call forge_client_garage_fnc_openUI;` |
| Virtual garage | `call forge_client_garage_fnc_openVG;` |
| Organization portal | `call forge_client_org_fnc_openUI;` |
| Phone | `call forge_client_phone_fnc_openUI;` |
| Store | `call forge_client_store_fnc_openUI;` |
Notifications are normally opened during client initialization and then updated
through the notification event/service.
## Addon Guides
- [Client Main Usage Guide](/client-addons/main)
- [Client Common Usage Guide](/client-addons/common)
- [Client Actor Usage Guide](/client-addons/actor)
- [Client Bank Usage Guide](/client-addons/bank)
- [Client CAD Usage Guide](/client-addons/cad)
- [Client Garage Usage Guide](/client-addons/garage)
- [Client Locker Usage Guide](/client-addons/locker)
- [Client Notifications Usage Guide](/client-addons/notifications)
- [Client Organization Usage Guide](/client-addons/organization)
- [Client Phone Usage Guide](/client-addons/phone)
- [Client Store Usage Guide](/client-addons/store)
## Extension Calls
Client addons should usually call server SQF events, not the `forge_server`
extension directly. The server addon owns validation context and converts the
request into extension commands.
Example:
```sqf
[SRPC(bank,requestDeposit), [getPlayerUID player, 100]] call CFUNC(serverEvent);
```
Direct extension calls from client code bypass server authorization boundaries
and should be avoided.
## Browser Bridge Notes
`forge_client_common_fnc_initWebUIBridge` provides reusable bridge and screen
objects for newer browser UIs. It queues outbound events until a browser screen
is ready, then delivers payloads through:
```sqf
_control ctrlWebBrowserAction ["ExecJS", format ["ForgeBridge.receive(%1)", _json]];
```
Feature addons still own their event names, request payloads, and response
mapping.
## Development Checklist
- Keep feature-specific behavior in the owning addon.
- Send authoritative changes to the server addon.
- Use namespaced browser events such as `bank::deposit::request`.
- Treat `profileNamespace` as local player preference or utility state only.
- Make browser-ready events request the current server state before rendering
stale data.
- Queue or ignore bridge responses when the display is closed.
- Keep mission object setup on the mission/server side and client display logic
on the client side.

View File

@ -0,0 +1,48 @@
# Client Main Usage Guide
The client `main` addon provides the shared mod identity, version metadata,
CBA settings, and macro foundation used by the Forge client addons.
## Purpose
Use `forge_client_main` as the foundation dependency for client addons that
need Forge macros, function naming, settings, or mod-level configuration.
Feature logic should stay in the owning addon. `main` should remain limited to
shared client configuration and compile infrastructure.
## Key Files
| File | Purpose |
| --- | --- |
| `script_mod.hpp` | Client mod identity. |
| `script_version.hpp` | Client mod version values. |
| `script_macros.hpp` | Shared client macros. |
| `CfgSettings.hpp` | Client CBA settings. |
| `config.cpp` | Addon config and mod wiring. |
## Dependency Pattern
Feature addons normally depend on `forge_client_main` in their `config.cpp`.
```cpp
class forge_client_example {
requiredAddons[] = {
"forge_client_main"
};
};
```
## Usage Notes
- Put domain UI, repositories, and event handling in feature addons.
- Put reusable browser bridge behavior in `forge_client_common`.
- Put server-only behavior in `arma/server/addons`.
- Keep settings in `CfgSettings.hpp` when they apply to the client mod as a
whole or to a client feature toggle.
## Related Guides
- [Client Usage Guide](/client-addons)
- [Client Common Usage Guide](/client-addons/common)
- [Development Guide](/getting-started/development)

View File

@ -0,0 +1,107 @@
# Client Phone Usage Guide
The client phone addon provides the in-game phone UI for contacts, SMS
messages, email, and local utility apps such as notes, calendar events, world
clocks, and alarms.
## Open Phone UI
```sqf
call forge_client_phone_fnc_openUI;
```
The phone UI creates `RscPhone`, loads `ui/_site/index.html`, and routes
browser alerts through `forge_client_phone_fnc_handleUIEvents`.
## State Ownership
Contacts, messages, and emails are server-owned and requested through the
server phone addon.
Local utility app state is stored in `profileNamespace`:
- notes
- calendar events
- world clocks
- alarms
- theme/preferences
## Phone Class
`forge_client_phone_fnc_initClass` creates `GVAR(PhoneClass)`.
The phone class currently owns local notes, events, and settings helpers.
Contacts, messages, and emails continue to use server-backed request/response
events.
## Browser Events
### Session and Preferences
| Event | Client behavior |
| --- | --- |
| `phone::get::player` | Send player UID to browser with `setPlayerUid`. |
| `phone::get::theme` | Send saved light/dark theme to browser. |
| `phone::set::theme` | Save theme preference to `profileNamespace`. |
### Contacts
| Event | Client behavior |
| --- | --- |
| `phone::get::contacts` | Load cached contacts and request server refresh. |
| `phone::refresh::contacts` | Request contacts from server. |
| `phone::add::contact` | Add contact by phone number. |
| `phone::add::contact::by::phone` | Add contact by phone number. |
| `phone::add::contact::by::email` | Add contact by email. |
| `phone::remove::contact` | Remove contact by UID. |
### Messages
| Event | Client behavior |
| --- | --- |
| `phone::get::messages` | Request messages from server. |
| `phone::get::message::thread` | Request thread with another UID. |
| `phone::send::message` | Send SMS through server. |
| `phone::mark::message::read` | Mark message read on server. |
| `phone::delete::message` | Delete message on server. |
### Email
| Event | Client behavior |
| --- | --- |
| `phone::get::emails` | Request emails from server. |
| `phone::send::email` | Send email through server. |
| `phone::mark::email::read` | Mark email read on server. |
| `phone::delete::email` | Delete email on server. |
### Local Utility Apps
| Event | Client behavior |
| --- | --- |
| `phone::get::notes` | Load local notes. |
| `phone::save::note` | Save local note. |
| `phone::delete::note` | Delete local note. |
| `phone::get::events` | Load local calendar events. |
| `phone::save::event` | Save local calendar event. |
| `phone::delete::event` | Delete local calendar event. |
| `phone::get::clocks` | Load local world clocks. |
| `phone::save::clock` | Save local world clock. |
| `phone::delete::clock` | Delete local world clock. |
| `phone::get::alarms` | Load local alarms. |
| `phone::save::alarm` | Save local alarm. |
| `phone::delete::alarm` | Delete local alarm. |
| `phone::toggle::alarm` | Toggle local alarm enabled state. |
## Usage Rules
- Send contact, message, and email mutations to the server phone addon.
- Keep local-only utility apps in `profileNamespace` until they are migrated to
server-backed storage.
- Do not treat local phone utility state as shared multiplayer state.
- Validate required UID, phone, email, subject, and message fields before
sending server requests.
## Related Guides
- [Phone Usage Guide](/server-modules/phone)
- [Client Notifications Usage Guide](/client-addons/notifications)

View File

@ -0,0 +1,92 @@
# Client Store Usage Guide
The client store addon provides the storefront browser UI for catalog browsing,
category hydration, payment source display, cart handling, and checkout
requests.
## Open Store UI
```sqf
call forge_client_store_fnc_openUI;
```
The UI opens `RscStore`, loads `ui/_site/index.html`, and routes browser alerts
through `forge_client_store_fnc_handleUIEvents`.
## Bridge
`forge_client_store_fnc_initUIBridge` owns:
- browser control lookup
- store hydrate requests
- category requests
- checkout requests
- category hydrate/failure responses
- checkout success/failure responses
- store config refresh after successful checkout
Store currently uses its own `StoreUIBridge.receive(...)` browser bridge rather
than the shared `ForgeBridge.receive(...)` delivery used by newer bridges.
## Browser Events
| Event | Client behavior |
| --- | --- |
| `store::ready` | Request store hydrate from the server. |
| `store::category::request` | Request catalog items for a category. |
| `store::checkout::request` | Forward checkout JSON to the server. |
| `store::close` | Close the display. |
## Browser Response Events
| Event | Purpose |
| --- | --- |
| `store::hydrate` | Initial storefront/session/config payload. |
| `store::config::hydrate` | Refreshed payment/source config. |
| `store::category::hydrate` | Category catalog payload. |
| `store::category::failure` | Category request failure. |
| `store::checkout::success` | Checkout success payload. |
| `store::checkout::failure` | Checkout failure payload. |
## Category Requests
Category requests require a non-empty category value.
```json
{
"category": "weapons"
}
```
The client lowercases the category before forwarding it to the server store
addon.
## Checkout Requests
Checkout requests send a serialized checkout payload:
```json
{
"checkoutJson": "{\"items\":[],\"paymentSource\":\"cash\"}"
}
```
The client only forwards the checkout data. The server store addon and
extension validate prices, inventory grants, payment source authorization, and
integration with bank, organization, locker, and garage state.
After a successful checkout, the client asks the server for a fresh store config
payload so payment-source balances and permissions stay current.
## Authoritative State
Catalog data, prices, checkout validation, money movement, organization funds,
credit lines, locker grants, garage grants, and persistence are server-owned.
## Related Guides
- [Store Usage Guide](/server-modules/store)
- [Client Bank Usage Guide](/client-addons/bank)
- [Client Organization Usage Guide](/client-addons/organization)
- [Client Locker Usage Guide](/client-addons/locker)
- [Client Garage Usage Guide](/client-addons/garage)

View File

@ -0,0 +1,92 @@
# Client Common Usage Guide
The client `common` addon contains shared browser UI bridge declarations and
common client-side browser integration patterns.
## Purpose
Use `forge_client_common` when a browser-backed feature UI needs reusable
screen lifecycle behavior:
- active browser control tracking
- browser ready state
- pending event queues
- `ExecJS` payload delivery
- shared bridge object inheritance through `createHashMapObject`
Feature addons still own their app-specific events and server RPC mapping.
## Shared Bridge
Initialize the bridge declarations with:
```sqf
private _webUIDeclarations = call forge_client_common_fnc_initWebUIBridge;
private _bridgeDeclaration = _webUIDeclarations get "bridgeDeclaration";
```
Feature bridges can inherit from the shared declaration:
```sqf
GVAR(MyUIBridgeBaseClass) = compileFinal createHashMapFromArray [
["#base", _bridgeDeclaration],
["#type", "MyUIBridgeBaseClass"],
["handleReady", compileFinal {
params [["_control", controlNull, [controlNull]]];
_self call ["setActiveBrowserControl", [_control]];
_self call ["sendEvent", ["myAddon::hydrate", createHashMap, _control]];
}]
];
```
## Event Delivery
`sendEvent` builds this payload:
```json
{
"event": "myAddon::event",
"data": {}
}
```
If the browser control is missing or not ready, the payload is queued on the
screen object. When the screen marks ready, `flushPendingEvents` delivers the
queue.
## Screen Lifecycle
The shared screen object tracks:
| Field | Purpose |
| --- | --- |
| `control` | Active browser control. |
| `readyState` | Whether the browser app has sent its ready event. |
| `pendingEvents` | Outbound events waiting for a ready browser. |
Call `handleClose` or `dispose` when a display closes so stale controls and
queued events are cleared.
## Current Consumers
The common bridge pattern is used by the newer bank, CAD, garage, and
organization client bridges. Store currently keeps its own bridge object and
browser bridge function names.
## Usage Rules
- Keep bridge inheritance in feature addons thin and explicit.
- Keep shared code generic; do not add bank, CAD, org, or store-specific logic
to `common`.
- Prefer namespaced events such as `garage::sync`.
- Send hash maps or arrays that can be safely serialized with `toJSON`.
- Avoid direct extension calls from the client bridge; send CBA server events.
## Related Guides
- [Client Usage Guide](/client-addons)
- [Client Bank Usage Guide](/client-addons/bank)
- [Client CAD Usage Guide](/client-addons/cad)
- [Client Garage Usage Guide](/client-addons/garage)
- [Client Organization Usage Guide](/client-addons/organization)

View File

@ -0,0 +1,98 @@
# Client Actor Usage Guide
The client actor addon owns the player interaction menu and client-side actor
repository. It is the main launcher for nearby player actions and other Forge
client UIs.
## Open the Actor Menu
```sqf
call forge_client_actor_fnc_openUI;
```
The actor menu opens `RscActorMenu`, loads `ui/_site/index.html`, and routes
browser alerts through `forge_client_actor_fnc_handleUIEvents`.
## Repository
`forge_client_actor_fnc_initRepository` creates `GVAR(ActorRepository)`.
The repository:
- requests actor initialization from the server
- saves actor state through the server actor addon
- caches client-visible actor fields
- applies position, direction, stance, rank, and loadout on JIP sync when the
relevant settings allow it
- provides nearby interaction actions to the browser UI
Initialize actor state through the repository:
```sqf
GVAR(ActorRepository) call ["init", []];
```
Save actor state through the server:
```sqf
GVAR(ActorRepository) call ["save", [true]];
```
## Nearby Actions
The menu asks for nearby actions with:
```text
actor::get::actions
```
The repository scans objects within 5 meters and returns actions based on
mission object variables:
| Variable | Action |
| --- | --- |
| `storeType` | store |
| `isAtm` | ATM |
| `isBank` | bank |
| `isGarage` | garage |
| `garageType` | garage subtype |
| `isLocker` | virtual arsenal action when VA is enabled |
| `deviceType` | device action placeholder |
| nearby player unit | player interaction placeholder |
The response is pushed into the browser with `updateAvailableActions(...)`.
## Browser Events
| Event | Client behavior |
| --- | --- |
| `actor::get::actions` | Refresh nearby actions. |
| `actor::close::menu` | Close actor menu. |
| `actor::open::atm` | Open bank UI in ATM mode. |
| `actor::open::bank` | Open bank UI in bank mode. |
| `actor::open::cad` | Open CAD UI. |
| `actor::open::garage` | Open garage UI. |
| `actor::open::vgarage` | Open virtual garage. |
| `actor::open::org` | Open organization UI. |
| `actor::open::vlocker` | Open ACE arsenal on `FORGE_Locker_Box`. |
| `actor::open::phone` | Open phone UI. |
| `actor::open::store` | Open store UI. |
Device and player interaction events currently display placeholder feedback.
## Authoritative State
Actor persistence is server-owned. The client repository requests and displays
actor data, but actor creation, durable updates, and hot-state behavior are
handled by the server actor addon and extension.
## Related Guides
- [Actor Usage Guide](/server-modules/actor)
- [Client Bank Usage Guide](/client-addons/bank)
- [Client CAD Usage Guide](/client-addons/cad)
- [Client Garage Usage Guide](/client-addons/garage)
- [Client Locker Usage Guide](/client-addons/locker)
- [Client Organization Usage Guide](/client-addons/organization)
- [Client Phone Usage Guide](/client-addons/phone)
- [Client Store Usage Guide](/client-addons/store)

View File

@ -0,0 +1,84 @@
# Client Bank Usage Guide
The client bank addon opens the bank and ATM browser UI, forwards banking
requests to the server bank addon, and pushes account updates back into the
browser.
## Open Bank UI
Open full bank mode:
```sqf
call forge_client_bank_fnc_openUI;
```
Open ATM mode:
```sqf
[true] call forge_client_bank_fnc_openUI;
```
The open function creates `RscBank`, sets the bridge mode to `bank` or `atm`,
loads `ui/_site/index.html`, and routes browser events through
`forge_client_bank_fnc_handleUIEvents`.
## Bridge and Repository
`forge_client_bank_fnc_initRepository` tracks account load and cached account
state.
`forge_client_bank_fnc_initUIBridge` owns:
- active browser control tracking
- bank/ATM mode
- browser ready handling
- account hydrate and sync responses
- deposit, withdrawal, transfer, earnings deposit, credit repayment, and PIN
requests
- browser notice delivery
## Browser Events
| Event | Client behavior |
| --- | --- |
| `bank::ready` | Mark browser ready and request hydrate from the server. |
| `bank::refresh` | Request fresh bank hydrate data. |
| `bank::deposit::request` | Forward deposit amount to the server. |
| `bank::withdraw::request` | Forward withdrawal amount to the server. |
| `bank::transfer::request` | Forward target, source field, and amount. |
| `bank::depositEarnings::request` | Request earnings deposit. |
| `bank::repayCreditLine::request` | Request credit-line repayment. |
| `bank::pin::request` | Forward PIN validation request. |
| `bank::close` | Dispose bridge screen state and close the display. |
## Browser Response Events
The bridge sends:
| Event | Purpose |
| --- | --- |
| `bank::hydrate` | Full session/account payload. |
| `bank::sync` | Account patch or sync data. |
| `bank::notice` | UI-visible notice payload. |
## Request Flow
Example deposit flow:
1. Browser sends `bank::deposit::request` with an `amount`.
2. Client bridge calls the server bank request event.
3. Server bank addon validates the request and calls bank hot-state logic.
4. Server response is caught by the client post-init event handlers.
5. Client bridge sends `bank::sync` or `bank::notice` back to the browser.
## Authoritative State
Balances, PIN authorization, transfers, checkout charges, credit lines, and
persistence are server-owned. The client should only display account data and
request mutations through server events.
## Related Guides
- [Bank Usage Guide](/server-modules/bank)
- [Client Common Usage Guide](/client-addons/common)
- [Client Store Usage Guide](/client-addons/store)

View File

@ -0,0 +1,100 @@
# Client CAD Usage Guide
The client CAD addon provides the map and dispatch UI for groups, active
tasks, task assignment, dispatch orders, support requests, and task
acknowledge/decline workflows.
## Open CAD UI
```sqf
call forge_client_cad_fnc_openUI;
```
The CAD UI opens `RscMapUI` and loads separate browser controls for:
- top bar
- bottom bar
- side panel
- dispatcher board
The native Arma map remains part of the same display.
## Repository and Bridge
`forge_client_cad_fnc_initRepository` caches the hydrated CAD payload,
selected mode, dispatch view, session data, groups, tasks, requests, and
assignments.
`forge_client_cad_fnc_initUIBridge` owns:
- ready state for side panel, top bar, and dispatcher board
- operations vs dispatch mode
- board vs map dispatch view
- hydrate requests
- task assignment, acknowledge, and decline requests
- dispatch order create/close requests
- support request submit/close requests
- group status, role, and profile requests
- map focus actions
## Browser Events
| Event | Client behavior |
| --- | --- |
| `cad::topbar::ready` | Mark top bar ready and push top bar state. |
| `cad::ready` | Mark side panel ready and request hydrate. |
| `cad::dispatcher::ready` | Mark dispatcher board ready and push hydrate data. |
| `cad::mode::set` | Switch between operations and dispatch mode. |
| `cad::dispatchView::set` | Switch dispatch board/map view. |
| `cad::refresh` | Request fresh CAD hydrate data. |
| `cad::tasks::assign` | Assign a task to a group. |
| `cad::tasks::acknowledge` | Acknowledge assigned task. |
| `cad::tasks::decline` | Decline assigned task. |
| `cad::dispatchOrder::create` | Create dispatch order. |
| `cad::dispatchOrder::close` | Close dispatch order. |
| `cad::supportRequest::submit` | Submit support request. |
| `cad::supportRequest::close` | Close support request. |
| `cad::groups::status` | Update group status. |
| `cad::groups::role` | Update group role. |
| `cad::groups::profile` | Update status and role together. |
| `cad::groups::focus` | Center map on a group. |
| `cad::tasks::focus` | Center map on a task. |
| `cad::requests::focus` | Center map on a support request. |
| `map::zoomIn` | Zoom native map in. |
| `map::zoomOut` | Zoom native map out. |
| `map::search` | Placeholder status update. |
| `map::close` | Dispose bridge state and close the display. |
## Response Events
The bridge pushes:
| Event | Purpose |
| --- | --- |
| `cad::hydrate` | Full hydrated CAD payload to the side panel. |
| `cad::assignment::response` | Task assignment/acknowledge/decline result. |
| `cad::group::response` | Group status/role/profile result. |
| `cad::request::response` | Support request result. |
Dispatcher board controls also receive direct `ExecJS` status and hydrate
calls.
## Task Compatibility
CAD task visibility depends on server-side task catalog entries. Tasks created
through Eden Forge task modules or `forge_server_task_fnc_startTask` are the
normal CAD-compatible task sources because they register task catalog data.
Direct handler or task-function calls only work with CAD when the task catalog
entry already exists.
## Authorization Notes
Only dispatcher sessions can enter dispatch mode. If the hydrated session is
not a dispatcher, the bridge forces the UI back to operations mode.
## Related Guides
- [CAD Usage Guide](/server-modules/cad)
- [Task Usage Guide](/server-modules/task)
- [Client Common Usage Guide](/client-addons/common)

View File

@ -0,0 +1,114 @@
# Client Garage Usage Guide
The client garage addon provides player vehicle storage UI, vehicle
store/retrieve actions, selected nearby vehicle service requests, vehicle
context building, and the virtual garage view.
## Open Garage UI
```sqf
call forge_client_garage_fnc_openUI;
```
The garage UI opens `RscGarage`, loads `ui/_site/index.html`, and routes
browser events through `forge_client_garage_fnc_handleUIEvents`.
## Open Virtual Garage
```sqf
call forge_client_garage_fnc_openVG;
```
The virtual garage resolves the active interaction object near the player,
discovers nearby `garage*` markers placed in Eden, chooses the matching spawn
lane for the selected vehicle type, opens the BIS garage interface, and
restricts the available vehicle lists from the virtual garage repository. When
the BIS garage closes, only the vehicle selected in that virtual garage session
is finalized and spawned onto the resolved lane.
## Client Services
| Service | Purpose |
| --- | --- |
| `GarageRepository` | Player garage view state. |
| `VGRepository` | Virtual garage unlock view state. |
| `GarageHelperService` | Vehicle names, hit points, and payload helpers. |
| `GarageContextService` | Nearby/current vehicle context. |
| `GaragePayloadService` | Browser hydrate payload construction. |
| `GarageActionService` | Store/retrieve request handling and selected nearby vehicle refuel/repair request forwarding. |
| `GarageUIBridge` | Browser ready, hydrate, and sync delivery. |
## Browser Events
| Event | Client behavior |
| --- | --- |
| `garage::ready` | Mark browser ready and send `garage::hydrate`. |
| `garage::refresh` | Send current garage payload as `garage::sync`. |
| `garage::vehicle::retrieve::request` | Forward retrieve request through the action service. |
| `garage::vehicle::store::request` | Forward store request through the action service. |
| `garage::vehicle::refuel::request` | Forward selected nearby vehicle refuel request to the server economy service. |
| `garage::vehicle::repair::request` | Forward selected nearby vehicle repair request to the server economy service. |
| `garage::close` | Dispose bridge screen state and close the display. |
## Browser Response Events
| Event | Purpose |
| --- | --- |
| `garage::hydrate` | Initial vehicle and session payload. |
| `garage::sync` | Refreshed vehicle payload. |
| `garage::service::success` | Browser notice for accepted refuel/repair requests. |
| `garage::service::failure` | Browser notice for rejected refuel/repair requests. |
Server action responses are handled by the action service and notification
flow.
## Vehicle Service
The selected vehicle detail panel includes refuel and repair actions for nearby
world vehicles. Stored records must be retrieved first because server economy
services operate on live vehicle objects, not stored garage records.
Refuel requests use the server economy `RefuelService` event. Repair requests
use the server economy `RepairService` event. Both services are billed by the
server economy addon through organization funds.
## Mission Setup
Garage interactions are normally surfaced through the actor menu when nearby
objects have garage variables such as:
```sqf
_object setVariable ["isGarage", true, true];
_object setVariable ["garageType", "cars", true];
```
When using the server garage auto-init flow, editor-placed objects whose
variable names contain `garage` are marked as garage interaction points and
their `garageType` can be inferred from the name.
Virtual garage spawn lanes are resolved from empty markers placed in Eden. The
marker name should contain `garage` and one of the six supported category names:
`cars`, `armor`, `helis`, `planes`, `naval`, or `other`. Markers are matched to
the nearby interaction object by proximity, and names that include the garage
object's variable name are preferred when multiple garages exist.
Vehicle spawning is strict by category. If the active garage site does not have
a matching local marker for the vehicle category being retrieved or spawned from
the virtual garage, the request is blocked and the player is shown a message.
Nearby world vehicles are not used as virtual garage spawn candidates. They are
only checked to determine whether the resolved spawn position is blocked. If
any vehicle is within 5 meters of the spawn marker when the virtual garage is
opened, the session is blocked and the player is shown a warning.
## Authoritative State
The client gathers vehicle context and sends store/retrieve requests. Stored
vehicle state, validation, spawning, removal, and persistence are owned by the
server garage addon and extension.
## Related Guides
- [Garage Usage Guide](/server-modules/garage)
- [Client Actor Usage Guide](/client-addons/actor)
- [Client Notifications Usage Guide](/client-addons/notifications)

View File

@ -0,0 +1,87 @@
# Client Locker Usage Guide
The client locker addon manages personal locker display state, local locker
container behavior, and virtual arsenal unlock state.
## Repositories
`forge_client_locker_fnc_initRepository` creates `GVAR(LockerRepository)`.
`forge_client_locker_fnc_initVARepository` creates `GVAR(VARepository)`.
Initialize locker state:
```sqf
GVAR(LockerRepository) call ["init", []];
GVAR(VARepository) call ["init", []];
```
## Locker Container Flow
The repository searches mission namespace variables whose names contain
`locker` and refer to objects. For each server/mission locker object, it creates
a local `Box_NATO_Equip_F` at the same position and attaches container event
handlers.
On container open:
- the local container is cleared
- cached locker items are inserted into the container
- over-capacity warnings are emitted when the item count is above 25
On container close:
- cargo, nested container items, and weapon attachments are read back
- the new locker map is sent to the server with the override request
- the local repository cache is updated
## Virtual Arsenal Flow
The virtual arsenal repository creates a local `FORGE_Locker_Box` and requests
virtual arsenal unlocks from the server.
As sync data arrives, it applies unlocks through ACE Arsenal:
| Data key | Client behavior |
| --- | --- |
| `items` | Add virtual items. |
| `weapons` | Add virtual weapons. |
| `magazines` | Add virtual magazines. |
| `backpacks` | Add virtual backpacks. |
The actor menu opens the virtual locker with:
```sqf
[FORGE_Locker_Box, player, false] spawn ace_arsenal_fnc_openBox;
```
## Server Events
The client repository sends requests for:
- locker initialization
- locker save
- locker override after container close
- virtual arsenal initialization
- virtual arsenal save
The server locker addon and extension own the saved locker and virtual arsenal
state.
## Mission Setup
Mission locker objects must be placed into `missionNamespace` with a variable
name containing `locker`. The client creates local interactive containers from
those authoritative mission objects.
Example:
```sqf
missionNamespace setVariable ["forge_locker_alpha", _lockerObject, true];
```
## Related Guides
- [Locker Usage Guide](/server-modules/locker)
- [Owned Storage Usage Guide](/server-modules/owned-storage)
- [Client Actor Usage Guide](/client-addons/actor)

View File

@ -0,0 +1,74 @@
# Client Notifications Usage Guide
The client notifications addon owns the notification HUD, notification sound,
and local notification service used by Forge client and server modules.
## Runtime Behavior
The notification display is created during client initialization. The browser
HUD sends:
```text
notifications::ready
```
When that event is received, `NotificationService` initializes and sends a
startup notification.
## Create a Notification
Use the notification service when available:
```sqf
GVAR(NotificationService) call ["create", [
"success",
"Title",
"Notification text.",
4000
]];
```
Arguments:
| Argument | Purpose |
| --- | --- |
| `_type` | Notification type, such as `success`, `info`, `warning`, or `error`. |
| `_title` | Notification title. |
| `_content` | Notification body text. |
| `_duration` | Display duration in milliseconds. |
The service dispatches a browser `forge:notify` custom event.
## CBA Event Surface
Other addons can use the client notification event:
```sqf
["forge_client_notifications_recieveNotification", [
"warning",
"Garage",
"Vehicle spawn position is blocked.",
3000
]] call CBA_fnc_localEvent;
```
The event payload is:
```sqf
[_type, _title, _content, _duration]
```
## Usage Rules
- Use the shared notification service instead of opening separate transient
browser UIs.
- Keep server-driven player feedback short and actionable.
- Treat notification state as transient client UI state.
- Do not use notifications as the only record of durable domain changes.
## Related Guides
- [Client Usage Guide](/client-addons)
- [Client Garage Usage Guide](/client-addons/garage)
- [Client Bank Usage Guide](/client-addons/bank)
- [Client Store Usage Guide](/client-addons/store)

View File

@ -0,0 +1,106 @@
# Client Organization Usage Guide
The client organization addon provides the organization portal UI and browser
bridge for login, registration, membership, invites, credit lines, leave and
disband flows, assets, fleet, and treasury display.
## Open Organization UI
```sqf
call forge_client_org_fnc_openUI;
```
The UI opens `RscOrg`, loads `ui/_site/index.html`, and routes browser alerts
through `forge_client_org_fnc_handleUIEvents`.
## Repository and Bridge
`forge_client_org_fnc_initRepository` caches organization portal state.
`forge_client_org_fnc_initUIBridge` owns:
- active browser control tracking
- portal hydrate requests
- create/login response routing
- leave and disband requests
- credit-line assignment requests
- invite, accept invite, and decline invite requests
- targeted browser response events
## Browser Events
| Event | Client behavior |
| --- | --- |
| `org::ready` | Mark browser ready and request `org::sync`. |
| `org::login::request` | Request portal hydrate as `org::login::success`. |
| `org::create::request` | Validate org name and request creation on server. |
| `org::disband::request` | Request disband on server. |
| `org::leave::request` | Request leave on server. |
| `org::credit::request` | Request credit-line assignment. |
| `org::invite::request` | Request member invite. |
| `org::invite::accept` | Accept invite by org ID. |
| `org::invite::decline` | Decline invite by org ID. |
| `org::close` | Close the display. |
## Browser Response Events
| Event | Purpose |
| --- | --- |
| `org::sync` | Full portal sync payload. |
| `org::login::success` | Login hydrate payload. |
| `org::create::success` | Creation hydrate payload. |
| `org::create::failure` | Creation validation or server failure. |
| `org::disband::success` | Requester disband success. |
| `org::disband::failure` | Disband failure. |
| `org::portal::revoked` | Portal state revoked by someone else's disband action. |
| `org::leave::success` | Leave success. |
| `org::leave::failure` | Leave failure. |
| `org::credit::success` | Credit-line request success. |
| `org::credit::failure` | Credit-line request failure. |
| `org::member::creditUpdated` | Targeted member credit-line patch. |
| `org::invite::success` | Invite success. |
| `org::invite::failure` | Invite failure. |
| `org::invite::decision::success` | Invite accept/decline success. |
| `org::invite::decision::failure` | Invite accept/decline failure. |
## Request Examples
Create organization request payload:
```json
{
"orgName": "Example Logistics"
}
```
Credit-line request payload:
```json
{
"memberUid": "76561198000000000",
"memberName": "Player Name",
"amount": 2500
}
```
Invite request payload:
```json
{
"targetUid": "76561198000000000",
"targetName": "Player Name"
}
```
## Authoritative State
Organization funds, reputation, membership, invites, credit lines, assets,
fleet, and persistence are server-owned. The client portal only displays and
requests changes.
## Related Guides
- [Organization Usage Guide](/server-modules/organization)
- [Client Common Usage Guide](/client-addons/common)
- [Client Bank Usage Guide](/client-addons/bank)
- [Client Store Usage Guide](/client-addons/store)

172
docus/content/index.md Normal file
View File

@ -0,0 +1,172 @@
---
seo:
title: Forge Framework Documentation
description: Documentation for the Forge Arma 3 framework, covering architecture, persistence, extension APIs, gameplay modules, and client UIs.
---
::u-page-hero
#title
Forge Framework Documentation
#description
Forge is a persistent Arma 3 framework that combines SQF addons, a Rust
`arma-rs` extension, SurrealDB persistence, shared domain crates, and
browser-backed player interfaces.
Use these docs to understand the runtime architecture, extension API surface,
server gameplay modules, and client addon integration patterns.
#links
:::u-button
---
color: primary
size: xl
to: /getting-started
trailing-icon: i-lucide-arrow-right
---
Start here
:::
:::u-button
---
color: neutral
icon: simple-icons-github
size: xl
to: https://github.com/InnovativeDevSolutions/forge
variant: outline
---
View source
:::
::
::u-page-section
#title
What Forge Covers
#features
:::u-page-feature
---
icon: i-lucide-boxes
---
#title
Domain [Modules]{.text-primary}
#description
Actor, bank, CAD, garage, locker, organization, phone, store, task, and
owned-storage workflows share a consistent service and extension model.
:::
:::u-page-feature
---
icon: i-lucide-server
---
#title
Rust [Extension]{.text-primary}
#description
The server extension keeps command parsing thin, routes domain requests into
services, and persists durable state through SurrealDB.
:::
:::u-page-feature
---
icon: i-lucide-database-zap
---
#title
Durable [Persistence]{.text-primary}
#description
Repository traits stay storage-agnostic while concrete adapters in the
extension handle schema and database mapping.
:::
:::u-page-feature
---
icon: i-lucide-monitor-smartphone
---
#title
Browser [UIs]{.text-primary}
#description
Client addons host web-based interfaces inside Arma displays and synchronize
state through namespaced browser bridge events.
:::
:::u-page-feature
---
icon: i-lucide-arrow-left-right
---
#title
Transport [Layer]{.text-primary}
#description
Large payloads move through chunked request and response transport while
smaller commands still use direct `callExtension` paths.
:::
:::u-page-feature
---
icon: i-lucide-wrench
---
#title
Development [Workflow]{.text-primary}
#description
The docs cover module boundaries, local validation checks, and where new
domain logic belongs across Rust, SQF, and web UI layers.
:::
::
::u-page-section
#title
Documentation Areas
#features
:::u-page-feature
---
icon: i-lucide-rocket
to: /getting-started
---
#title
[Getting Started]{.text-primary}
#description
Framework overview, architecture, module reference, and development rules.
:::
:::u-page-feature
---
icon: i-lucide-server-cog
to: /server-extension
---
#title
Server [Extension]{.text-primary}
#description
Extension architecture, command surface, and SQF usage examples.
:::
:::u-page-feature
---
icon: i-lucide-layers-3
to: /server-modules
---
#title
Server [Modules]{.text-primary}
#description
Gameplay-domain usage guides for persistence, hot state, and command flows.
:::
:::u-page-feature
---
icon: i-lucide-monitor-smartphone
to: /client-addons
---
#title
Client [Addons]{.text-primary}
#description
Browser bridge, client UX entry points, and addon-specific event contracts.
:::
::

1
docus/dist/200.html vendored Normal file
View File

@ -0,0 +1 @@
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/forge/_nuxt/entry.B0IIbxeE.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/forge/_nuxt/B3fabVUf.js"><script type="module" src="/forge/_nuxt/B3fabVUf.js" crossorigin></script><script>"use strict";(()=>{const t=window,e=document.documentElement,c=["dark","light"],n=getStorageValue("localStorage","nuxt-color-mode")||"system";let i=n==="system"?u():n;const r=e.getAttribute("data-color-mode-forced");r&&(i=r),l(i),t["__NUXT_COLOR_MODE__"]={preference:n,value:i,getColorScheme:u,addColorScheme:l,removeColorScheme:d};function l(o){const s=""+o+"",a="";e.classList?e.classList.add(s):e.className+=" "+s,a&&e.setAttribute("data-"+a,o)}function d(o){const s=""+o+"",a="";e.classList?e.classList.remove(s):e.className=e.className.replace(new RegExp(s,"g"),""),a&&e.removeAttribute("data-"+a)}function f(o){return t.matchMedia("(prefers-color-scheme"+o+")")}function u(){if(t.matchMedia&&f("").media!=="not all"){for(const o of c)if(f(":"+o).matches)return o}return"light"}})();function getStorageValue(t,e){switch(t){case"localStorage":return window.localStorage.getItem(e);case"sessionStorage":return window.sessionStorage.getItem(e);case"cookie":return getCookie(e);default:return null}}function getCookie(t){const c=("; "+window.document.cookie).split("; "+t+"=");if(c.length===2)return c.pop()?.split(";").shift()}</script><meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" /></head><body><div id="__nuxt" class="isolate"></div><div id="teleports"></div><script>window.__NUXT_SITE_CONFIG__=(function(a){return {_priority:{env:-15,url:a,name:a},env:"production",name:"forge-docus",url:"https:\u002F\u002Finnovativedevsolutions.github.io"}}(-3))</script><script>window.__NUXT__={};window.__NUXT__.config={public:{assistant:{enabled:false,apiPath:"/__docus__/assistant"},mdc:{components:{prose:true,map:{accordion:"ProseAccordion","accordion-item":"ProseAccordionItem",badge:"ProseBadge",callout:"ProseCallout",card:"ProseCard","card-group":"ProseCardGroup",caution:"ProseCaution","code-collapse":"ProseCodeCollapse","code-group":"ProseCodeGroup","code-icon":"ProseCodeIcon","code-preview":"ProseCodePreview","code-tree":"ProseCodeTree",collapsible:"ProseCollapsible",field:"ProseField","field-group":"ProseFieldGroup",icon:"ProseIcon",kbd:"ProseKbd",note:"ProseNote",steps:"ProseSteps",tabs:"ProseTabs","tabs-item":"ProseTabsItem",tip:"ProseTip",warning:"ProseWarning"},customElements:[]},headings:{anchorLinks:{h1:false,h2:true,h3:true,h4:true,h5:false,h6:false}},highlight:{noApiRoute:true,theme:{light:"material-theme-lighter",default:"material-theme",dark:"material-theme-palenight"},shikiEngine:"javascript",langs:["bash","diff","json","js","ts","html","css","vue","shell","mdc","md","yaml"],highlighter:"shiki"}},content:{wsUrl:""},"nuxt-robots":{version:"6.0.7",isNuxtContentV2:false,debug:false,credits:true,groups:[{userAgent:["*"],allow:["/"],disallow:[],contentUsage:[],contentSignal:[],_indexable:true,_rules:[{pattern:"/",allow:true}],_normalized:true}],sitemap:["/sitemap.xml"],header:true,robotsEnabledValue:"index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1",robotsDisabledValue:"noindex, nofollow",cacheControl:"max-age=14400, must-revalidate",botDetection:true,pageMetaRobots:{}},"nuxt-og-image":{defaults:{emojis:"noto",extension:"png",width:1200,height:600,cacheMaxAgeSeconds:259200},hasServerRuntime:true}},app:{baseURL:"/forge/",buildId:"3a1bf7b7-6f51-4d2c-9c98-142f71e69a9a",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1776806626417,false]</script></body></html>

1
docus/dist/404.html vendored Normal file
View File

@ -0,0 +1 @@
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/forge/_nuxt/entry.B0IIbxeE.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/forge/_nuxt/B3fabVUf.js"><script type="module" src="/forge/_nuxt/B3fabVUf.js" crossorigin></script><script>"use strict";(()=>{const t=window,e=document.documentElement,c=["dark","light"],n=getStorageValue("localStorage","nuxt-color-mode")||"system";let i=n==="system"?u():n;const r=e.getAttribute("data-color-mode-forced");r&&(i=r),l(i),t["__NUXT_COLOR_MODE__"]={preference:n,value:i,getColorScheme:u,addColorScheme:l,removeColorScheme:d};function l(o){const s=""+o+"",a="";e.classList?e.classList.add(s):e.className+=" "+s,a&&e.setAttribute("data-"+a,o)}function d(o){const s=""+o+"",a="";e.classList?e.classList.remove(s):e.className=e.className.replace(new RegExp(s,"g"),""),a&&e.removeAttribute("data-"+a)}function f(o){return t.matchMedia("(prefers-color-scheme"+o+")")}function u(){if(t.matchMedia&&f("").media!=="not all"){for(const o of c)if(f(":"+o).matches)return o}return"light"}})();function getStorageValue(t,e){switch(t){case"localStorage":return window.localStorage.getItem(e);case"sessionStorage":return window.sessionStorage.getItem(e);case"cookie":return getCookie(e);default:return null}}function getCookie(t){const c=("; "+window.document.cookie).split("; "+t+"=");if(c.length===2)return c.pop()?.split(";").shift()}</script><meta name="robots" content="noindex, nofollow" /></head><body><div id="__nuxt" class="isolate"></div><div id="teleports"></div><script>window.__NUXT_SITE_CONFIG__=(function(a){return {_priority:{env:-15,url:a,name:a},env:"production",name:"forge-docus",url:"https:\u002F\u002Finnovativedevsolutions.github.io"}}(-3))</script><script>window.__NUXT__={};window.__NUXT__.config={public:{assistant:{enabled:false,apiPath:"/__docus__/assistant"},mdc:{components:{prose:true,map:{accordion:"ProseAccordion","accordion-item":"ProseAccordionItem",badge:"ProseBadge",callout:"ProseCallout",card:"ProseCard","card-group":"ProseCardGroup",caution:"ProseCaution","code-collapse":"ProseCodeCollapse","code-group":"ProseCodeGroup","code-icon":"ProseCodeIcon","code-preview":"ProseCodePreview","code-tree":"ProseCodeTree",collapsible:"ProseCollapsible",field:"ProseField","field-group":"ProseFieldGroup",icon:"ProseIcon",kbd:"ProseKbd",note:"ProseNote",steps:"ProseSteps",tabs:"ProseTabs","tabs-item":"ProseTabsItem",tip:"ProseTip",warning:"ProseWarning"},customElements:[]},headings:{anchorLinks:{h1:false,h2:true,h3:true,h4:true,h5:false,h6:false}},highlight:{noApiRoute:true,theme:{light:"material-theme-lighter",default:"material-theme",dark:"material-theme-palenight"},shikiEngine:"javascript",langs:["bash","diff","json","js","ts","html","css","vue","shell","mdc","md","yaml"],highlighter:"shiki"}},content:{wsUrl:""},"nuxt-robots":{version:"6.0.7",isNuxtContentV2:false,debug:false,credits:true,groups:[{userAgent:["*"],allow:["/"],disallow:[],contentUsage:[],contentSignal:[],_indexable:true,_rules:[{pattern:"/",allow:true}],_normalized:true}],sitemap:["/sitemap.xml"],header:true,robotsEnabledValue:"index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1",robotsDisabledValue:"noindex, nofollow",cacheControl:"max-age=14400, must-revalidate",botDetection:true,pageMetaRobots:{}},"nuxt-og-image":{defaults:{emojis:"noto",extension:"png",width:1200,height:600,cacheMaxAgeSeconds:259200},hasServerRuntime:true}},app:{baseURL:"/forge/",buildId:"3a1bf7b7-6f51-4d2c-9c98-142f71e69a9a",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1776806626418,false]</script></body></html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
H4sIAAAAAAAACr1YbVPbSBL+K136wl6VBLuXDZfjaj8YbBYHxxC/BBJEUe1RW5rzaEY7M7Ixqfz3q5ZkG5OXw9zefkgFS5qZnqe7n366b4KTQac16sCoddzrQPcU+hcj6Fx3h6Mh3AmjPWl/J/XUwE8ygVHnegSXg+671uAjnHc+hhAHljBZxgEcX1z0Oq0+P3LelsKXlj6QddLoOIAPrcHJWWvAb+ffenh3l6HL7u7ioD5k3O++H3f+9i+IIlhvF4RBtz/sDEbQ7Y8untj3odUbd4bw057ISMxcmd8p1InU6V4IU1SOQtjzn5YX5xN/fPXLx8N/pxfpUr2yeZRm5dvxWzfpnZx10+PLh+51ezrbC2Fv/mr/9f7PUfRpaHtvzLte2psdXvbzs8NPr39dLNu9s2lL9VT7df/98vpcpq/Kf3R52dvzC3E6uHr96eTyn73zq4t0cP5+ev9pLlU0zs9Gv/b6H/rY7xz2H7p79Q1z8hiEQXtwcbnxxFMvNNf5CpLneLBZ+10neukVbXtkYiq38tf8MyEnrCz8V56je0/6a4fylR4t1ziXKTarKwvandPWuDcCb0viLwr02fYWjszTj/c+f9mrI4zyPzGAVvCsY6h5cCB1Qvf7ecJu5X+f48AvC4qDozjIpZY52lkchHEwR1Xy45ubOCijAlOKMrImDsLPX8KbOPCUFwo98YM4mEdOGX/UoH4UB3HwJYyDU2NTglOLOS2MnUHbiDIn7Rvcbr+70ZZzmu1u4qCoj19tLB0gFJx9jm8NLZsjvILp+jyfoQdh8onU5GD4/hQwSYx2ISAMSufjWMcBbyxMQqu90eYYWcfmxQGsoyGEYWktoWofb84UFILL0FICiclRahAWPfEBOoljPbFm4chGExQzSqBQuCQLUnuyUxTk9hsQ1hcbOwKfkSNIjHDgDZQ6Ies86oRfgC21lzkBWpFJT1UUhBsroXXZBVdWu4dxrB3ZOVlIMSc+HHKTlKqxD4SSDFuFSWVUaivPQIHek9WVed93kpJ65h67p4wmpffsMv5KGGVs9bqwHFfLKq6cfKgj5F5Vv72pfh2k5L3UaeQ8Wk9J/c6iVPxQiiYOZKRKIROK0FqziKxMM1+H2pDXQUaWGki/Z4ym0ltUPzIm875wRwcHqfRZOdkXJj/oam3m6OWc2jQfGlUyTu5gynFYLV2b6GReKKpMdlG9Q5NQVqL21Tem9Epqqi3/IGkBzpRWsOm3jfFVxjkSdQrsknRXHPR1gpwYrk0/SrQpIUfQEzdWhzev6s+/9sDE3POy59vVrhOkTjhX4MozCp3rY16RDW9176N1wNzywnd1zP4wFr9FGHHQEt7YECaoZyGctNohpGgxpRCUETOyIRibopYPVdiHUGRGc0Z7w0nl0c1WiWwWmpKIX2BKwOQyVWbh6uQHBPZ2w0KcclJQlWGbvMxNQmqdT8+DuE7eXTBmUnsBwp1NydsZ41FG0LDM5rYzosIx8+aMQoHWcTXymdQhWFN6ciu+tPRHSc475h/TEJYUK4ZqiNZBUlqcKALn0TNDWlOm2YaRdwQ2QY8TdBQ9YLFTCDdW7I7w5aZivADjARXGSW/sEpgTvWMcltCEY4SpNs5LAYtMKuJYFJY8ASZYeLKMLdeOONYbD2WoE8ZTZJRjhfUKFMixKKROd8Q0N5otjFyO1leJtAuyx3WhfAGy4+5LmOHkUelzkBnnYUGTiO+fPKrPILWTCdXKIpGOa6ir0HJLLTJrNFeQWG+HpcacXIGCEmgEAEysTFICmpP2bkdk61qnaOo3Be+5uI4salcY+xJW6LFYeQG2PeTaU+BSGUwc5Ga+gUZkpWYl1KR9BaUlVxjNwmdtbBXIjGuOSpFdMQnHvVQKStZH0pJYXeuxfhOo1GNCYxXHWnxX2BeWtMh2ogeakzIFK9wXwH3VFJUXcnClFwUX/EbkwcSUOkErmUyVEahgjkomtcSrm8qaZRcsm0DTIo51Q8vKpFLAhJTRqQMU1jhXSeaQZXSzjCYw7kKlaWtw/3fxstUhQMsS/p/ki2UB4H8sQZ9r9o6O/r0+B4arc3b296ajYn/PJS3CJw1BEwKWpmTrNqWi+EcBallTvUiQRMKkW8A1jx81zs93+LCWDn+tZlmvfYLaSrCsGqgKNG4bS8eyj+6Rtf2uqNUJEr36Fmb5Stv+BYi9XEf/3rSPUcMONRxpKRNyMDV2uyHOjK91WtNlNqBWgvlPEBWPQKwb2Kiu4rtg2JT/3TFsNUftDOHxlhAIV733+BpIe7uEwkjtGz6u7hO5goScSlGLBlZ13qLwK6a9rXAQcXD0eT3uqk7jvpY4rttU8ATq6O8hT7s2f6/69pvbL19Wc6B6IlQNo/Z4gMX/H6wGROvN/8s8J3wyVTt6yuccKjzEqPd5Oq4J6/LF1LidllvRtTXqcOGPRxvjLsNV3aqafPEfD68PtR2/+UUbOr/MBvnicNjKxtc66x4+3E8uMCqjN5cPXTz7WTTTzB1WBGEwvmzz+HJ7mDvsjKAaLMNv1YAQrs46gw7IBH6Drwe8mxnq7X8A6Goc29EWAAA=

1
docus/dist/_nuxt/-nV3O3Hd.js vendored Normal file
View File

@ -0,0 +1 @@
const o={or:"या",error:{title:"पृष्ठ नहीं मिला",description:"हमें खुशी है, लेकिन आप जो पृष्ठ खोज रहे हैं वह मौजूद नहीं है।"}},a={copy:{page:"पृष्ठ कॉपी करें",link:"Markdown पृष्ठ कॉपी करें",view:"Markdown के रूप में देखें",gpt:"ChatGPT में खोलें",claude:"Claude में खोलें"},links:"समुदाय",toc:"इस पृष्ठ पर",menu:"मेनू",report:"समस्या की रिपोर्ट करें",edit:"इस पृष्ठ को संपादित करें"},e={copyLogo:"लोगो कॉपी करें",copyWordmark:"वर्डमार्क कॉपी करें",downloadLogo:"लोगो डाउनलोड करें",downloadWordmark:"वर्डमार्क डाउनलोड करें",brandAssets:"ब्रांड एसेट्स",logoCopied:"लोगो कॉपी हो गया",wordmarkCopied:"वर्डमार्क कॉपी हो गया",logoDownloaded:"लोगो डाउनलोड हो गया",wordmarkDownloaded:"वर्डमार्क डाउनलोड हो गया",copyLogoFailed:"लोगो कॉपी नहीं हो सका",copyWordmarkFailed:"वर्डमार्क कॉपी नहीं हो सका"},n={title:"AI से पूछें",placeholder:"सवाल पूछें...",tooltip:"AI से सवाल पूछें",tryAsking:"सवाल पूछने की कोशिश करें",askAnything:"कुछ भी पूछें...",clearChat:"चैट हटाएँ",close:"बंद करें",expand:"विस्तार करें",collapse:"छोटा करें",thinking:"सोच रहे हैं...",askMeAnything:"कुछ भी पूछें",askMeAnythingDescription:"डॉक्यूमेंट नेविगेट करने, अवधारणाओं को समझने और जवाब खोजने में मदद पाएँ।",faq:"अक्सर पूछे जाने वाले सवाल",chatCleared:"रीफ़्रेश करने पर चैट साफ़ हो जाती है",lineBreak:"लाइन ब्रेक",explainWithAi:"AI से समझाएँ",toolListPages:"लिस्ट किए गए डॉक्यूमेंट पेज",toolReadPage:"पढ़ें",loading:{searching:"डॉक्यूमेंट खोजना",reading:"दस्तावेज़ों के माध्यम से पढ़ना",analyzing:"कंटेंट का विश्लेषण करना",finding:"सबसे अच्छा जवाब ढूँढना",finished:"इस्तेमाल किए गए स्रोत"}},i={common:o,docs:a,logo:e,assistant:n};export{n as assistant,o as common,i as default,a as docs,e as logo};

1
docus/dist/_nuxt/2h0Q0JkN.js vendored Normal file
View File

@ -0,0 +1 @@
import{a0 as g,a1 as f,aQ as b,e as r,ac as t,s as e,af as v,B as y,ab as x,q as d,c as u,am as p,o as h}from"./B3fabVUf.js";const k={slots:{base:["relative text-xl text-highlighted font-bold mt-8 mb-3 scroll-mt-[calc(32px+45px+var(--ui-header-height))] lg:scroll-mt-[calc(32px+var(--ui-header-height))] [&>a]:focus-visible:outline-primary [&>a>code]:border-dashed hover:[&>a>code]:border-primary hover:[&>a>code]:text-primary [&>a>code]:text-lg/6 [&>a>code]:font-bold","[&>a>code]:transition-colors"],leading:["absolute -ms-8 top-0.5 opacity-0 group-hover:opacity-100 group-focus:opacity-100 p-1 bg-elevated hover:text-primary rounded-md hidden lg:flex text-muted","transition"],leadingIcon:"size-4 shrink-0",link:"group lg:ps-2 lg:-ms-2"}},C=["id"],q=["href"],_={__name:"ProseH3",props:{id:{type:String,required:!1},class:{type:null,required:!1},ui:{type:Object,required:!1}},setup(a){const l=a,i=g(),s=f("prose.h3",l),{headings:n}=b().public?.mdc||{},o=u(()=>p({extend:p(k),...i.ui?.prose?.h3||{}})()),m=u(()=>l.id&&typeof n?.anchorLinks=="object"&&n.anchorLinks.h3);return(c,B)=>(h(),r("h3",{id:a.id,class:t(o.value.base({class:[e(s)?.base,l.class]}))},[a.id&&m.value?(h(),r("a",{key:0,href:`#${a.id}`,class:t(o.value.link({class:e(s)?.link}))},[v("span",{class:t(o.value.leading({class:e(s)?.leading}))},[y(x,{name:e(i).ui.icons.hash,class:t(o.value.leadingIcon({class:e(s)?.leadingIcon}))},null,8,["name","class"])],2),d(c.$slots,"default")],10,q)):d(c.$slots,"default",{key:1})],10,C))}};export{_ as default};

1
docus/dist/_nuxt/4YIHfatS.js vendored Normal file
View File

@ -0,0 +1 @@
import{a0 as n,a1 as i,e as p,q as c,ac as u,s as d,c as m,am as t,o as f}from"./B3fabVUf.js";const _={base:"py-3 px-4 text-sm align-top border-e border-b first:border-s border-muted [&_code]:text-xs/5 [&_p]:my-0 [&_p]:leading-6 [&_ul]:my-0 [&_ol]:my-0 [&_ul]:ps-4.5 [&_ol]:ps-4.5 [&_li]:leading-6 [&_li]:my-0.5",variants:{align:{left:"text-left",center:"text-center",right:"text-right"}},defaultVariants:{align:"left"}},b={__name:"ProseTd",props:{align:{type:String,required:!1},class:{type:null,required:!1},ui:{type:Object,required:!1}},setup(s){const e=s,a=n(),r=i("prose.td",e),l=m(()=>t({extend:t(_),...a.ui?.prose?.td||{}}));return(o,g)=>(f(),p("td",{class:u(l.value({align:e.align,class:[d(r)?.base,e.class]}))},[c(o.$slots,"default")],2))}};export{b as default};

1
docus/dist/_nuxt/AFG10c_a.js vendored Normal file

File diff suppressed because one or more lines are too long

9
docus/dist/_nuxt/B2gqyXxK.js vendored Normal file
View File

@ -0,0 +1,9 @@
import{bK as q,bL as M,bM as R,G as U,H as B,bN as E,bO as _,d as D,o as g,a as x,p as m,q as W,m as P,s as a,W as I,bP as N,bQ as O,c as C,a0 as j,a1 as V,a4 as z,ar as G,e as A,B as f,bR as H,F as X,v as F,bS as $,bT as Q,bU as S,bV as Z,ac as k,f as T,bW as K,aQ as J,am as L,bX as Y}from"./B3fabVUf.js";var ee=0;function te(s){const i=new WeakMap,r=q();function c(n){if(s.mode!=="popLayout")return;const d=n.offsetParent,y=d instanceof HTMLElement&&d.offsetWidth||0,l={height:n.offsetHeight||0,width:n.offsetWidth||0,top:n.offsetTop,left:n.offsetLeft,right:0};l.right=y-l.width-l.left;const p=s.anchorX==="left"?`left: ${l.left}px`:`right: ${l.right}px`,h=`pop-${ee++}`;n.dataset.motionPopId=h;const e=document.createElement("style");r.value.nonce&&(e.nonce=r.value.nonce),i.set(n,e),document.head.appendChild(e),e.sheet&&e.sheet.insertRule(`
[data-motion-pop-id="${h}"] {
position: absolute !important;
width: ${l.width}px !important;
height: ${l.height}px !important;
top: ${l.top}px !important;
${p} !important;
}
`)}function v(n){const d=i.get(n);d&&(i.delete(n),M.render(()=>{document.head.removeChild(d)}))}return{addPopStyle:c,removePopStyle:v}}var oe=0;function ae(s){const i=String(oe++),r=new Map,{addPopStyle:c,removePopStyle:v}=te(s);function n(e){const t=[],o=E.get(e);o&&e.getAttribute(_.motionAttribute)===i&&t.push(o);const u=Array.from(e.querySelectorAll(`[${_.motionAttribute}="${i}"]`));for(const w of u){const b=E.get(w);b&&t.push(b)}return t}function d(e,t){const o=r.get(e);o&&(o.remaining.delete(t),o.remaining.size===0&&l(o))}const y={initial:s.initial,custom:s.custom,presenceId:i,onMotionExitComplete:d};R(y),U(()=>{y.initial=void 0});function l(e){v(e.el),e.states.forEach(t=>{t.getSnapshot(t.options,!1)}),e.done(),r.delete(e.el),e.el?.isConnected?e.states[0]?.didUpdate():e.states.forEach(t=>{t.unmount()}),s.onExitComplete?.()}function p(e,t){n(e).forEach(o=>{o.setActive("exit",!1),o.getSnapshot(o.options,!0)}),t()}function h(e,t){y.custom=s.custom;const o=e,u=n(o);if(u.length===0){t(),s.onExitComplete?.();return}const w={remaining:new Set(u),states:u,done:t,el:o};r.set(o,w),c(o),u.forEach(b=>{b.presenceContainer=o,b.setActive("exit",!0),b.getSnapshot(b.options,!1)}),u[0]?.didUpdate()}return B(()=>{r.forEach(e=>{e.states.forEach(t=>{t.unmount()})}),r.clear()}),{enter:p,exit:h}}var ne=D({name:"AnimatePresence",inheritAttrs:!0,__name:"AnimatePresence",props:{mode:{default:"sync"},initial:{type:Boolean,default:!0},as:{},custom:{},onExitComplete:{},anchorX:{default:"left"}},setup(s){const i=s,{enter:r,exit:c}=ae(i),v=C(()=>i.mode!=="wait"?{tag:i.as}:{mode:i.mode==="wait"?"out-in":void 0});return(n,d)=>(g(),x(I(n.mode==="wait"?N:O),P(v.value,{appear:"",css:!1,onLeave:a(c),onEnter:a(r)}),{default:m(()=>[W(n.$slots,"default")]),_:3},16,["onLeave","onEnter"]))}}),se=ne;const ie={slots:{base:"rounded-md w-full",overlay:"fixed inset-0 bg-default/75 backdrop-blur-sm will-change-opacity",content:"fixed inset-0 flex items-center justify-center cursor-zoom-out focus:outline-none",zoomedImage:"w-full h-auto max-w-[95vw] max-h-[95vh] object-contain rounded-md"},variants:{zoom:{true:"will-change-transform"},open:{true:""}},compoundVariants:[{zoom:!0,open:!1,class:"cursor-zoom-in"}]},le=["onClick"],ue=Object.assign({inheritAttrs:!1},{__name:"ProseImg",props:{src:{type:String,required:!0},alt:{type:String,required:!0},width:{type:[String,Number],required:!1},height:{type:[String,Number],required:!1},class:{type:null,required:!1},zoom:{type:Boolean,required:!1,default:!0},ui:{type:Object,required:!1}},setup(s){const i=s,r=j(),c=V("prose.img",i),[v,n]=z(),[d,y]=z(),l=F(!1),p=C(()=>L({extend:L(ie),...r.ui?.prose?.img||{}})({zoom:i.zoom,open:l.value})),h=C(()=>K(i.src,J().app.baseURL)),e=C(()=>`${h.value}::${Y()}`);return i.zoom&&G(window,"scroll",()=>{l.value=!1}),(t,o)=>(g(),A(X,null,[f(a(v),null,{default:m(()=>[(g(),x(I(a($)),P({src:h.value,alt:s.alt,width:s.width,height:s.height},t.$attrs,{class:p.value.base({class:[a(c)?.base,i.class]})}),null,16,["src","alt","width","height","class"]))]),_:1}),f(a(d),null,{default:m(()=>[(g(),x(I(a($)),P({src:h.value,alt:s.alt},t.$attrs,{class:p.value.zoomedImage({class:[a(c)?.zoomedImage]})}),null,16,["src","alt","class"]))]),_:1}),s.zoom?(g(),x(a(H),{key:0,open:l.value,"onUpdate:open":o[0]||(o[0]=u=>l.value=u),modal:!1},{default:m(({close:u})=>[f(a(Q),{"as-child":""},{default:m(()=>[f(a(S),{"layout-id":e.value,"as-child":"",transition:{type:"spring",bounce:.15,duration:.5,ease:"easeInOut"}},{default:m(()=>[f(a(n))]),_:1},8,["layout-id"])]),_:1}),f(a(Z),null,{default:m(()=>[f(a(se),null,{default:m(()=>[l.value?(g(),x(a(S),{key:0,initial:{opacity:0},animate:{opacity:1},exit:{opacity:0},class:k(p.value.overlay({class:[a(c)?.overlay]}))},null,8,["class"])):T("",!0),l.value?(g(),A("div",{key:1,class:k(p.value.content({class:[a(c)?.content]})),onClick:u},[f(a(S),{"as-child":"","layout-id":e.value,transition:{type:"spring",bounce:.15,duration:.5,ease:"easeInOut"}},{default:m(()=>[f(a(y))]),_:1},8,["layout-id"])],10,le)):T("",!0)]),_:2},1024)]),_:2},1024)]),_:1},8,["open"])):(g(),x(a(n),{key:1}))],64))}});export{ue as default};

41
docus/dist/_nuxt/B3fabVUf.js vendored Normal file

File diff suppressed because one or more lines are too long

1
docus/dist/_nuxt/B4xqMpdT.js vendored Normal file
View File

@ -0,0 +1 @@
import{a0 as r,a1 as c,e as p,q as u,ac as i,s as m,c as f,am as e,o as d}from"./B3fabVUf.js";const _={base:"my-1.5 ps-1.5 leading-7 [&>ul]:my-0"},b={__name:"ProseLi",props:{class:{type:null,required:!1},ui:{type:Object,required:!1}},setup(a){const s=a,o=r(),t=c("prose.li",s),l=f(()=>e({extend:e(_),...o.ui?.prose?.li||{}}));return(n,y)=>(d(),p("li",{class:i(l.value({class:[m(t)?.base,s.class]}))},[u(n.$slots,"default")],2))}};export{b as default};

1
docus/dist/_nuxt/B6bUjJBd.js vendored Normal file
View File

@ -0,0 +1 @@
import{d as m,bH as I,I as y,h as g,G as F,H as h,o as K,a as S,p as l,B as C,s as n,P as q,q as w,c as u,y as B,bI as T,n as R,bJ as x}from"./B3fabVUf.js";var _=m({__name:"RovingFocusItem",props:{tabStopId:{type:String,required:!1},focusable:{type:Boolean,required:!1,default:!0},active:{type:Boolean,required:!1},allowShiftKey:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:"span"}},setup(d){const r=d,a=I(),f=y(),i=u(()=>r.tabStopId||f),c=u(()=>a.currentTabStopId.value===i.value),{getItems:p,CollectionItem:v}=g();F(()=>{r.focusable&&a.onFocusableItemAdd()}),h(()=>{r.focusable&&a.onFocusableItemRemove()});function b(e){if(e.key==="Tab"&&e.shiftKey){a.onItemShiftTab();return}if(e.target!==e.currentTarget)return;const t=B(e,a.orientation.value,a.dir.value);if(t!==void 0){if(e.metaKey||e.ctrlKey||e.altKey||!r.allowShiftKey&&e.shiftKey)return;e.preventDefault();let o=[...p().map(s=>s.ref).filter(s=>s.dataset.disabled!=="")];if(t==="last")o.reverse();else if(t==="prev"||t==="next"){t==="prev"&&o.reverse();const s=o.indexOf(e.currentTarget);o=a.loop.value?T(o,s+1):o.slice(s+1)}R(()=>x(o))}}return(e,t)=>(K(),S(n(v),null,{default:l(()=>[C(n(q),{tabindex:c.value?0:-1,"data-orientation":n(a).orientation.value,"data-active":e.active?"":void 0,"data-disabled":e.focusable?void 0:"",as:e.as,"as-child":e.asChild,onMousedown:t[0]||(t[0]=o=>{e.focusable?n(a).onItemFocus(i.value):o.preventDefault()}),onFocus:t[1]||(t[1]=o=>n(a).onItemFocus(i.value)),onKeydown:b},{default:l(()=>[w(e.$slots,"default")]),_:3},8,["tabindex","data-orientation","data-active","data-disabled","as","as-child"])]),_:3}))}}),A=_;export{A as R};

1
docus/dist/_nuxt/B7V4_Oof.js vendored Normal file
View File

@ -0,0 +1 @@
const a={or:"veya",error:{title:"Sayfa bulunamadı",description:"Üzgünüz, bu sayfa bulunamadı."}},o={copy:{page:"Sayfayı kopyala",link:"Markdown bağlantısını kopyala",view:"Markdown olarak görüntüle",gpt:"ChatGPT'de aç",claude:"Claude'da aç"},links:"Topluluk",toc:"Bu sayfada",menu:"Menü",report:"Sorun bildir",edit:"Bu sayfayı düzenle"},n={copyLogo:"Logoyu kopyala",copyWordmark:"Wordmark'ı kopyala",downloadLogo:"Logoyu indir",downloadWordmark:"Wordmark'ı indir",brandAssets:"Marka materyalleri",logoCopied:"Logo kopyalandı",wordmarkCopied:"Wordmark kopyalandı",logoDownloaded:"Logo indirildi",wordmarkDownloaded:"Wordmark indirildi",copyLogoFailed:"Logo kopyalanamadı",copyWordmarkFailed:"Wordmark kopyalanamadı"},e={title:"Yapay zekaya sor",placeholder:"Bir soru sorun...",tooltip:"Yapay zekaya bir soru sorun",tryAsking:"Bir soru sormayı deneyin",askAnything:"Her şeyi sor...",clearChat:"Sohbeti temizle",close:"Kapat",expand:"Genişlet",collapse:"Daralt",thinking:"Düşünüyorum...",askMeAnything:"Her şeyi sor",askMeAnythingDescription:"Belgelerde gezinme, kavramları anlama ve yanıt bulma konusunda yardım alın.",faq:"SSS",chatCleared:"Sohbet yenilendiğinde temizlenir",lineBreak:"Satır sonu",explainWithAi:"Yapay zeka ile açıklayın",toolListPages:"Listelenen dokümantasyon sayfaları",toolReadPage:"Oku",loading:{searching:"Dokümantasyonda arama",reading:"Dokümanlar okunuyor",analyzing:"İçerik analiz ediliyor",finding:"En iyi yanıt bulunuyor",finished:"Kullanılan kaynaklar"}},r={common:a,docs:o,logo:n,assistant:e};export{e as assistant,a as common,r as default,o as docs,n as logo};

1
docus/dist/_nuxt/B91fwRQf.js vendored Normal file
View File

@ -0,0 +1 @@
import{$ as h,aR as b,a0 as v,a1 as x,e as c,ac as r,s as e,B as u,af as d,ag as q,f as k,ak as C,q as S,m as $,c as B,am as p,o as m}from"./B3fabVUf.js";import w from"./C-sCiTCq.js";const P={slots:{root:"relative my-5 group",header:"flex items-center gap-1.5 border border-muted bg-default border-b-0 relative rounded-t-md px-4 py-3",filename:"text-default text-sm/6",icon:"size-4 shrink-0",copy:"absolute top-[11px] right-[11px] lg:opacity-0 lg:group-hover:opacity-100 transition",base:"group font-mono text-sm/6 border border-muted bg-muted rounded-md px-4 py-3 whitespace-pre-wrap break-words overflow-x-auto focus:outline-none **:[.line]:block **:[.line.highlight]:-mx-4 **:[.line.highlight]:px-4 **:[.line.highlight]:bg-accented/50!"},variants:{filename:{true:{root:"[&>pre]:rounded-t-none [&>pre]:my-0 my-5"}}}},A={__name:"ProsePre",props:{icon:{type:null,required:!1},code:{type:String,required:!1},language:{type:String,required:!1},filename:{type:String,required:!1},highlights:{type:Array,required:!1},hideHeader:{type:Boolean,required:!1},meta:{type:String,required:!1},class:{type:null,required:!1},ui:{type:Object,required:!1}},setup(a){const t=a,{t:f}=h(),{copy:g,copied:y}=b(),l=v(),s=x("prose.pre",t),o=B(()=>p({extend:p(P),...l.ui?.prose?.pre||{}})());return(i,n)=>(m(),c("div",{class:r(o.value.root({class:[e(s)?.root],filename:!!a.filename}))},[a.filename&&!a.hideHeader?(m(),c("div",{key:0,class:r(o.value.header({class:e(s)?.header}))},[u(w,{icon:a.icon,filename:a.filename,class:r(o.value.icon({class:e(s)?.icon}))},null,8,["icon","filename","class"]),d("span",{class:r(o.value.filename({class:e(s)?.filename}))},q(a.filename),3)],2)):k("",!0),u(C,{icon:e(y)?e(l).ui.icons.copyCheck:e(l).ui.icons.copy,color:"neutral",variant:"outline",size:"sm","aria-label":e(f)("prose.pre.copy"),class:r(o.value.copy({class:e(s)?.copy})),tabindex:"-1",onClick:n[0]||(n[0]=z=>e(g)(t.code||""))},null,8,["icon","aria-label","class"]),d("pre",$({class:o.value.base({class:[e(s)?.base,t.class]})},i.$attrs),[S(i.$slots,"default")],16)],2))}};export{A as default};

1
docus/dist/_nuxt/BKwruWNQ.js vendored Normal file
View File

@ -0,0 +1 @@
import{a0 as p,a1 as u,e as d,af as i,q as f,ac as o,s as t,c as m,am as r,o as b}from"./B3fabVUf.js";const v={slots:{root:"relative my-5 overflow-x-auto",base:"w-full border-separate border-spacing-0 rounded-md"}},g={__name:"ProseTable",props:{class:{type:null,required:!1},ui:{type:Object,required:!1}},setup(l){const e=l,c=p(),s=u("prose.table",e),a=m(()=>r({extend:r(v),...c.ui?.prose?.table||{}})());return(n,_)=>(b(),d("div",{class:o(a.value.root({class:[t(s)?.root,e.class]}))},[i("table",{class:o(a.value.base({class:t(s)?.base}))},[f(n.$slots,"default")],2)],2))}};export{g as default};

1
docus/dist/_nuxt/BLnzyn6S.js vendored Normal file
View File

@ -0,0 +1 @@
import{r as a}from"./Cf5i2Hk_.js";import t from"./Jc8Ntx_l.js";import{a0 as s,a as e,p as n,s as p,o as c}from"./B3fabVUf.js";import"./BN_7HF1G.js";const d={__name:"ProseCaution",setup(i){const o=s();return(r,f)=>(c(),e(t,{color:"error",icon:p(o).ui.icons.caution},{default:n(()=>[a(r.$slots,"default",{mdcUnwrap:"p"})]),_:3},8,["icon"]))}};export{d as default};

1
docus/dist/_nuxt/BN_7HF1G.js vendored Normal file
View File

@ -0,0 +1 @@
const y=["p","h1","h2","h3","h4","h5","h6","li"];function f(r,i){return r.type===i||typeof r.type=="object"&&r.type.tag===i||r.tag===i}function u(r){return f(r,"text")||f(r,Symbol.for("v-txt"))}function l(r){return Array.isArray(r.children)||typeof r.children=="string"?r.children:typeof r.children?.default=="function"?r.children.default():[]}function n(r){if(!r)return"";if(Array.isArray(r))return r.map(n).join("");if(u(r))return r.value||r.children||"";const i=l(r);return Array.isArray(i)?i.map(n).filter(Boolean).join(""):""}function h(r,i=[]){if(Array.isArray(r))return r.flatMap(e=>h(e,i));let t=r;return i.some(e=>e==="*"||f(r,e))&&(t=l(r)||r,!Array.isArray(t)&&y.some(e=>f(r,e))&&(t=[t])),t}function p(r,i=[]){return r=Array.isArray(r)?r:[r],i.length?r.flatMap(t=>p(h(t,[i[0]]),i.slice(1))).filter(t=>!(u(t)&&n(t).trim()==="")):r}function a(r,i=[]){return typeof i=="string"&&(i=i.split(/[,\s]/).map(t=>t.trim()).filter(Boolean)),i.length?p(r,i).reduce((t,e)=>(u(e)?typeof t[t.length-1]=="string"?t[t.length-1]+=e.children:t.push(e.children):t.push(e),t),[]):r}export{a as f,n};

1
docus/dist/_nuxt/BOynLmEf.js vendored Normal file
View File

@ -0,0 +1 @@
const a={or:"ou",error:{title:"Página não encontrada",description:"Desculpe, mas esta página não pôde ser encontrada."}},o={copy:{page:"Copiar página",link:"Copiar página em Markdown",view:"Visualizar como Markdown",gpt:"Abrir no ChatGPT",claude:"Abrir no Claude"},links:"Comunidade",toc:"Nesta página",menu:"Menu",report:"Reportar um erro",edit:"Editar esta página"},e={copyLogo:"Copiar logo",copyWordmark:"Copiar wordmark",downloadLogo:"Baixar logo",downloadWordmark:"Baixar wordmark",brandAssets:"Recursos da marca",logoCopied:"Logo copiado",wordmarkCopied:"Wordmark copiado",logoDownloaded:"Logo baixado",wordmarkDownloaded:"Wordmark baixado",copyLogoFailed:"Falha ao copiar o logo",copyWordmarkFailed:"Falha ao copiar o wordmark"},r={title:"Pergunte à IA",placeholder:"Faça uma pergunta...",tooltip:"Faça uma pergunta à IA",tryAsking:"Tente fazer uma pergunta",askAnything:"Pergunte qualquer coisa...",clearChat:"Limpar chat",close:"Fechar",expand:"Expandir",collapse:"Recolher",thinking:"Pensando...",askMeAnything:"Perguntar qualquer coisa",askMeAnythingDescription:"Obtenha ajuda para navegar pela documentação, entender conceitos e encontrar respostas.",faq:"Perguntas frequentes",chatCleared:"O chat é apagado ao atualizar",lineBreak:"Quebra de linha",explainWithAi:"Explicar com IA",toolListPages:"Páginas de documentação listadas",toolReadPage:"Ler",loading:{searching:"Pesquisar a documentação",reading:"Ler os documentos",analyzing:"Analisar o conteúdo",finding:"Encontrar a melhor resposta",finished:"Fontes utilizadas"}},n={common:a,docs:o,logo:e,assistant:r};export{r as assistant,a as common,n as default,o as docs,e as logo};

1
docus/dist/_nuxt/BRj7a3jo.js vendored Normal file
View File

@ -0,0 +1 @@
const e={or:"oder",error:{title:"Säit net fonnt",description:"Et deet ons leed, awer d'Säit déi Dir sicht gëtt et net."}},n={copy:{page:"Säit kopéieren",link:"Markdown Säit kopéieren",view:"Als Markdown kucken",gpt:"An ChatGPT opmaachen",claude:"An Claude opmaachen"},links:"Gemeinschaft",toc:"Op dëser Säit",menu:"Menü",report:"Problem mellen",edit:"Dës Säit änneren"},o={copyLogo:"Logo kopéieren",copyWordmark:"Wortmark kopéieren",downloadLogo:"Logo eroflueden",downloadWordmark:"Wortmark eroflueden",brandAssets:"Marken-Materialien",logoCopied:"Logo kopéiert",wordmarkCopied:"Wortmark kopéiert",logoDownloaded:"Logo erofgelueden",wordmarkDownloaded:"Wortmark erofgelueden",copyLogoFailed:"Logo konnt net kopéiert ginn",copyWordmarkFailed:"Wortmark konnt net kopéiert ginn"},t={title:"Frot d'AI",placeholder:"Stell eng Fro...",tooltip:"Stell der AI eng Fro",tryAsking:"Probéieren eng Fro ze stellen",askAnything:"Frot iergendeppes...",clearChat:"Chat läschen",close:"Zoumaachen",expand:"Erweideren",collapse:"Zesummeklappen",thinking:"Denken...",askMeAnything:"Frot alles",askMeAnythingDescription:"Kritt Hëllef fir an der Dokumentatioun ze navigéieren, Konzepter ze verstoen an Äntwerten ze fannen.",faq:"FAQ",chatCleared:"Chat gëtt op Erfrëschung gereinegt",lineBreak:"Zeilepaus",explainWithAi:"Erklär mat AI",toolListPages:"Opgelëschte Dokumentatiounssäiten",toolReadPage:"Liesen",loading:{searching:"D'Dokumentatioun gëtt duerchsicht",reading:"D'Dokumenter ginn duerchgelies",analyzing:"Den Inhalt gëtt analyséiert",finding:"Déi bescht Äntwert gëtt fonnt",finished:"Benotzte Quellen"}},r={common:e,docs:n,logo:o,assistant:t};export{t as assistant,e as common,r as default,n as docs,o as logo};

1
docus/dist/_nuxt/BXokzvm8.js vendored Normal file
View File

@ -0,0 +1 @@
import{a0 as c,a1 as i,e as l,q as p,ac as u,s as m,c as d,am as a,o as f,ah as b,ag as g}from"./B3fabVUf.js";const y={base:"*:first:mt-0 *:last:mb-0 *:my-1.5"},I={__name:"ProseTabsItem",props:{label:{type:String,required:!0},description:{type:String,required:!1},class:{type:null,required:!1},ui:{type:Object,required:!1}},setup(e){const s=e,t=c(),r=i("prose.tabsItem",s),o=d(()=>a({extend:a(y),...t.ui?.prose?.tabsItem||{}}));return(n,q)=>(f(),l("div",{class:u(o.value({class:[m(r)?.base,s.class]}))},[p(n.$slots,"default",{},()=>[b(g(e.description),1)])],2))}};export{I as default};

1
docus/dist/_nuxt/BeOsfPZ5.js vendored Normal file
View File

@ -0,0 +1 @@
const a={or:"o",error:{title:"Pagina non trovata",description:"Ci scusiamo, ma sembra che questa pagina non sia disponibile."}},o={copy:{page:"Copia pagina",link:"Copia pagina Markdown",view:"Visualizza come Markdown",gpt:"Apri in ChatGPT",claude:"Apri in Claude"},links:"Comunità",toc:"In questa pagina",menu:"Menu",report:"Segnala un problema",edit:"Modifica questa pagina"},i={copyLogo:"Copia logo",copyWordmark:"Copia wordmark",downloadLogo:"Scarica logo",downloadWordmark:"Scarica wordmark",brandAssets:"Risorse del brand",logoCopied:"Logo copiato",wordmarkCopied:"Wordmark copiato",logoDownloaded:"Logo scaricato",wordmarkDownloaded:"Wordmark scaricato",copyLogoFailed:"Impossibile copiare il logo",copyWordmarkFailed:"Impossibile copiare il wordmark"},e={title:"Chiedi all'AI",placeholder:"Fai una domanda...",tooltip:"Fai una domanda all'IA",tryAsking:"Prova a fare una domanda",askAnything:"Chiedi qualsiasi cosa...",clearChat:"Cancella chat",close:"Chiudi",expand:"Espandi",collapse:"Comprimi",thinking:"Sto pensando...",askMeAnything:"Chiedi qualsiasi cosa",askMeAnythingDescription:"Ricevi assistenza per esplorare la documentazione, comprendere i concetti e trovare risposte.",faq:"Domande frequenti",chatCleared:"La chat viene cancellata all'aggiornamento",lineBreak:"Interruzione di riga",explainWithAi:"Spiega con l'IA",toolListPages:"Pagine di documentazione elencate",toolReadPage:"Leggi",loading:{searching:"Ricerca della documentazione",reading:"Leggere i documenti",analyzing:"Analizzare il contenuto",finding:"Trovare la risposta migliore",finished:"Fonti utilizzate"}},n={common:a,docs:o,logo:i,assistant:e};export{e as assistant,a as common,n as default,o as docs,i as logo};

1
docus/dist/_nuxt/BePYYsBE.js vendored Normal file
View File

@ -0,0 +1 @@
const o={or:"හෝ",error:{title:"පිටුව හමු නොවීය",description:"අපට කණගාටුයි, නමුත් මෙම පිටුව සොයාගත නොහැකි විය."}},a={copy:{page:"පිටුව පිටපත් කරන්න",link:"Markdown පිටුව පිටපත් කරන්න",view:"Markdown ලෙස බලන්න",gpt:"ChatGPT හි විවෘත කරන්න",claude:"Claude හි විවෘත කරන්න"},links:"Community",toc:"මෙම පිටුවේ",menu:"මෙනුව",report:"ගැටලුවක් වාර්තා කරන්න",edit:"මෙම පිටුව සංස්කරණය කරන්න"},e={copyLogo:"ලාංඡනය පිටපත් කරන්න",copyWordmark:"වචන ලකුණ පිටපත් කරන්න",downloadLogo:"ලාංඡනය බාගන්න",downloadWordmark:"වචන ලකුණ බාගන්න",brandAssets:"වෙළඳ නාම සම්පත්",logoCopied:"ලාංඡනය පිටපත් කරන ලදී",wordmarkCopied:"වචන ලකුණ පිටපත් කරන ලදී",logoDownloaded:"ලාංඡනය බාගත කරන ලදී",wordmarkDownloaded:"වචන ලකුණ බාගත කරන ලදී",copyLogoFailed:"ලාංඡනය පිටපත් කිරීමට අසමත් විය",copyWordmarkFailed:"වචන ලකුණ පිටපත් කිරීමට අසමත් විය"},n={title:"AI අහන්න",placeholder:"ප්‍රශ්නයක් අහන්න...",tooltip:"AI ගෙන් ප්‍රශ්නයක් අසන්න",tryAsking:"ප්රශ්නයක් ඇසීමට උත්සාහ කරන්න",askAnything:"ඕන දෙයක් අහන්න...",clearChat:"කතාබස් පැහැදිලි කරන්න",close:"වසන්න",expand:"පුළුල් කරන්න",collapse:"හකුළන්න",thinking:"සිතමින්...",askMeAnything:"ඕන දෙයක් අහන්න",askMeAnythingDescription:"ලේඛන සැරිසැරීමට, සංකල්ප තේරුම් ගැනීමට සහ පිළිතුරු සෙවීමට උදවු ලබා ගන්න.",faq:"නිතර අසන පැන",chatCleared:"නැවුම් කිරීමේදී කතාබස් හිස් වේ",lineBreak:"රේඛා බිඳීම",explainWithAi:"AI සමඟ පැහැදිලි කරන්න",toolListPages:"ලැයිස්තුගත ලේඛන පිටු",toolReadPage:"කියවන්න",loading:{searching:"ලේඛන සෙවීම",reading:"ලේඛන හරහා කියවීම",analyzing:"අන්තර්ගතය විශ්ලේෂණය කිරීම",finding:"හොඳම පිළිතුර සොයා ගැනීම",finished:"භාවිතා කරන ලද මූලාශ්ර"}},i={common:o,docs:a,logo:e,assistant:n};export{n as assistant,o as common,i as default,a as docs,e as logo};

1
docus/dist/_nuxt/BecbEIqG.js vendored Normal file
View File

@ -0,0 +1 @@
import{Q as C,a0 as q,a1 as S,o as r,a as d,p,e as g,ac as o,s as i,q as u,ab as $,f as c,af as m,a7 as w,m as F,ah as y,ag as v,P as I,c as k,am as b,c4 as P}from"./B3fabVUf.js";const j={slots:{root:"relative rounded-sm",wrapper:"",leading:"inline-flex items-center justify-center",leadingIcon:"size-5 shrink-0 text-primary",title:"text-base text-pretty font-semibold text-highlighted",description:"text-[15px] text-pretty text-muted"},variants:{orientation:{horizontal:{root:"flex items-start gap-2.5",leading:"p-0.5"},vertical:{leading:"mb-2.5"}},to:{true:{root:["has-focus-visible:ring-2 has-focus-visible:ring-primary","transition"]}},title:{true:{description:"mt-1"}}}},B=Object.assign({inheritAttrs:!1},{__name:"UPageFeature",props:{as:{type:null,required:!1},icon:{type:null,required:!1},title:{type:String,required:!1},description:{type:String,required:!1},orientation:{type:null,required:!1,default:"horizontal"},to:{type:null,required:!1},target:{type:[String,Object,null],required:!1},onClick:{type:Function,required:!1},class:{type:null,required:!1},ui:{type:Object,required:!1}},setup(e){const t=e,s=C(),h=q(),l=S("pageFeature",t),a=k(()=>b({extend:b(j),...h.ui?.pageFeature||{}})({orientation:t.orientation,title:!!t.title||!!s.title,to:!!t.to||!!t.onClick})),x=k(()=>(s.title&&P(s.title())||t.title||"Feature link").trim());return(n,f)=>(r(),d(i(I),{as:e.as,"data-orientation":e.orientation,"data-slot":"root",class:o(a.value.root({class:[i(l)?.root,t.class]})),onClick:e.onClick},{default:p(()=>[e.icon||s.leading?(r(),g("div",{key:0,"data-slot":"leading",class:o(a.value.leading({class:i(l)?.leading}))},[u(n.$slots,"leading",{ui:a.value},()=>[e.icon?(r(),d($,{key:0,name:e.icon,"data-slot":"leadingIcon",class:o(a.value.leadingIcon({class:i(l)?.leadingIcon}))},null,8,["name","class"])):c("",!0)])],2)):c("",!0),m("div",{"data-slot":"wrapper",class:o(a.value.wrapper({class:i(l)?.wrapper}))},[e.to?(r(),d(w,F({key:0,"aria-label":x.value},{to:e.to,target:e.target,...n.$attrs},{class:"focus:outline-none peer",raw:""}),{default:p(()=>[...f[0]||(f[0]=[m("span",{class:"absolute inset-0","aria-hidden":"true"},null,-1)])]),_:1},16,["aria-label"])):c("",!0),u(n.$slots,"default",{},()=>[e.title||s.title?(r(),g("div",{key:0,"data-slot":"title",class:o(a.value.title({class:i(l)?.title}))},[u(n.$slots,"title",{},()=>[y(v(e.title),1)])],2)):c("",!0),e.description||s.description?(r(),g("div",{key:1,"data-slot":"description",class:o(a.value.description({class:i(l)?.description}))},[u(n.$slots,"description",{},()=>[y(v(e.description),1)])],2)):c("",!0)])],2)]),_:3},8,["as","data-orientation","class","onClick"]))}});export{B as default};

1
docus/dist/_nuxt/BfXwXGCj.js vendored Normal file
View File

@ -0,0 +1 @@
import{aQ as c,e as t,s as d,q as n,c as p,o as r}from"./B3fabVUf.js";const u=["id"],f=["href"],k={__name:"ProseH5",props:{id:{type:String,required:!1}},setup(a){const e=a,{headings:o}=c().public.mdc,i=p(()=>e.id&&(typeof o?.anchorLinks=="boolean"&&o?.anchorLinks===!0||typeof o?.anchorLinks=="object"&&o?.anchorLinks?.h5));return(s,h)=>(r(),t("h5",{id:e.id},[e.id&&d(i)?(r(),t("a",{key:0,href:`#${e.id}`},[n(s.$slots,"default")],8,f)):n(s.$slots,"default",{key:1})],8,u))}};export{k as default};

1
docus/dist/_nuxt/Bkurqz2d.js vendored Normal file
View File

@ -0,0 +1 @@
const o={or:"ή",error:{title:"Η σελίδα δεν βρέθηκε",description:"Λυπούμαστε, αλλά η σελίδα που αναζητάτε δεν υπάρχει."}},a={copy:{page:"Αντιγραφή σελίδας",link:"Αντιγραφή σελίδας Markdown",view:"Προβολή ως Markdown",gpt:"Άνοιγμα στο ChatGPT",claude:"Άνοιγμα στο Claude"},links:"Κοινότητα",toc:"Σε αυτή τη σελίδα",menu:"Μενού",report:"Αναφορά προβλήματος",edit:"Επεξεργασία αυτής της σελίδας"},e={copyLogo:"Αντιγραφή λογοτύπου",copyWordmark:"Αντιγραφή wordmark",downloadLogo:"Λήψη λογοτύπου",downloadWordmark:"Λήψη wordmark",brandAssets:"Υλικά επωνυμίας",logoCopied:"Το λογότυπο αντιγράφηκε",wordmarkCopied:"Το wordmark αντιγράφηκε",logoDownloaded:"Το λογότυπο κατέβηκε",wordmarkDownloaded:"Το wordmark κατέβηκε",copyLogoFailed:"Αποτυχία αντιγραφής λογοτύπου",copyWordmarkFailed:"Αποτυχία αντιγραφής wordmark"},d={title:"Ερώτηση στην τεχνητή νοημοσύνη",placeholder:"Κάντε μια ερώτηση...",tooltip:"Κάντε μια ερώτηση στην τεχνητή νοημοσύνη",tryAsking:"Δοκιμάστε να κάνετε μια ερώτηση",askAnything:"Ρωτήστε οτιδήποτε...",clearChat:"Εκκαθάριση συνομιλίας",close:"Κλείσιμο",expand:"Ανάπτυξη",collapse:"Σύμπτυξη",thinking:"Σκέψη...",askMeAnything:"Ρωτήστε οτιδήποτε",askMeAnythingDescription:"Λάβετε βοήθεια για την πλοήγηση στην τεκμηρίωση, την κατανόηση των εννοιών και την εύρεση απαντήσεων.",faq:"Συχνές Ερωτήσεις",chatCleared:"Η συνομιλία εκκαθαρίζεται κατά την ανανέωση",lineBreak:"Διακοπή γραμμής",explainWithAi:"Εξηγήστε με τεχνητή νοημοσύνη",toolListPages:"Καταχωρημένες σελίδες τεκμηρίωσης",toolReadPage:"Ανάγνωση",loading:{searching:"Αναζήτηση στην τεκμηρίωση",reading:"Ανάγνωση των εγγράφων",analyzing:"Ανάλυση του περιεχομένου",finding:"Βρίσκοντας την καλύτερη απάντηση",finished:"Πηγές που χρησιμοποιήθηκαν"}},n={common:o,docs:a,logo:e,assistant:d};export{d as assistant,o as common,n as default,a as docs,e as logo};

1
docus/dist/_nuxt/BlIgAVvl.js vendored Normal file
View File

@ -0,0 +1 @@
import{a0 as c,a1 as p,e as l,q as u,ac as m,s as i,c as f,am as s,o as d}from"./B3fabVUf.js";const _={base:""},g={__name:"ProseEm",props:{class:{type:String,required:!1},ui:{type:Object,required:!1}},setup(a){const e=a,o=c(),t=p("prose.em",e),r=f(()=>s({extend:s(_),...o.ui?.prose?.em||{}}));return(n,C)=>(d(),l("em",{class:m(r.value({class:[i(t)?.base,e.class]}))},[u(n.$slots,"default")],2))}};export{g as default};

1
docus/dist/_nuxt/BlxJeUnt.js vendored Normal file
View File

@ -0,0 +1 @@
const o={or:"або",error:{title:"Сторінку не знайдено",description:"Вибачте, але сторінку, яку ви шукаєте, не знайдено."}},a={copy:{page:"Скопіювати сторінку",link:"Скопіювати Markdown сторінку",view:"Переглянути як Markdown",gpt:"Відкрити в ChatGPT",claude:"Відкрити в Claude"},links:"Спільнота",toc:"На цій сторінці",menu:"Меню",report:"Повідомити про проблему",edit:"Редагувати цю сторінку"},e={copyLogo:"Копіювати логотип",copyWordmark:"Копіювати словесний знак",downloadLogo:"Завантажити логотип",downloadWordmark:"Завантажити словесний знак",brandAssets:"Матеріали бренду",logoCopied:"Логотип скопійовано",wordmarkCopied:"Словесний знак скопійовано",logoDownloaded:"Логотип завантажено",wordmarkDownloaded:"Словесний знак завантажено",copyLogoFailed:"Не вдалося скопіювати логотип",copyWordmarkFailed:"Не вдалося скопіювати словесний знак"},n={title:"Запитайте ШІ",placeholder:"Задайте питання...",tooltip:"Задайте питання ШІ",tryAsking:"Спробуйте задати питання",askAnything:"Запитайте будь-що...",clearChat:"Очистити чат",close:"Закрити",expand:"Розгорнути",collapse:"Згорнути",thinking:"Думаючи...",askMeAnything:"Запитайте будь-що",askMeAnythingDescription:"Отримайте допомогу в навігації документацією, розумінні понять і пошуку відповідей.",faq:"Поширені запитання",chatCleared:"Чат очищається під час оновлення",lineBreak:"Розрив рядка",explainWithAi:"Поясніть за допомогою ШІ",toolListPages:"Перелічені сторінки документації",toolReadPage:"Читати",loading:{searching:"Пошук документації",reading:"Читання документів",analyzing:"Аналіз змісту",finding:"Пошук найкращої відповіді",finished:"Використані джерела"}},i={common:o,docs:a,logo:e,assistant:n};export{n as assistant,o as common,i as default,a as docs,e as logo};

1
docus/dist/_nuxt/BnHu5O7o.js vendored Normal file
View File

@ -0,0 +1 @@
import{_ as o}from"./DXQrCARz.js";import{bY as a,a as n,p as s,o as r,q as c}from"./B3fabVUf.js";const _={};function f(t,l){const e=o;return r(),n(e,null,{default:s(()=>[c(t.$slots,"default")]),_:3})}const m=a(_,[["render",f]]);export{m as default};

1
docus/dist/_nuxt/BnW3dU0n.js vendored Normal file

File diff suppressed because one or more lines are too long

1
docus/dist/_nuxt/Bnc4fItI.js vendored Normal file
View File

@ -0,0 +1 @@
import{r as f}from"./Cf5i2Hk_.js";import{Q as y,a0 as x,a1 as q,a as v,p as g,ac as r,s as t,P as k,c as C,am as u,o as l,af as S,e as i,ag as o,f as n,ah as w}from"./B3fabVUf.js";import"./BN_7HF1G.js";const B={slots:{root:"my-5",container:"flex items-center gap-3 font-mono text-sm",name:"font-semibold text-primary",wrapper:"flex-1 flex items-center gap-1.5 text-xs",required:"rounded-sm bg-error/10 text-error px-1.5 py-0.5",type:"rounded-sm bg-elevated text-toned px-1.5 py-0.5",description:"mt-3 text-muted text-sm [&_code]:text-xs/4"}},V={__name:"ProseField",props:{as:{type:null,required:!1},name:{type:String,required:!1},type:{type:String,required:!1},description:{type:String,required:!1},required:{type:Boolean,required:!1},class:{type:null,required:!1},ui:{type:Object,required:!1}},setup(e){const c=e,d=y(),p=x(),s=q("prose.field",c),a=C(()=>u({extend:u(B),...p.ui?.prose?.field||{}})());return(m,b)=>(l(),v(t(k),{as:e.as,class:r(a.value.root({class:[t(s)?.root,c.class]}))},{default:g(()=>[S("div",{class:r(a.value.container({class:t(s)?.container}))},[e.name?(l(),i("span",{key:0,class:r(a.value.name({class:t(s)?.name}))},o(e.name),3)):n("",!0),e.type||e.required?(l(),i("div",{key:1,class:r(a.value.wrapper({class:t(s)?.wrapper}))},[e.type?(l(),i("span",{key:0,class:r(a.value.type({class:t(s)?.type}))},o(e.type),3)):n("",!0),e.required?(l(),i("span",{key:1,class:r(a.value.required({class:t(s)?.required}))}," required ",2)):n("",!0)],2)):n("",!0)],2),d.default||e.description?(l(),i("div",{key:0,class:r(a.value.description({class:t(s)?.description}))},[f(m.$slots,"default",{mdcUnwrap:"p"},()=>[w(o(e.description),1)])],2)):n("",!0)]),_:3},8,["as","class"]))}};export{V as default};

1
docus/dist/_nuxt/BosS-MS4.js vendored Normal file
View File

@ -0,0 +1 @@
const e={or:"oder",error:{title:"Seite nicht gefunden",description:"Es tut uns leid, aber diese Seite konnte nicht gefunden werden."}},n={copy:{page:"Seite kopieren",link:"Markdown-Seite kopieren",view:"Als Markdown anzeigen",gpt:"In ChatGPT öffnen",claude:"In Claude öffnen"},links:"Community",toc:"Auf dieser Seite",menu:"Menü",report:"Problem melden",edit:"Diese Seite bearbeiten"},o={copyLogo:"Logo kopieren",copyWordmark:"Wortmarke kopieren",downloadLogo:"Logo herunterladen",downloadWordmark:"Wortmarke herunterladen",brandAssets:"Markenmaterialien",logoCopied:"Logo kopiert",wordmarkCopied:"Wortmarke kopiert",logoDownloaded:"Logo heruntergeladen",wordmarkDownloaded:"Wortmarke heruntergeladen",copyLogoFailed:"Logo konnte nicht kopiert werden",copyWordmarkFailed:"Wortmarke konnte nicht kopiert werden"},i={title:"AI fragen",placeholder:"Stellen Sie eine Frage...",tooltip:"KI eine Frage stellen",tryAsking:"Versuchen Sie, eine Frage zu stellen",askAnything:"Fragen Sie alles...",clearChat:"Chat löschen",close:"Schließen",expand:"Erweitern",collapse:"Einklappen",thinking:"Denken...",askMeAnything:"Fragen Sie alles",askMeAnythingDescription:"Erhalten Sie Hilfe beim Navigieren durch die Dokumentation, beim Verstehen von Konzepten und beim Finden von Antworten.",faq:"FAQ",chatCleared:"Chat wird beim Aktualisieren gelöscht",lineBreak:"Zeilenumbruch",explainWithAi:"Mit KI erklären",toolListPages:"Aufgelistete Dokumentationsseiten",toolReadPage:"Lesen",loading:{searching:"Durchsuchen der Dokumentation",reading:"Lesen der Dokumente",analyzing:"Analysieren des Inhalts",finding:"Die beste Antwort finden",finished:"Verwendete Quellen"}},t={common:e,docs:n,logo:o,assistant:i};export{i as assistant,e as common,t as default,n as docs,o as logo};

1
docus/dist/_nuxt/Bqoc_FpL.js vendored Normal file
View File

@ -0,0 +1 @@
const o={or:"או",error:{title:"העמוד לא נמצא",description:"אנו מתנצלים, אך העמוד שאתה מחפש לא קיים."}},a={copy:{page:"העתק עמוד",link:"העתק עמוד Markdown",view:"הצג כ-Markdown",gpt:"פתח ב-ChatGPT",claude:"פתח ב-Claude"},links:"קהילה",toc:"בעמוד זה",menu:"תפריט",report:"דווח על בעיה",edit:"ערוך עמוד זה"},e={copyLogo:"העתק לוגו",copyWordmark:"העתק סימן מילולי",downloadLogo:"הורד לוגו",downloadWordmark:"הורד סימן מילולי",brandAssets:"נכסי מותג",logoCopied:"הלוגו הועתק",wordmarkCopied:"הסימן המילולי הועתק",logoDownloaded:"הלוגו הורד",wordmarkDownloaded:"הסימן המילולי הורד",copyLogoFailed:"העתקת הלוגו נכשלה",copyWordmarkFailed:"העתקת הסימן המילולי נכשלה"},n={title:"שאל את הבינה המלאכותית",placeholder:"שאל שאלה...",tooltip:"שאל את הבינה המלאכותית שאלה",tryAsking:"נסה לשאול שאלה",askAnything:"שאל כל דבר...",clearChat:"נקה צ'אט",close:"סגירה",expand:"הרחבה",collapse:"כיווץ התצוגה",thinking:"חושב...",askMeAnything:"שאלו כל דבר",askMeAnythingDescription:"קבלת עזרה בניווט בתיעוד, הבנת מושגים ומציאת תשובות.",faq:"שאלות נפוצות",chatCleared:"הצ'אט נוקה בעת הרענון",lineBreak:"מעבר שורה",explainWithAi:"הסבר באמצעות בינה מלאכותית",toolListPages:"דפי תיעוד רשומים",toolReadPage:"קריאה",loading:{searching:"חיפוש בתיעוד",reading:"קריאה דרך המסמכים",analyzing:"ניתוח התוכן",finding:"מציאת התשובה הטובה ביותר",finished:"מקורות בשימוש"}},i={common:o,docs:a,logo:e,assistant:n};export{n as assistant,o as common,i as default,a as docs,e as logo};

1
docus/dist/_nuxt/BscxgZ9w.js vendored Normal file
View File

@ -0,0 +1 @@
const o={or:"lub",error:{title:"Nie znaleziono strony",description:"Przepraszamy, ale nie znaleziono tej strony."}},a={copy:{page:"Skopiuj stronę",link:"Skopiuj stronę Markdown",view:"Wyświetl jako Markdown",gpt:"Otwórz w ChatGPT",claude:"Otwórz w Claude"},links:"Społeczność",toc:"Na tej stronie",menu:"Menu",report:"Zgłoś problem",edit:"Edytuj tę stronę"},i={copyLogo:"Kopiuj logo",copyWordmark:"Kopiuj wordmark",downloadLogo:"Pobierz logo",downloadWordmark:"Pobierz wordmark",brandAssets:"Materiały marki",logoCopied:"Logo skopiowane",wordmarkCopied:"Wordmark skopiowany",logoDownloaded:"Logo pobrane",wordmarkDownloaded:"Wordmark pobrany",copyLogoFailed:"Nie udało się skopiować logo",copyWordmarkFailed:"Nie udało się skopiować wordmarku"},e={title:"Zapytaj SI",placeholder:"Zadaj pytanie...",tooltip:"Zadaj AI pytanie",tryAsking:"Spróbuj zadać pytanie",askAnything:"Zapytaj o cokolwiek...",clearChat:"Wyczyść czat",close:"Zamknij",expand:"Rozwiń",collapse:"Zwiń",thinking:"Zastanawiam się...",askMeAnything:"Zapytaj o cokolwiek",askMeAnythingDescription:"Uzyskaj pomoc w poruszaniu się po dokumentacji, zrozumieniu pojęć i znalezieniu odpowiedzi.",faq:"Często zadawane pytania",chatCleared:"Czat został wyczyszczony po odświeżeniu",lineBreak:"Podział wiersza",explainWithAi:"Wyjaśnij za pomocą sztucznej inteligencji",toolListPages:"Wymienione strony dokumentacji",toolReadPage:"Czytaj",loading:{searching:"Przeszukiwanie dokumentacji",reading:"Czytanie dokumentów",analyzing:"Analizowanie treści",finding:"Znajdowanie najlepszej odpowiedzi",finished:"Wykorzystane źródła"}},n={common:o,docs:a,logo:i,assistant:e};export{e as assistant,o as common,n as default,a as docs,i as logo};

1
docus/dist/_nuxt/BvtDsFck.js vendored Normal file
View File

@ -0,0 +1 @@
import{s,e as o,ah as e,af as t,f as n,o as a}from"./B3fabVUf.js";const i={key:0},u={__name:"ProseScript",props:{src:{type:String,required:!0}},setup(c){return(l,r)=>s(!1)?(a(),o("div",i,[...r[0]||(r[0]=[e(" Rendering the ",-1),t("code",null,"script",-1),e(" element is dangerous and is disabled by default. Consider implementing your own ",-1),t("code",null,"ProseScript",-1),e(" element to have control over script rendering. ",-1)])])):n("",!0)}};export{u as default};

1
docus/dist/_nuxt/Bzhn7Nlk.js vendored Normal file
View File

@ -0,0 +1 @@
import{a0 as p,a1 as n,e as l,q as u,ac as i,s as d,c as m,am as e,o as f}from"./B3fabVUf.js";const g={base:"grid grid-cols-1 sm:grid-cols-2 gap-5 my-5 *:my-0"},y={__name:"ProseCardGroup",props:{class:{type:null,required:!1},ui:{type:Object,required:!1}},setup(a){const s=a,o=p(),r=n("prose.cardGroup",s),t=m(()=>e({extend:e(g),...o.ui?.prose?.cardGroup||{}}));return(c,C)=>(f(),l("div",{class:i(t.value({class:[d(r)?.base,s.class]}))},[u(c.$slots,"default")],2))}};export{y as default};

1
docus/dist/_nuxt/C-sCiTCq.js vendored Normal file
View File

@ -0,0 +1 @@
import{a0 as f,a as d,ab as y,f as v,c as t,ao as a,o as r}from"./B3fabVUf.js";const u={"package.json":"i-vscode-icons-file-type-node","tsconfig.json":"i-vscode-icons-file-type-tsconfig",".npmrc":"i-vscode-icons-file-type-npm",".editorconfig":"i-vscode-icons-file-type-editorconfig",".eslintrc":"i-vscode-icons-file-type-eslint",".eslintrc.cjs":"i-vscode-icons-file-type-eslint",".eslintignore":"i-vscode-icons-file-type-eslint","eslint.config.js":"i-vscode-icons-file-type-eslint","eslint.config.mjs":"i-vscode-icons-file-type-eslint","eslint.config.cjs":"i-vscode-icons-file-type-eslint",".gitignore":"i-vscode-icons-file-type-git","yarn.lock":"i-vscode-icons-file-type-yarn",".env":"i-vscode-icons-file-type-dotenv",".env.example":"i-vscode-icons-file-type-dotenv",".vscode/settings.json":"i-vscode-icons-file-type-vscode",nuxt:"i-vscode-icons-file-type-nuxt",".nuxtrc":"i-vscode-icons-file-type-nuxt",".nuxtignore":"i-vscode-icons-file-type-nuxt","nuxt.config.js":"i-vscode-icons-file-type-nuxt","nuxt.config.ts":"i-vscode-icons-file-type-nuxt","nuxt.schema.ts":"i-vscode-icons-file-type-nuxt","tailwind.config.js":"i-vscode-icons-file-type-tailwind","tailwind.config.ts":"i-vscode-icons-file-type-tailwind",vue:"i-vscode-icons-file-type-vue",ts:"i-vscode-icons-file-type-typescript",tsx:"i-vscode-icons-file-type-typescript",mjs:"i-vscode-icons-file-type-js",cjs:"i-vscode-icons-file-type-js",js:"i-vscode-icons-file-type-js",jsx:"i-vscode-icons-file-type-js",md:"i-vscode-icons-file-type-markdown",py:"i-vscode-icons-file-type-python",cs:"i-vscode-icons-file-type-csharp",asm:"i-vscode-icons-file-type-assembly",f:"i-vscode-icons-file-type-fortran",hs:"i-vscode-icons-file-type-haskell",fs:"i-vscode-icons-file-type-fsharp",kt:"i-vscode-icons-file-type-kotlin",rs:"i-vscode-icons-file-type-rust",rb:"i-vscode-icons-file-type-ruby",lsp:"i-vscode-icons-file-type-lisp",ps1:"i-vscode-icons-file-type-powershell",psd1:"i-vscode-icons-file-type-powershell",psm1:"i-vscode-icons-file-type-powershell",go:"i-vscode-icons-file-type-go",gleam:"i-vscode-icons-file-type-gleam",bicep:"i-vscode-icons-file-type-bicep",bicepparam:"i-vscode-icons-file-type-bicep",exs:"i-vscode-icons-file-type-elixir",erl:"i-vscode-icons-file-type-erlang",sbt:"i-vscode-icons-file-type-scala",h:"i-vscode-icons-file-type-cppheader",ino:"i-vscode-icons-file-type-arduino",pl:"i-vscode-icons-file-type-perl",jl:"i-vscode-icons-file-type-julia",dart:"i-vscode-icons-file-type-dartlang",ico:"i-vscode-icons-file-type-favicon",npm:"i-vscode-icons-file-type-npm",pnpm:"i-vscode-icons-file-type-pnpm",npx:"i-vscode-icons-file-type-npm",yarn:"i-vscode-icons-file-type-yarn",bun:"i-vscode-icons-file-type-bun",deno:"i-vscode-icons-file-type-deno",yml:"i-vscode-icons-file-type-yaml",terminal:"i-lucide-terminal"},g={__name:"ProseCodeIcon",props:{icon:{type:null,required:!1},filename:{type:String,required:!1}},setup(l){const e=l,p=f(),o=t(()=>a(p.ui?.prose?.codeIcon||{},u)),c=t(()=>{if(e.icon)return e.icon;if(!e.filename)return;const i=e.filename.replace(/\s*\(.*\)\s*$/,""),s=i.includes(".")&&i.split(".").pop(),n=i.split("/").pop();return(n&&o.value[n.toLowerCase()])??(s&&(o.value[s]??`i-vscode-icons-file-type-${s}`))});return(i,s)=>c.value?(r(),d(y,{key:0,name:c.value},null,8,["name"])):v("",!0)}};export{g as default};

1
docus/dist/_nuxt/C0AA5UMg.js vendored Normal file

File diff suppressed because one or more lines are too long

1
docus/dist/_nuxt/C1fSKR9D.js vendored Normal file
View File

@ -0,0 +1 @@
const e={or:"või",error:{title:"Lehekülge ei leitud",description:"Vabandame, kuid otsitavat lehekülge ei leitud."}},a={copy:{page:"Kopeeri lehekülg",link:"Kopeeri Markdown lehekülg",view:"Vaata Markdownina",gpt:"Ava ChatGPT-s",claude:"Ava Claude'is"},links:"Kogukond",toc:"Sellel lehel",menu:"Menüü",report:"Teata probleemist",edit:"Muuda seda lehekülge"},i={copyLogo:"Kopeeri logo",copyWordmark:"Kopeeri sõnamärk",downloadLogo:"Laadi logo alla",downloadWordmark:"Laadi sõnamärk alla",brandAssets:"Brändimaterjalid",logoCopied:"Logo kopeeritud",wordmarkCopied:"Sõnamärk kopeeritud",logoDownloaded:"Logo allalaaditud",wordmarkDownloaded:"Sõnamärk allalaaditud",copyLogoFailed:"Logo kopeerimine ebaõnnestus",copyWordmarkFailed:"Sõnamärgi kopeerimine ebaõnnestus"},o={title:"Küsi tehisintellekti",placeholder:"Esita küsimus...",tooltip:"Esita tehisintellektile küsimus",tryAsking:"Proovi esitada küsimus",askAnything:"Küsi ükskõik mida...",clearChat:"Tühjenda vestlus",close:"Sulge",expand:"Laienda",collapse:"Ahenda",thinking:"Mõtlen...",askMeAnything:"Küsi ükskõik mida",askMeAnythingDescription:"Saa abi dokumentatsioonis navigeerimisel, kontseptsioonide mõistmisel ja vastuste leidmisel.",faq:"KKK",chatCleared:"Vestlus kustutatakse värskendamisel",lineBreak:"Reavahetus",explainWithAi:"Selgita tehisintellektiga",toolListPages:"Loetletud dokumentatsiooni lehed",toolReadPage:"Loe",loading:{searching:"Dokumentatsiooni otsimine",reading:"Dokumentide lugemine",analyzing:"Sisu analüüsimine",finding:"Parima vastuse leidmine",finished:"Kasutatud allikad"}},t={common:e,docs:a,logo:i,assistant:o};export{o as assistant,e as common,t as default,a as docs,i as logo};

1
docus/dist/_nuxt/C2GbCjp9.js vendored Normal file
View File

@ -0,0 +1 @@
const o={or:"یان",error:{title:"لاپەڕە نەدۆزرایەوە",description:"ببورن بەڵام ئەم پەیجە نەدۆزرایەوە."}},a={copy:{page:"کۆپیکردنی پەڕە",link:"کۆپیکردنی پەڕەی Markdown",view:"بینین وەک Markdown",gpt:"کردنەوە لە ChatGPT",claude:"کردنەوە لە Claude"},links:"کۆمەڵگا",toc:"لەم پەڕەدا",menu:"مینیو",report:"ڕاپۆرتکردنی کێشە",edit:"دەستکاریکردنی ئەم پەڕەیە"},e={copyLogo:"کۆپیکردنی لۆگۆ",copyWordmark:"کۆپیکردنی وشەنیشان",downloadLogo:"داگرتنی لۆگۆ",downloadWordmark:"داگرتنی وشەنیشان",brandAssets:"سامانەکانی براند",logoCopied:"لۆگۆ کۆپی کرا",wordmarkCopied:"وشەنیشان کۆپی کرا",logoDownloaded:"لۆگۆ دابەزێنرا",wordmarkDownloaded:"وشەنیشان دابەزێنرا",copyLogoFailed:"کۆپیکردنی لۆگۆ سەرکەوتوو نەبوو",copyWordmarkFailed:"کۆپیکردنی وشەنیشان سەرکەوتوو نەبوو"},n={title:"پرسیار لە AI بکە",placeholder:"پرسیارێک بکە...",tooltip:"پرسیارێک لە AI بکە",tryAsking:"هەوڵبدە پرسیارێک بکەیت",askAnything:"هەر شتێک بپرسە...",clearChat:"چاتی پاک بکەرەوە",close:"دابخە",expand:"فراوانتر بکە",collapse:"داڕمان",thinking:"بیرکردنەوە...",askMeAnything:"هەر شتێک بپرسە",askMeAnythingDescription:"یارمەتی وەربگرە لە گەشتکردن بە بەڵگەنامەکان، تێگەیشتن لە چەمکەکان و دۆزینەوەی وەڵامەکان.",faq:"پرسیارە بەردەوامەکان",chatCleared:"چات لە کاتی نوێکردنەوەدا پاک دەکرێتەوە",lineBreak:"هێڵ شکاندن",explainWithAi:"بە AI ڕوونی بکەرەوە",toolListPages:"لاپەڕەکانی بەڵگەنامەی ڕیزبەندی",toolReadPage:"خوێندنەوە",loading:{searching:"گەڕان بەدوای بەڵگەنامەکاندا",reading:"خوێندنەوە لە ڕێگەی دۆکیۆمێنتەکانەوە",analyzing:"شیکردنەوەی ناوەڕۆک",finding:"دۆزینەوەی باشترین وەڵام",finished:"سەرچاوە بەکارهێنراوەکان"}},i={common:o,docs:a,logo:e,assistant:n};export{n as assistant,o as common,i as default,a as docs,e as logo};

1
docus/dist/_nuxt/C3Ly8WAT.js vendored Normal file
View File

@ -0,0 +1 @@
import{r}from"./Cf5i2Hk_.js";import a from"./Jc8Ntx_l.js";import{a0 as t,a as e,p,s as c,o as n}from"./B3fabVUf.js";import"./BN_7HF1G.js";const d={__name:"ProseTip",setup(i){const o=t();return(s,f)=>(n(),e(a,{color:"success",icon:c(o).ui.icons.tip},{default:p(()=>[r(s.$slots,"default",{mdcUnwrap:"p"})]),_:3},8,["icon"]))}};export{d as default};

1
docus/dist/_nuxt/C5B99YjC.js vendored Normal file
View File

@ -0,0 +1 @@
import{i}from"./B3fabVUf.js";function r(e,s,o){const t=e.findIndex(n=>i(n,s)),d=e.findIndex(n=>i(n,o));if(t===-1||d===-1)return[];const[x,f]=[t,d].sort((n,I)=>n-I);return e.slice(x,f+1)}export{r as f};

1
docus/dist/_nuxt/C6Z5zZ8h.js vendored Normal file
View File

@ -0,0 +1 @@
import{a0 as c,a1 as p,e as l,q as u,ac as d,s as i,c as m,am as e,o as f}from"./B3fabVUf.js";const b={base:""},C={__name:"ProseTbody",props:{class:{type:null,required:!1},ui:{type:Object,required:!1}},setup(a){const s=a,o=c(),t=p("prose.tbody",s),r=m(()=>e({extend:e(b),...o.ui?.prose?.tbody||{}}));return(n,y)=>(f(),l("tbody",{class:d(r.value({class:[i(t)?.base,s.class]}))},[u(n.$slots,"default")],2))}};export{C as default};

1
docus/dist/_nuxt/C8-8Pk2V.js vendored Normal file
View File

@ -0,0 +1 @@
const a={or:"ali",error:{title:"Stran ni bila najdena",description:"Opravičujemo se, vendar stran, ki jo iščete, ni bila najdena."}},o={copy:{page:"Kopiraj stran",link:"Kopiraj Markdown stran",view:"Prikaži kot Markdown",gpt:"Odpri v ChatGPT",claude:"Odpri v Claude"},links:"Skupnost",toc:"Na tej strani",menu:"Meni",report:"Prijavi težavo",edit:"Uredi to stran"},e={copyLogo:"Kopiraj logotip",copyWordmark:"Kopiraj besedno znamko",downloadLogo:"Prenesi logotip",downloadWordmark:"Prenesi besedno znamko",brandAssets:"Materiali blagovne znamke",logoCopied:"Logotip kopiran",wordmarkCopied:"Besedna znamka kopirana",logoDownloaded:"Logotip prenesen",wordmarkDownloaded:"Besedna znamka prenesena",copyLogoFailed:"Kopiranje logotipa ni uspelo",copyWordmarkFailed:"Kopiranje besedne znamke ni uspelo"},i={title:"Vprašajte AI",placeholder:"Postavite vprašanje...",tooltip:"Zastavite vprašanje AI",tryAsking:"Poskusite postaviti vprašanje",askAnything:"Vprašajte karkoli...",clearChat:"Počisti klepet",close:"Zapri",expand:"Razširi",collapse:"Strni",thinking:"Razmišljanje...",askMeAnything:"Vprašaj karkoli",askMeAnythingDescription:"Poiščite pomoč pri krmarjenju po dokumentaciji, razumevanju konceptov in iskanju odgovorov.",faq:"Pogosta vprašanja",chatCleared:"Klepet se ob osvežitvi izbriše",lineBreak:"Prelom vrstice",explainWithAi:"Razloži z AI",toolListPages:"Navedene strani dokumentacije",toolReadPage:"Preberi",loading:{searching:"Iskanje po dokumentaciji",reading:"Prebiranje dokumentov",analyzing:"Analiza vsebine",finding:"Iskanje najboljšega odgovora",finished:"Uporabljeni viri"}},n={common:a,docs:o,logo:e,assistant:i};export{i as assistant,a as common,n as default,o as docs,e as logo};

1
docus/dist/_nuxt/C8-Mg-oX.js vendored Normal file
View File

@ -0,0 +1 @@
import{Q as x,a0 as b,a1 as $,o as a,a as y,p as g,q as n,B as q,bZ as w,ac as o,s,e as d,ah as f,ag as h,f as c,F as C,r as S,ak as B,m as P,P as z,c as j,am as m}from"./B3fabVUf.js";const H={slots:{root:"relative isolate",container:"flex flex-col lg:grid py-24 sm:py-32 lg:py-40 gap-16 sm:gap-y-24",wrapper:"",header:"",headline:"mb-4",title:"text-5xl sm:text-7xl text-pretty tracking-tight font-bold text-highlighted",description:"text-lg sm:text-xl/8 text-muted",body:"mt-10",footer:"mt-10",links:"flex flex-wrap gap-x-6 gap-y-3"},variants:{orientation:{horizontal:{container:"lg:grid-cols-2 lg:items-center",description:"text-pretty"},vertical:{container:"",headline:"justify-center",wrapper:"text-center",description:"text-balance",links:"justify-center"}},reverse:{true:{wrapper:"order-last"}},headline:{true:{headline:"font-semibold text-primary flex items-center gap-1.5"}},title:{true:{description:"mt-6"}}}},N={key:2,class:"hidden lg:block"},F={__name:"UPageHero",props:{as:{type:null,required:!1},headline:{type:String,required:!1},title:{type:String,required:!1},description:{type:String,required:!1},links:{type:Array,required:!1},orientation:{type:null,required:!1,default:"vertical"},reverse:{type:Boolean,required:!1},class:{type:null,required:!1},ui:{type:Object,required:!1}},setup(t){const u=t,e=x(),p=b(),l=$("pageHero",u),i=j(()=>m({extend:m(H),...p.ui?.pageHero||{}})({orientation:u.orientation,reverse:u.reverse,title:!!u.title||!!e.title}));return(r,V)=>(a(),y(s(z),{as:t.as,"data-orientation":t.orientation,"data-slot":"root",class:o(i.value.root({class:[s(l)?.root,u.class]}))},{default:g(()=>[n(r.$slots,"top"),q(w,{"data-slot":"container",class:o(i.value.container({class:s(l)?.container}))},{default:g(()=>[e.header||t.headline||e.headline||t.title||e.title||t.description||e.description||e.body||e.footer||t.links?.length||e.links?(a(),d("div",{key:0,"data-slot":"wrapper",class:o(i.value.wrapper({class:s(l)?.wrapper}))},[e.header||t.headline||e.headline||t.title||e.title||t.description||e.description?(a(),d("div",{key:0,"data-slot":"header",class:o(i.value.header({class:s(l)?.header}))},[n(r.$slots,"header",{},()=>[t.headline||e.headline?(a(),d("div",{key:0,"data-slot":"headline",class:o(i.value.headline({class:s(l)?.headline,headline:!e.headline}))},[n(r.$slots,"headline",{},()=>[f(h(t.headline),1)])],2)):c("",!0),t.title||e.title?(a(),d("h1",{key:1,"data-slot":"title",class:o(i.value.title({class:s(l)?.title}))},[n(r.$slots,"title",{},()=>[f(h(t.title),1)])],2)):c("",!0),t.description||e.description?(a(),d("div",{key:2,"data-slot":"description",class:o(i.value.description({class:s(l)?.description}))},[n(r.$slots,"description",{},()=>[f(h(t.description),1)])],2)):c("",!0)])],2)):c("",!0),e.body?(a(),d("div",{key:1,"data-slot":"body",class:o(i.value.body({class:s(l)?.body}))},[n(r.$slots,"body")],2)):c("",!0),e.footer||t.links?.length||e.links?(a(),d("div",{key:2,"data-slot":"footer",class:o(i.value.footer({class:s(l)?.footer}))},[n(r.$slots,"footer",{},()=>[t.links?.length||e.links?(a(),d("div",{key:0,"data-slot":"links",class:o(i.value.links({class:s(l)?.links}))},[n(r.$slots,"links",{},()=>[(a(!0),d(C,null,S(t.links,(k,v)=>(a(),y(B,P({key:v,size:"xl"},{ref_for:!0},k),null,16))),128))])],2)):c("",!0)])],2)):c("",!0)],2)):c("",!0),e.default?n(r.$slots,"default",{key:1}):t.orientation==="horizontal"?(a(),d("div",N)):c("",!0)]),_:3},8,["class"]),n(r.$slots,"bottom")]),_:3},8,["as","data-orientation","class"]))}};export{F as default};

1
docus/dist/_nuxt/C8791Vr7.js vendored Normal file
View File

@ -0,0 +1 @@
import{a0 as c,a1 as n,e as p,ac as l,s as u,c as i,am as s,o as m}from"./B3fabVUf.js";const f={base:"border-t border-default my-12"},h={__name:"ProseHr",props:{class:{type:null,required:!1},ui:{type:Object,required:!1}},setup(a){const e=a,r=c(),o=n("prose.hr",e),t=i(()=>s({extend:s(f),...r.ui?.prose?.hr||{}}));return(d,_)=>(m(),p("hr",{class:l(t.value({class:[u(o)?.base,e.class]}))},null,2))}};export{h as default};

1
docus/dist/_nuxt/C919_JS_.js vendored Normal file
View File

@ -0,0 +1 @@
const e={or:"sau",error:{title:"Pagina nu a fost găsită",description:"Ne pare rău, dar această pagină nu a putut fi găsită."}},a={copy:{page:"Copiază pagina",link:"Copiază pagina în Markdown",view:"Vezi ca Markdown",gpt:"Deschide în ChatGPT",claude:"Deschide în Claude"},links:"Comunitate",toc:"Pe această pagină",menu:"Meniu",report:"Raportează o problemă",edit:"Editează această pagină"},o={copyLogo:"Copiază logo",copyWordmark:"Copiază wordmark",downloadLogo:"Descarcă logo",downloadWordmark:"Descarcă wordmark",brandAssets:"Resurse de brand",logoCopied:"Logo copiat",wordmarkCopied:"Wordmark copiat",logoDownloaded:"Logo descărcat",wordmarkDownloaded:"Wordmark descărcat",copyLogoFailed:"Nu s-a putut copia logo-ul",copyWordmarkFailed:"Nu s-a putut copia wordmark-ul"},i={title:"Întrebați AI",placeholder:"Pune o întrebare...",tooltip:"Pune o întrebare AI",tryAsking:"Încercați să puneți o întrebare",askAnything:"Întreabă orice...",clearChat:"Ștergeți chatul",close:"Închide",expand:"Extindeți",collapse:"Colaps",thinking:"Gândind...",askMeAnything:"Întreabă orice",askMeAnythingDescription:"Obțineți ajutor pentru navigarea în documentație, înțelegerea conceptelor și găsirea răspunsurilor.",faq:"Întrebări frecvente",chatCleared:"Chatul este șters la reîmprospătare",lineBreak:"Rupere de linie",explainWithAi:"Explicați cu AI",toolListPages:"Pagini de documentație listate",toolReadPage:"Citiți",loading:{searching:"Căutarea documentației",reading:"Citind documentele",analyzing:"Analizând conținutul",finding:"Găsirea celui mai bun răspuns",finished:"Surse folosite"}},n={common:e,docs:a,logo:o,assistant:i};export{i as assistant,e as common,n as default,a as docs,o as logo};

1
docus/dist/_nuxt/CArNbJ6I.js vendored Normal file
View File

@ -0,0 +1 @@
const a={or:"atau",error:{title:"Halaman tidak ditemukan",description:"Kami minta maaf, halaman ini tidak dapat ditemukan."},copied:"Berhasil disalin ke papan klip"},n={copy:{page:"Salin halaman",link:"Salin halaman Markdown",view:"Lihat sebagai Markdown",gpt:"Buka di ChatGPT",claude:"Buka di Claude",mcp_url:"Salin URL Server MCP",mcp_add:"Tambah Server MCP"},links:"Komunitas",toc:"Pada halaman ini",menu:"Menu",report:"Laporkan masalah",edit:"Ubah halaman ini"},i={copyLogo:"Salin logo",copyWordmark:"Salin wordmark",downloadLogo:"Unduh logo",downloadWordmark:"Unduh wordmark",brandAssets:"Aset merek",logoCopied:"Logo disalin",wordmarkCopied:"Wordmark disalin",logoDownloaded:"Logo diunduh",wordmarkDownloaded:"Wordmark diunduh",copyLogoFailed:"Gagal menyalin logo",copyWordmarkFailed:"Gagal menyalin wordmark"},o={title:"Tanya AI",placeholder:"Ajukan pertanyaan...",tooltip:"Ajukan pertanyaan kepada AI",tryAsking:"Coba ajukan pertanyaan",askAnything:"Tanyakan apa saja...",clearChat:"Hapus obrolan",close:"Tutup",expand:"Perluas",collapse:"Ciutkan",thinking:"Berpikir...",askMeAnything:"Tanyakan apa saja",askMeAnythingDescription:"Dapatkan bantuan untuk menavigasi dokumentasi, memahami konsep, dan menemukan jawaban.",faq:"Pertanyaan Umum",chatCleared:"Obrolan dihapus saat penyegaran",lineBreak:"Jeda baris",explainWithAi:"Jelaskan dengan AI",toolListPages:"Halaman dokumentasi yang terdaftar",toolReadPage:"Baca",loading:{searching:"Mencari dokumentasi",reading:"Membaca dokumen",analyzing:"Menganalisis konten",finding:"Menemukan jawaban terbaik",finished:"Sumber yang digunakan"}},e={common:a,docs:n,logo:i,assistant:o};export{o as assistant,a as common,e as default,n as docs,i as logo};

Some files were not shown because too many files have changed in this diff Show More