Compare commits
No commits in common. "461b75443b9dceb3984c79088e45357f04694ee3" and "4bb4c8ff5e35eebc4dea9e5e44cfd15b21917c73" have entirely different histories.
461b75443b
...
4bb4c8ff5e
60
.github/workflows/deploy-docs.yml
vendored
60
.github/workflows/deploy-docs.yml
vendored
@ -1,60 +0,0 @@
|
|||||||
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
4
.gitignore
vendored
@ -23,10 +23,6 @@ target/
|
|||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
node_modules/
|
node_modules/
|
||||||
docus/.nuxt/
|
|
||||||
docus/.output/
|
|
||||||
docus/.data/
|
|
||||||
docus/.nitro/
|
|
||||||
|
|
||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@ -37,11 +37,6 @@ the client.
|
|||||||
The client builds vehicle context and sends requests. The server garage addon
|
The client builds vehicle context and sends requests. The server garage addon
|
||||||
and extension own stored vehicle state.
|
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
|
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
|
for nearby world vehicles. Stored records must be retrieved before they can be
|
||||||
serviced because fuel and repair operate on live vehicle objects. Service
|
serviced because fuel and repair operate on live vehicle objects. Service
|
||||||
|
|||||||
@ -88,21 +88,9 @@ GVAR(GarageActionServiceBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
GVAR(GarageUIBridge) call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [["message", "Stored vehicle record could not be found."]]]];
|
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 _context = GVAR(GarageContextService) call ["getContext", []];
|
||||||
private _vehicleCategory = GVAR(GarageHelperService) call ["resolveVGCategory", [_className]];
|
private _spawnPosition = _context getOrDefault ["spawnPosition", getPosATL player];
|
||||||
private _spawnLane = GVAR(GarageContextService) call ["getExactSpawnLane", [_vehicleCategory, _context]];
|
private _spawnHeading = _context getOrDefault ["spawnHeading", getDir player];
|
||||||
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 _spawnRadius = _context getOrDefault ["spawnRadius", 6];
|
||||||
private _blockingVehicles = [];
|
private _blockingVehicles = [];
|
||||||
{ _blockingVehicles pushBackUnique _x; } forEach (_spawnPosition nearEntities [["Car", "Tank", "Air", "Ship"], _spawnRadius]);
|
{ _blockingVehicles pushBackUnique _x; } forEach (_spawnPosition nearEntities [["Car", "Tank", "Air", "Ship"], _spawnRadius]);
|
||||||
@ -111,6 +99,11 @@ GVAR(GarageActionServiceBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
GVAR(GarageUIBridge) call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [["message", "The garage spawn area is blocked."]]]];
|
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"];
|
private _vehicle = createVehicle [_className, _spawnPosition, [], 0, "CAN_COLLIDE"];
|
||||||
_vehicle setDir _spawnHeading;
|
_vehicle setDir _spawnHeading;
|
||||||
_vehicle setFuel (_vehicleData getOrDefault ["fuel", 0]);
|
_vehicle setFuel (_vehicleData getOrDefault ["fuel", 0]);
|
||||||
|
|||||||
@ -29,189 +29,81 @@ GVAR(GarageContextServiceBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["name", "Vehicle Garage"],
|
["name", "Vehicle Garage"],
|
||||||
["anchorPosition", getPosATL player],
|
["anchorPosition", getPosATL player],
|
||||||
["sourceObject", objNull],
|
["sourceObject", objNull],
|
||||||
["garageType", ""],
|
|
||||||
["spawnHeading", getDir player],
|
["spawnHeading", getDir player],
|
||||||
["spawnPosition", player getPos [8, getDir player]],
|
["spawnPosition", player getPos [8, getDir player]],
|
||||||
["spawnLanes", createHashMap],
|
|
||||||
["spawnRadius", 6],
|
["spawnRadius", 6],
|
||||||
["nearbyRadius", 30],
|
["nearbyRadius", 30]
|
||||||
["laneRadius", 150]
|
|
||||||
]
|
]
|
||||||
}],
|
}],
|
||||||
["findNearbyGarageObject", compileFinal {
|
["scanEntryValues", compileFinal {
|
||||||
private _nearestGarage = objNull;
|
params [["_values", [], [[]]], ["_state", createHashMap, [createHashMap]]];
|
||||||
private _nearestDistance = 1e10;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
if (isNull _x || { !(_x getVariable ["isGarage", false]) }) then { continue; };
|
if (_x isEqualType "" && { (_state getOrDefault ["name", "Vehicle Garage"]) isEqualTo "Vehicle Garage" }) then { _state set ["name", _x]; };
|
||||||
private _distance = player distance2D _x;
|
if (_x isEqualType "") then {
|
||||||
if (_distance < _nearestDistance) then {
|
private _resolvedObject = _state getOrDefault ["sourceObject", objNull];
|
||||||
_nearestDistance = _distance;
|
if (isNull _resolvedObject) then {
|
||||||
_nearestGarage = _x;
|
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;
|
||||||
};
|
};
|
||||||
} forEach (player nearObjects 12);
|
if (_x isEqualType objNull && { isNull (_state getOrDefault ["sourceObject", objNull]) }) then {
|
||||||
|
_state set ["sourceObject", _x];
|
||||||
_nearestGarage
|
if ((_state getOrDefault ["anchorPosition", []]) isEqualTo []) then { _state set ["anchorPosition", getPosATL _x]; };
|
||||||
}],
|
continue;
|
||||||
["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;
|
if (_x isEqualType 0 && { (_state getOrDefault ["spawnHeading", -1]) < 0 }) then { _state set ["spawnHeading", _x]; continue; };
|
||||||
|
if (_x isEqualType [] && { count _x > 0 }) then {
|
||||||
_lanes
|
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
|
||||||
}],
|
}],
|
||||||
["selectSpawnLane", compileFinal {
|
["resolveEntry", compileFinal {
|
||||||
params [
|
params [["_entry", [], [[]]]];
|
||||||
["_lanes", createHashMap, [createHashMap]],
|
private _state = createHashMapFromArray [["name", "Vehicle Garage"], ["anchorPosition", []], ["sourceObject", objNull], ["offset", []], ["spawnHeading", -1]];
|
||||||
["_preferredCategory", "", [""]],
|
_self call ["scanEntryValues", [_entry, _state]];
|
||||||
["_defaultPosition", [], [[]]],
|
private _anchorPosition = _state getOrDefault ["anchorPosition", []];
|
||||||
["_defaultHeading", 0, [0]]
|
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]]
|
||||||
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 {
|
["resolveContext", compileFinal {
|
||||||
private _context = _self call ["createDefaultContext", []];
|
private _context = _self call ["createDefaultContext", []];
|
||||||
private _garageObject = _self call ["findNearbyGarageObject", []];
|
private _locations = (missionConfigFile >> "FORGE_CfgGarages" >> "locations") call BFUNC(getCfgData);
|
||||||
private _garageName = _self call ["resolveGarageName", [_garageObject]];
|
if !(_locations isEqualType []) exitWith { _self set ["lastContext", _context]; _context };
|
||||||
private _garageType = "";
|
|
||||||
private _anchorPosition = getPosATL player;
|
|
||||||
private _spawnHeading = getDir player;
|
|
||||||
private _spawnPosition = player getPos [8, _spawnHeading];
|
|
||||||
private _spawnLanes = createHashMap;
|
|
||||||
|
|
||||||
if (!isNull _garageObject) then {
|
private _nearestEntry = [];
|
||||||
_garageType = GVAR(GarageHelperService) call ["normalizeGarageCategory", [_garageObject getVariable ["garageType", ""]]];
|
private _nearestDistance = 1e10;
|
||||||
_anchorPosition = getPosATL _garageObject;
|
{
|
||||||
_spawnHeading = getDir _garageObject;
|
private _entry = _self call ["resolveEntry", [_x]];
|
||||||
_spawnPosition = _garageObject getPos [8, _spawnHeading];
|
private _anchorPosition = _entry getOrDefault ["anchorPosition", []];
|
||||||
_spawnLanes = _self call ["discoverSpawnLanes", [_garageObject]];
|
if (_anchorPosition isEqualTo []) then { continue; };
|
||||||
};
|
private _distance = player distance2D _anchorPosition;
|
||||||
|
if (_distance < _nearestDistance) then { _nearestDistance = _distance; _nearestEntry = _entry; };
|
||||||
|
} forEach _locations;
|
||||||
|
|
||||||
private _selectedLane = _self call ["selectSpawnLane", [_spawnLanes, _garageType, _spawnPosition, _spawnHeading]];
|
if (_nearestEntry isEqualTo []) exitWith { _self set ["lastContext", _context]; _context };
|
||||||
_spawnHeading = _selectedLane getOrDefault ["spawnHeading", _spawnHeading];
|
|
||||||
_spawnPosition = _selectedLane getOrDefault ["spawnPosition", _spawnPosition];
|
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 }; };
|
||||||
|
|
||||||
_context set ["name", _garageName];
|
_context set ["name", _garageName];
|
||||||
_context set ["anchorPosition", _anchorPosition];
|
_context set ["anchorPosition", _anchorPosition];
|
||||||
_context set ["sourceObject", _garageObject];
|
_context set ["sourceObject", _garageObject];
|
||||||
_context set ["garageType", _garageType];
|
|
||||||
_context set ["spawnHeading", _spawnHeading];
|
_context set ["spawnHeading", _spawnHeading];
|
||||||
_context set ["spawnPosition", _spawnPosition];
|
_context set ["spawnPosition", _spawnPosition];
|
||||||
_context set ["spawnLanes", _spawnLanes];
|
|
||||||
_self set ["lastContext", _context];
|
_self set ["lastContext", _context];
|
||||||
_context
|
_context
|
||||||
}],
|
}],
|
||||||
|
|||||||
@ -22,33 +22,6 @@
|
|||||||
#pragma hemtt ignore_variables ["_self"]
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
GVAR(GarageHelperServiceBaseClass) = compileFinal createHashMapFromArray [
|
GVAR(GarageHelperServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||||
["#type", "GarageHelperServiceBaseClass"],
|
["#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 {
|
["resolveCategory", compileFinal {
|
||||||
params [["_className", "", [""]]];
|
params [["_className", "", [""]]];
|
||||||
|
|
||||||
@ -63,33 +36,6 @@ GVAR(GarageHelperServiceBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
default { "other" };
|
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 {
|
["resolveDisplayName", compileFinal {
|
||||||
params [["_className", "", [""]]];
|
params [["_className", "", [""]]];
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
* File: fnc_openVG.sqf
|
* File: fnc_openVG.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2025-12-16
|
* Date: 2025-12-16
|
||||||
* Last Update: 2026-04-22
|
* Last Update: 2026-01-30
|
||||||
* Public: No
|
* Public: No
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
@ -20,12 +20,11 @@
|
|||||||
* call forge_client_garage_fnc_openVG
|
* call forge_client_garage_fnc_openVG
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private _context = GVAR(GarageContextService) call ["getContext", []];
|
private _locations = (missionConfigFile >> "FORGE_CfgGarages" >> "locations") call BFUNC(getCfgData);
|
||||||
private _spawnLane = GVAR(GarageContextService) call ["getSpawnLane", [_context getOrDefault ["garageType", ""], _context]];
|
{
|
||||||
|
FORGE_VehSpawnPos = (_x select 1) getPos [5, (_x select 2)];
|
||||||
FORGE_VehSpawnPos = _spawnLane getOrDefault ["spawnPosition", player getPos [8, getDir player]];
|
true;
|
||||||
missionNamespace setVariable [QGVAR(activeVGContext), _context];
|
} count _locations;
|
||||||
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_center = createVehicle ["Land_HelipadEmpty_F", FORGE_VehSpawnPos, [], 0, "NONE"];
|
||||||
BIS_fnc_garage_centerType = getText (configFile >> "CfgVehicles" >> "B_Quadbike_01_F" >> "model");
|
BIS_fnc_garage_centerType = getText (configFile >> "CfgVehicles" >> "B_Quadbike_01_F" >> "model");
|
||||||
@ -54,41 +53,16 @@ if !(GVAR(isPreLoaded)) then {
|
|||||||
}] call BFUNC(addScriptedEventHandler);
|
}] call BFUNC(addScriptedEventHandler);
|
||||||
|
|
||||||
[missionNamespace, "garageClosed", {
|
[missionNamespace, "garageClosed", {
|
||||||
private _nearbyVehicles = BIS_fnc_garage_center nearEntities [["Car", "Tank", "Air", "Ship"], 15];
|
private _nearestObjects = 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 _veh = typeOf _obj;
|
||||||
private _textures = getObjectTextures _obj;
|
private _textures = getObjectTextures _obj;
|
||||||
private _animationNames = animationNames _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 _spawnedVehicles;
|
{ deleteVehicle _x } forEach _nearestObjects;
|
||||||
|
private _createVehicle = createVehicle [_veh, FORGE_VehSpawnPos, [], 0, "CAN_COLLIDE"];
|
||||||
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 {
|
if (_textures isNotEqualTo []) then {
|
||||||
private _count = 0;
|
private _count = 0;
|
||||||
@ -107,9 +81,6 @@ if !(GVAR(isPreLoaded)) then {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
missionNamespace setVariable [QGVAR(activeVGNearbyVehicles), nil];
|
|
||||||
missionNamespace setVariable [QGVAR(activeVGContext), nil];
|
|
||||||
}] call BFUNC(addScriptedEventHandler);
|
}] call BFUNC(addScriptedEventHandler);
|
||||||
|
|
||||||
GVAR(isPreLoaded) = true;
|
GVAR(isPreLoaded) = true;
|
||||||
|
|||||||
@ -20,37 +20,14 @@
|
|||||||
* call forge_server_garage_fnc_initGarage
|
* 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 _garages = (allVariables missionNamespace) select {
|
||||||
private _var = missionNamespace getVariable _x;
|
private _var = missionNamespace getVariable _x;
|
||||||
((toLowerANSI _x) find "garage") >= 0 && { _var isEqualType objNull } && { !isNull _var }
|
("garage" in _x) && { _var isEqualType objNull } && { !isNull _var }
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_garages isEqualTo []) exitWith { ["INFO", "No editor-placed garages found."] call EFUNC(common,log) };
|
if (_garages isEqualTo []) exitWith { ["INFO", "No editor-placed garages found."] call EFUNC(common,log) };
|
||||||
|
|
||||||
{
|
{
|
||||||
private _garageName = _x;
|
private _garage = missionNamespace getVariable _x;
|
||||||
private _garage = missionNamespace getVariable _garageName;
|
|
||||||
SETPVAR(_garage,isGarage,true);
|
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;
|
} forEach _garages;
|
||||||
|
|||||||
@ -33,9 +33,6 @@ password = "root"
|
|||||||
connect_timeout_ms = 5000
|
connect_timeout_ms = 5000
|
||||||
```
|
```
|
||||||
|
|
||||||
For install links and Forge-specific setup steps, see
|
|
||||||
[SurrealDB Setup](../../../docs/surrealdb-setup.md).
|
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- [API Reference](./api-reference.md)
|
- [API Reference](./api-reference.md)
|
||||||
|
|||||||
@ -19,12 +19,9 @@ browser events through `forge_client_garage_fnc_handleUIEvents`.
|
|||||||
call forge_client_garage_fnc_openVG;
|
call forge_client_garage_fnc_openVG;
|
||||||
```
|
```
|
||||||
|
|
||||||
The virtual garage resolves the active interaction object near the player,
|
The virtual garage uses mission-configured `FORGE_CfgGarages` locations to set
|
||||||
discovers nearby `garage*` markers placed in Eden, chooses the matching spawn
|
the spawn/preview position, opens the BIS garage interface, and restricts the
|
||||||
lane for the selected vehicle type, opens the BIS garage interface, and
|
available vehicle lists from the virtual garage repository.
|
||||||
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
|
## Client Services
|
||||||
|
|
||||||
@ -82,24 +79,8 @@ _object setVariable ["isGarage", true, true];
|
|||||||
_object setVariable ["garageType", "cars", true];
|
_object setVariable ["garageType", "cars", true];
|
||||||
```
|
```
|
||||||
|
|
||||||
When using the server garage auto-init flow, editor-placed objects whose
|
Virtual garage access also requires configured garage locations in mission
|
||||||
variable names contain `garage` are marked as garage interaction points and
|
config so the preview/spawn position can be resolved.
|
||||||
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
|
## Authoritative State
|
||||||
|
|
||||||
|
|||||||
@ -4,9 +4,6 @@ This guide covers the usual path for adding or changing a Forge module.
|
|||||||
|
|
||||||
## Local Checks
|
## Local Checks
|
||||||
|
|
||||||
Before running storage-backed workflows locally, complete
|
|
||||||
[SurrealDB Setup](./surrealdb-setup.md).
|
|
||||||
|
|
||||||
Run these before pushing Rust or extension changes:
|
Run these before pushing Rust or extension changes:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
|||||||
@ -125,9 +125,6 @@ password = "root"
|
|||||||
connect_timeout_ms = 5000
|
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:
|
Check persistence readiness before issuing commands that require storage:
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
|
|||||||
@ -13,8 +13,6 @@ collects framework-level documentation for those pieces.
|
|||||||
crates.
|
crates.
|
||||||
- [Development Guide](./DEVELOPMENT_GUIDE.md): how to add or change a module
|
- [Development Guide](./DEVELOPMENT_GUIDE.md): how to add or change a module
|
||||||
without breaking the framework boundaries.
|
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
|
## Server and Extension Usage Guides
|
||||||
|
|
||||||
|
|||||||
@ -1,101 +0,0 @@
|
|||||||
# 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
5
docus/.gitignore
vendored
@ -1,5 +0,0 @@
|
|||||||
.nuxt
|
|
||||||
.output
|
|
||||||
.data
|
|
||||||
.nitro
|
|
||||||
node_modules
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
# 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`
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
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'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
title: Getting Started
|
|
||||||
icon: i-lucide-rocket
|
|
||||||
@ -1,88 +0,0 @@
|
|||||||
# 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.
|
|
||||||
:::
|
|
||||||
::
|
|
||||||
@ -1,136 +0,0 @@
|
|||||||
# 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", []];
|
|
||||||
```
|
|
||||||
@ -1,221 +0,0 @@
|
|||||||
# 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. |
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@ -1,101 +0,0 @@
|
|||||||
# 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`.
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
# 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;
|
|
||||||
```
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
title: Server Modules
|
|
||||||
icon: i-lucide-layers-3
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
# 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.
|
|
||||||
:::
|
|
||||||
::
|
|
||||||
@ -1,127 +0,0 @@
|
|||||||
# Actor Usage Guide
|
|
||||||
|
|
||||||
The actor module stores persistent player character data: identity, loadout,
|
|
||||||
position, direction, stance, contact fields, state, holster status, rank, and
|
|
||||||
organization.
|
|
||||||
|
|
||||||
## Storage Model
|
|
||||||
|
|
||||||
Actor data is persisted through SurrealDB by the server extension.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"uid": "76561198000000000",
|
|
||||||
"name": "Player Name",
|
|
||||||
"loadout": {},
|
|
||||||
"position": [1234.5, 6789.0, 0.0],
|
|
||||||
"direction": 90.0,
|
|
||||||
"stance": "STAND",
|
|
||||||
"email": "0160000000@spearnet.mil",
|
|
||||||
"phone_number": "0160000000",
|
|
||||||
"state": "HEALTHY",
|
|
||||||
"holster": true,
|
|
||||||
"rank": null,
|
|
||||||
"organization": "default"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Rules validated by the Rust service:
|
|
||||||
|
|
||||||
- `uid` is authoritative from the command argument and must be a 17-digit Steam
|
|
||||||
UID.
|
|
||||||
- `name` is optional, but cannot be empty when set and cannot exceed 50
|
|
||||||
characters.
|
|
||||||
- `position` must be three finite numbers when set.
|
|
||||||
- `direction` must be in the `0.0 <= direction < 360.0` range.
|
|
||||||
- `email` must contain `@` and end with `.mil` when set.
|
|
||||||
- `phone_number` must start with `0160` and be 10 digits when set.
|
|
||||||
- Empty `phone_number`, `email`, or `organization` fields are filled on create.
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
All commands are called on the `actor` group.
|
|
||||||
|
|
||||||
| Command | Arguments | Returns |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `actor:get` | `uid` | Actor JSON. If no actor exists, returns a default actor but does not persist it. |
|
|
||||||
| `actor:create` | `uid`, `actor_json` | Persisted actor JSON. |
|
|
||||||
| `actor:update` | `uid`, `patch_json` | Updated actor JSON. |
|
|
||||||
| `actor:exists` | `uid` | `true` or `false`. |
|
|
||||||
| `actor:delete` | `uid` | `OK`. |
|
|
||||||
|
|
||||||
## Create an Actor
|
|
||||||
|
|
||||||
The `uid` field in the JSON is overwritten with the command UID.
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _actor = createHashMapFromArray [
|
|
||||||
["uid", getPlayerUID player],
|
|
||||||
["name", name player],
|
|
||||||
["loadout", getUnitLoadout player],
|
|
||||||
["position", getPosATL player],
|
|
||||||
["direction", getDir player],
|
|
||||||
["stance", stance player],
|
|
||||||
["email", ""],
|
|
||||||
["phone_number", ""],
|
|
||||||
["state", "HEALTHY"],
|
|
||||||
["holster", true],
|
|
||||||
["organization", "default"]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["actor:create", [
|
|
||||||
getPlayerUID player,
|
|
||||||
toJSON _actor
|
|
||||||
]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Update an Actor
|
|
||||||
|
|
||||||
`actor:update` accepts a JSON object containing only fields to change.
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _patch = createHashMapFromArray [
|
|
||||||
["position", getPosATL player],
|
|
||||||
["direction", getDir player],
|
|
||||||
["stance", stance player],
|
|
||||||
["loadout", getUnitLoadout player]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["actor:update", [
|
|
||||||
getPlayerUID player,
|
|
||||||
toJSON _patch
|
|
||||||
]];
|
|
||||||
```
|
|
||||||
|
|
||||||
Supported patch fields are `name`, `position`, `direction`, `stance`, `email`,
|
|
||||||
`phone_number`, `state`, `holster`, `rank`, `organization`, and `loadout`.
|
|
||||||
`uid` is ignored.
|
|
||||||
|
|
||||||
## Hot State
|
|
||||||
|
|
||||||
The `actor:hot:*` commands keep a runtime copy of actor data and write it back
|
|
||||||
only when `actor:hot:save` runs.
|
|
||||||
|
|
||||||
| Command | Arguments | Returns |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `actor:hot:init` | `uid` | Actor JSON from durable storage. |
|
|
||||||
| `actor:hot:get` | `uid` | Actor JSON. |
|
|
||||||
| `actor:hot:keys` | none | JSON array of hot actor UIDs. |
|
|
||||||
| `actor:hot:override` | `uid`, `actor_json` | Actor JSON. |
|
|
||||||
| `actor:hot:save` | `uid` | Current hot actor JSON and async durable save. |
|
|
||||||
| `actor:hot:remove` | `uid` | `OK`. |
|
|
||||||
|
|
||||||
Use hot state for frequently updated session data such as position and loadout.
|
|
||||||
Use durable commands for account creation and administrative changes.
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _result = "forge_server" callExtension ["actor:get", [getPlayerUID player]];
|
|
||||||
private _payload = _result select 0;
|
|
||||||
|
|
||||||
if (_payload find "Error:" == 0) exitWith {
|
|
||||||
systemChat format ["Actor error: %1", _payload];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _actor = fromJSON _payload;
|
|
||||||
```
|
|
||||||
@ -1,151 +0,0 @@
|
|||||||
# Store Usage Guide
|
|
||||||
|
|
||||||
The store module processes checkout requests. It charges a payment source and
|
|
||||||
grants purchased items to the player locker, virtual arsenal locker, and
|
|
||||||
virtual garage unlocks.
|
|
||||||
|
|
||||||
## 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;
|
|
||||||
```
|
|
||||||
@ -1,289 +0,0 @@
|
|||||||
# Task Usage Guide
|
|
||||||
|
|
||||||
The task module stores transient mission task metadata for active server or
|
|
||||||
mission lifecycle workflows. SQF still owns Arma-only runtime state such as
|
|
||||||
objects and participants.
|
|
||||||
|
|
||||||
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];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
@ -1,169 +0,0 @@
|
|||||||
# Bank Usage Guide
|
|
||||||
|
|
||||||
The bank module stores player account balances, earnings, PINs, and transaction
|
|
||||||
strings. The hot-state API also owns the active banking workflows used by the
|
|
||||||
UI: deposit, withdraw, transfer, checkout charge, and PIN validation.
|
|
||||||
|
|
||||||
## Storage Model
|
|
||||||
|
|
||||||
Bank data is persisted through SurrealDB by the server extension.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"uid": "76561198000000000",
|
|
||||||
"name": "Player Name",
|
|
||||||
"bank": 1000.0,
|
|
||||||
"cash": 250.0,
|
|
||||||
"earnings": 0.0,
|
|
||||||
"pin": 1234,
|
|
||||||
"transactions": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Rules validated by the Rust service:
|
|
||||||
|
|
||||||
- `uid` is authoritative from the command argument.
|
|
||||||
- `name` cannot be empty.
|
|
||||||
- `bank` and `cash` cannot be negative.
|
|
||||||
- `pin` must be a four-digit number.
|
|
||||||
- Durable `bank:get` requires an existing bank account.
|
|
||||||
|
|
||||||
## Durable Commands
|
|
||||||
|
|
||||||
| Command | Arguments | Returns |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `bank:create` | `uid`, `bank_json` | Persisted bank JSON. |
|
|
||||||
| `bank:get` | `uid` | Bank JSON. |
|
|
||||||
| `bank:update` | `uid`, `patch_json` | Updated bank JSON. |
|
|
||||||
| `bank:exists` | `uid` | `true` or `false`. |
|
|
||||||
| `bank:delete` | `uid` | `OK`. |
|
|
||||||
|
|
||||||
## Create an Account
|
|
||||||
|
|
||||||
The `uid` field in the JSON is overwritten with the command UID.
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _account = createHashMapFromArray [
|
|
||||||
["uid", getPlayerUID player],
|
|
||||||
["name", name player],
|
|
||||||
["bank", 0],
|
|
||||||
["cash", 0],
|
|
||||||
["earnings", 0],
|
|
||||||
["pin", 1234],
|
|
||||||
["transactions", []]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["bank:create", [
|
|
||||||
getPlayerUID player,
|
|
||||||
toJSON _account
|
|
||||||
]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Hot-State Commands
|
|
||||||
|
|
||||||
| Command | Arguments | Returns |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `bank:hot:init` | `uid` | Bank JSON loaded into hot state. |
|
|
||||||
| `bank:hot:get` | `uid` | Bank JSON. |
|
|
||||||
| `bank:hot:override` | `uid`, `bank_json` | Bank JSON. |
|
|
||||||
| `bank:hot:patch` | `uid`, `patch_json` | `{ account, patch }`. |
|
|
||||||
| `bank:hot:deposit` | `uid`, `amount`, `context_json` | `{ account, patch }`. |
|
|
||||||
| `bank:hot:withdraw` | `uid`, `amount`, `context_json` | `{ account, patch }`. |
|
|
||||||
| `bank:hot:deposit_earnings` | `uid`, `amount`, `context_json` | `{ account, patch }`. |
|
|
||||||
| `bank:hot:transfer` | `source_uid`, `target_uid`, `amount`, `context_json` | Transfer result JSON. |
|
|
||||||
| `bank:hot:charge_checkout` | `uid`, `amount`, `context_json` | `{ account, patch }`. |
|
|
||||||
| `bank:hot:validate_pin` | `uid`, `pin`, `context_json` | `{}` on success. |
|
|
||||||
| `bank:hot:save` | `uid` | Current hot bank JSON and async durable save. |
|
|
||||||
| `bank:hot:remove` | `uid` | `OK`. |
|
|
||||||
|
|
||||||
Use hot-state commands for UI workflows. They return patch objects so the UI can
|
|
||||||
update only changed fields.
|
|
||||||
|
|
||||||
## Deposit and Withdraw
|
|
||||||
|
|
||||||
ATM sessions require `atmAuthorized: true`. Full bank sessions can set
|
|
||||||
`mode: "bank"`.
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _context = createHashMapFromArray [
|
|
||||||
["mode", "atm"],
|
|
||||||
["atmAuthorized", true]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _deposit = "forge_server" callExtension ["bank:hot:deposit", [
|
|
||||||
getPlayerUID player,
|
|
||||||
"100",
|
|
||||||
toJSON _context
|
|
||||||
]];
|
|
||||||
|
|
||||||
private _withdraw = "forge_server" callExtension ["bank:hot:withdraw", [
|
|
||||||
getPlayerUID player,
|
|
||||||
"50",
|
|
||||||
toJSON _context
|
|
||||||
]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Transfer
|
|
||||||
|
|
||||||
Transfers are only available from the full bank interface. `fromField` can be
|
|
||||||
`bank` or `cash`.
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _context = createHashMapFromArray [
|
|
||||||
["mode", "bank"],
|
|
||||||
["atmAuthorized", false],
|
|
||||||
["fromField", "bank"]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["bank:hot:transfer", [
|
|
||||||
getPlayerUID player,
|
|
||||||
_targetUid,
|
|
||||||
"250",
|
|
||||||
toJSON _context
|
|
||||||
]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Checkout Charge
|
|
||||||
|
|
||||||
Checkout charging supports `sourceField: "cash"` or `sourceField: "bank"`.
|
|
||||||
Set `commit` to `false` to preview the patch without saving.
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _context = createHashMapFromArray [
|
|
||||||
["sourceField", "bank"],
|
|
||||||
["commit", true]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["bank:hot:charge_checkout", [
|
|
||||||
getPlayerUID player,
|
|
||||||
"125",
|
|
||||||
toJSON _context
|
|
||||||
]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## PIN Validation
|
|
||||||
|
|
||||||
PIN entry is only valid in ATM mode.
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _context = createHashMapFromArray [["mode", "atm"]];
|
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["bank:hot:validate_pin", [
|
|
||||||
getPlayerUID player,
|
|
||||||
"1234",
|
|
||||||
toJSON _context
|
|
||||||
]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _result = "forge_server" callExtension ["bank:hot:get", [getPlayerUID player]];
|
|
||||||
private _payload = _result select 0;
|
|
||||||
|
|
||||||
if (_payload find "Error:" == 0) exitWith {
|
|
||||||
systemChat format ["Bank error: %1", _payload];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _bank = fromJSON _payload;
|
|
||||||
```
|
|
||||||
@ -1,183 +0,0 @@
|
|||||||
# CAD Usage Guide
|
|
||||||
|
|
||||||
The CAD module stores transient operational state for dispatch activity,
|
|
||||||
assignments, dispatch orders, support requests, group profiles, grouped views,
|
|
||||||
and hydrated UI payloads. CAD state is in-memory and follows the active server
|
|
||||||
or mission lifecycle.
|
|
||||||
|
|
||||||
## Data Model
|
|
||||||
|
|
||||||
Most CAD records are flexible JSON objects. The service normalizes important
|
|
||||||
IDs and returns structured mutation results for higher-level workflows.
|
|
||||||
|
|
||||||
Common generated IDs:
|
|
||||||
|
|
||||||
- Orders: `cad-order:<sequence>`
|
|
||||||
- Requests: `cad-request:<sequence>`
|
|
||||||
- Assignments usually share a task ID or order ID.
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
### Activity
|
|
||||||
|
|
||||||
| Command | Arguments | Returns |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `cad:activity:append` | `activity_json` | `OK`. |
|
|
||||||
| `cad:activity:recent` | `limit` | Recent activity array JSON. |
|
|
||||||
|
|
||||||
### Assignments
|
|
||||||
|
|
||||||
| Command | Arguments | Returns |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `cad:assignments:list` | none | Assignment array JSON. |
|
|
||||||
| `cad:assignments:assign` | `entry_id`, `assignment_json` | Assignment mutation result JSON. |
|
|
||||||
| `cad:assignments:acknowledge` | `entry_id`, `patch_json` | Assignment mutation result JSON. |
|
|
||||||
| `cad:assignments:decline` | `entry_id`, `patch_json` | Assignment mutation result JSON and removes assignment. |
|
|
||||||
| `cad:assignments:upsert` | `entry_id`, `assignment_json` | `OK`. |
|
|
||||||
| `cad:assignments:delete` | `entry_id` | `OK`. |
|
|
||||||
|
|
||||||
### Orders
|
|
||||||
|
|
||||||
| Command | Arguments | Returns |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `cad:orders:list` | none | Order array JSON. |
|
|
||||||
| `cad:orders:create` | `order_seed_json` | Dispatch order mutation result JSON. |
|
|
||||||
| `cad:orders:create_from_context` | `context_json` | Dispatch order mutation result JSON. |
|
|
||||||
| `cad:orders:close` | `entry_id` | Dispatch order mutation result JSON and removes order/assignment. |
|
|
||||||
| `cad:orders:upsert` | `entry_id`, `order_json` | `OK`. |
|
|
||||||
| `cad:orders:delete` | `entry_id` | `OK`. |
|
|
||||||
|
|
||||||
### Requests
|
|
||||||
|
|
||||||
| Command | Arguments | Returns |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `cad:requests:list` | none | Request array JSON. |
|
|
||||||
| `cad:requests:submit` | `request_json` | Request mutation result JSON. |
|
|
||||||
| `cad:requests:submit_from_context` | `context_json` | Request mutation result JSON. |
|
|
||||||
| `cad:requests:close` | `entry_id` | Request mutation result JSON and removes request. |
|
|
||||||
| `cad:requests:upsert` | `entry_id`, `request_json` | `OK`. |
|
|
||||||
| `cad:requests:delete` | `entry_id` | `OK`. |
|
|
||||||
|
|
||||||
### Profiles and Views
|
|
||||||
|
|
||||||
| Command | Arguments | Returns |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `cad:profiles:list` | none | Profile array JSON. |
|
|
||||||
| `cad:profiles:update_from_context` | `context_json` | Profile mutation result JSON. |
|
|
||||||
| `cad:profiles:upsert` | `entry_id`, `profile_json` | `OK`. |
|
|
||||||
| `cad:profiles:delete` | `entry_id` | `OK`. |
|
|
||||||
| `cad:groups:build` | `groups_seed_json` | Group array JSON. |
|
|
||||||
| `cad:view:hydrate` | `hydrate_seed_json` | Hydrated CAD payload JSON. |
|
|
||||||
|
|
||||||
## Submit a Support Request
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _fields = createHashMapFromArray [
|
|
||||||
["pickup_location", "Grid 123456"],
|
|
||||||
["precedence", "urgent"],
|
|
||||||
["security", "secure"]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _context = createHashMapFromArray [
|
|
||||||
["type", "medevac_9line"],
|
|
||||||
["fields", _fields],
|
|
||||||
["groupId", "alpha"],
|
|
||||||
["groupCallsign", "Alpha 1-1"],
|
|
||||||
["submittedByUid", getPlayerUID player],
|
|
||||||
["submittedByName", name player],
|
|
||||||
["priority", "emergency"],
|
|
||||||
["position", getPosATL player],
|
|
||||||
["createdAt", diag_tickTime]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["cad:requests:submit_from_context", [
|
|
||||||
toJSON _context
|
|
||||||
]];
|
|
||||||
```
|
|
||||||
|
|
||||||
Supported priority values are `routine`, `priority`, and `emergency`. Unknown
|
|
||||||
values normalize to `priority`.
|
|
||||||
|
|
||||||
## Create a Dispatch Order
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _context = createHashMapFromArray [
|
|
||||||
["assigneeGroupId", "bravo"],
|
|
||||||
["assigneeGroupCallsign", "Bravo 1-1"],
|
|
||||||
["targetGroupId", "alpha"],
|
|
||||||
["targetGroupCallsign", "Alpha 1-1"],
|
|
||||||
["targetPosition", getPosATL player],
|
|
||||||
["createdByUid", getPlayerUID player],
|
|
||||||
["createdByName", name player],
|
|
||||||
["requestId", "cad-request:1"],
|
|
||||||
["requestType", "logreq"],
|
|
||||||
["requestTitle", "LOGREQ | Alpha 1-1"],
|
|
||||||
["requestSummary", "Ammo resupply requested"],
|
|
||||||
["requestFields", createHashMap],
|
|
||||||
["note", "Support Alpha 1-1 at current position."],
|
|
||||||
["priority", "priority"],
|
|
||||||
["createdAt", diag_tickTime]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["cad:orders:create_from_context", [
|
|
||||||
toJSON _context
|
|
||||||
]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Assignment Workflow
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _assignment = createHashMapFromArray [
|
|
||||||
["groupId", "bravo"],
|
|
||||||
["assigneeGroupCallsign", "Bravo 1-1"],
|
|
||||||
["assignedByUid", getPlayerUID player],
|
|
||||||
["assignedByName", name player],
|
|
||||||
["assignedAt", diag_tickTime],
|
|
||||||
["state", "assigned"]
|
|
||||||
];
|
|
||||||
|
|
||||||
"forge_server" callExtension ["cad:assignments:assign", [
|
|
||||||
"task-123",
|
|
||||||
toJSON _assignment
|
|
||||||
]];
|
|
||||||
|
|
||||||
private _ack = createHashMapFromArray [
|
|
||||||
["state", "acknowledged"],
|
|
||||||
["acknowledgedByUid", getPlayerUID player],
|
|
||||||
["acknowledgedAt", diag_tickTime]
|
|
||||||
];
|
|
||||||
|
|
||||||
"forge_server" callExtension ["cad:assignments:acknowledge", [
|
|
||||||
"task-123",
|
|
||||||
toJSON _ack
|
|
||||||
]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Hydrate the CAD UI
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _session = createHashMapFromArray [
|
|
||||||
["uid", getPlayerUID player],
|
|
||||||
["orgId", "default"],
|
|
||||||
["isDispatcher", true],
|
|
||||||
["groupId", "alpha"],
|
|
||||||
["isLeader", true]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _seed = createHashMapFromArray [
|
|
||||||
["groups", _liveGroups],
|
|
||||||
["activeTasks", _activeTasks],
|
|
||||||
["session", _session]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["cad:view:hydrate", [toJSON _seed]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _payload = _result select 0;
|
|
||||||
if (_payload find "Error:" == 0) exitWith {
|
|
||||||
systemChat format ["CAD error: %1", _payload];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@ -1,212 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@ -1,203 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@ -1,232 +0,0 @@
|
|||||||
# Organization Usage Guide
|
|
||||||
|
|
||||||
The organization module stores organization records, members, assets, fleet
|
|
||||||
entries, and credit lines. Durable commands manage persisted records directly.
|
|
||||||
Hot-state commands support the active organization UI workflows.
|
|
||||||
|
|
||||||
## Storage Model
|
|
||||||
|
|
||||||
Core organization:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "default",
|
|
||||||
"owner": "server",
|
|
||||||
"name": "Default Organization",
|
|
||||||
"funds": 0.0,
|
|
||||||
"reputation": 0,
|
|
||||||
"credit_lines": {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Hot organization:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "default",
|
|
||||||
"owner": "server",
|
|
||||||
"name": "Default Organization",
|
|
||||||
"funds": 0.0,
|
|
||||||
"reputation": 0,
|
|
||||||
"credit_lines": {},
|
|
||||||
"assets": {},
|
|
||||||
"fleet": {},
|
|
||||||
"members": {},
|
|
||||||
"pending_invites": {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Rules validated by the Rust service:
|
|
||||||
|
|
||||||
- `id` must be non-empty and contain only alphanumeric characters or `_`.
|
|
||||||
- `owner` must be `server` or a 17-digit Steam UID.
|
|
||||||
- `name` cannot be empty, cannot exceed 100 characters, and cannot contain
|
|
||||||
control characters.
|
|
||||||
- `funds`, reputation, and credit line amounts cannot be negative.
|
|
||||||
- Player registration is rejected when the player already belongs to a
|
|
||||||
non-default organization.
|
|
||||||
|
|
||||||
## Durable Commands
|
|
||||||
|
|
||||||
| Command | Arguments | Returns |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `org:create` | `org_id`, `org_json` | Organization JSON. |
|
|
||||||
| `org:get` | `org_id` | Organization JSON. |
|
|
||||||
| `org:update` | `org_id`, `patch_json` | Updated organization JSON. |
|
|
||||||
| `org:exists` | `org_id` | `true` or `false`. |
|
|
||||||
| `org:delete` | `org_id` | `OK`. |
|
|
||||||
| `org:assets:get` | `org_id` | Asset map JSON. |
|
|
||||||
| `org:assets:update` | `org_id`, `assets_json` | Updated asset map JSON. |
|
|
||||||
| `org:fleet:get` | `org_id` | Fleet map JSON. |
|
|
||||||
| `org:fleet:update` | `org_id`, `fleet_json` | Updated fleet map JSON. |
|
|
||||||
| `org:members:get` | `org_id` | Member array JSON. |
|
|
||||||
| `org:members:add` | `org_id`, `member_uid` | `OK`. |
|
|
||||||
| `org:members:remove` | `org_id`, `member_uid` | `OK`. |
|
|
||||||
|
|
||||||
## Create an Organization
|
|
||||||
|
|
||||||
The command key is authoritative for `id`.
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _org = createHashMapFromArray [
|
|
||||||
["id", _orgId],
|
|
||||||
["owner", getPlayerUID player],
|
|
||||||
["name", "Spearnet Logistics"],
|
|
||||||
["funds", 0],
|
|
||||||
["reputation", 0],
|
|
||||||
["credit_lines", createHashMap]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["org:create", [
|
|
||||||
_orgId,
|
|
||||||
toJSON _org
|
|
||||||
]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Update Organization Funds
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _patch = createHashMapFromArray [
|
|
||||||
["funds", 5000],
|
|
||||||
["reputation", 10]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["org:update", [
|
|
||||||
_orgId,
|
|
||||||
toJSON _patch
|
|
||||||
]];
|
|
||||||
```
|
|
||||||
|
|
||||||
Supported durable patch fields are `id`, `owner`, `name`, `funds`,
|
|
||||||
`reputation`, and `credit_lines`.
|
|
||||||
|
|
||||||
## Assets and Fleet
|
|
||||||
|
|
||||||
Assets are grouped by category, then classname.
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _assets = createHashMapFromArray [
|
|
||||||
["ammo", createHashMapFromArray [
|
|
||||||
["ACE_30Rnd_65x39_caseless_mag", createHashMapFromArray [
|
|
||||||
["classname", "ACE_30Rnd_65x39_caseless_mag"],
|
|
||||||
["type", "ammo"],
|
|
||||||
["quantity", 20]
|
|
||||||
]]
|
|
||||||
]]
|
|
||||||
];
|
|
||||||
|
|
||||||
"forge_server" callExtension ["org:assets:update", [_orgId, toJSON _assets]];
|
|
||||||
```
|
|
||||||
|
|
||||||
Fleet is keyed by an internal fleet entry ID.
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _fleet = createHashMapFromArray [
|
|
||||||
["B_Truck_01_transport_F_0", createHashMapFromArray [
|
|
||||||
["classname", "B_Truck_01_transport_F"],
|
|
||||||
["name", "Transport Truck"],
|
|
||||||
["type", "cars"],
|
|
||||||
["status", "Ready"],
|
|
||||||
["damage", "0%"]
|
|
||||||
]]
|
|
||||||
];
|
|
||||||
|
|
||||||
"forge_server" callExtension ["org:fleet:update", [_orgId, toJSON _fleet]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Hot-State Commands
|
|
||||||
|
|
||||||
| Command | Arguments | Returns |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `org:hot:init` | `org_id` | Hot organization JSON. |
|
|
||||||
| `org:hot:get` | `org_id` | Hot organization JSON. |
|
|
||||||
| `org:hot:override` | `org_id`, `hot_org_json` | Hot organization JSON. |
|
|
||||||
| `org:hot:ensure_member` | `context_json` | Hot organization JSON. |
|
|
||||||
| `org:hot:member_invites` | `member_uid` | Invite array JSON. |
|
|
||||||
| `org:hot:register` | `context_json` | Register result JSON. |
|
|
||||||
| `org:hot:invite_member` | `context_json` | Invite result JSON. |
|
|
||||||
| `org:hot:accept_invite` | `context_json` | Invite decision result JSON. |
|
|
||||||
| `org:hot:decline_invite` | `context_json` | Invite decision result JSON. |
|
|
||||||
| `org:hot:assign_credit_line` | `context_json` | Mutation result JSON. |
|
|
||||||
| `org:hot:repay_credit_line` | `context_json` | Repayment result JSON. |
|
|
||||||
| `org:hot:charge_checkout` | `context_json` | Mutation result JSON. |
|
|
||||||
| `org:hot:add_assets` | `context_json`, `assets_json` | Mutation result JSON. |
|
|
||||||
| `org:hot:add_fleet` | `context_json`, `fleet_json` | Mutation result JSON. |
|
|
||||||
| `org:hot:leave` | `context_json` | Leave result JSON. |
|
|
||||||
| `org:hot:disband` | `context_json` | Disband result JSON. |
|
|
||||||
| `org:hot:save` | `org_id` | Current hot organization JSON and async durable save. |
|
|
||||||
| `org:hot:remove` | `org_id` | `OK`. |
|
|
||||||
|
|
||||||
## Register from UI Context
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _context = createHashMapFromArray [
|
|
||||||
["requesterUid", getPlayerUID player],
|
|
||||||
["requesterName", name player],
|
|
||||||
["orgId", _orgId],
|
|
||||||
["orgName", "Spearnet Logistics"],
|
|
||||||
["existingOrgId", "default"]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["org:hot:register", [toJSON _context]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Invite and Accept
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _invite = createHashMapFromArray [
|
|
||||||
["requesterUid", getPlayerUID player],
|
|
||||||
["requesterName", name player],
|
|
||||||
["orgId", _orgId],
|
|
||||||
["requesterIsDefaultOrgCeo", false],
|
|
||||||
["targetUid", _targetUid],
|
|
||||||
["targetName", _targetName],
|
|
||||||
["targetOrgId", "default"]
|
|
||||||
];
|
|
||||||
|
|
||||||
"forge_server" callExtension ["org:hot:invite_member", [toJSON _invite]];
|
|
||||||
|
|
||||||
private _decision = createHashMapFromArray [
|
|
||||||
["requesterUid", _targetUid],
|
|
||||||
["requesterName", _targetName],
|
|
||||||
["orgId", _orgId],
|
|
||||||
["existingOrgId", "default"]
|
|
||||||
];
|
|
||||||
|
|
||||||
"forge_server" callExtension ["org:hot:accept_invite", [toJSON _decision]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Credit Line Checkout
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _credit = createHashMapFromArray [
|
|
||||||
["requesterUid", getPlayerUID player],
|
|
||||||
["orgId", _orgId],
|
|
||||||
["requesterIsDefaultOrgCeo", false],
|
|
||||||
["memberUid", _memberUid],
|
|
||||||
["memberName", _memberName],
|
|
||||||
["amount", 1000]
|
|
||||||
];
|
|
||||||
|
|
||||||
"forge_server" callExtension ["org:hot:assign_credit_line", [toJSON _credit]];
|
|
||||||
|
|
||||||
private _charge = createHashMapFromArray [
|
|
||||||
["requesterUid", _memberUid],
|
|
||||||
["orgId", _orgId],
|
|
||||||
["requesterIsDefaultOrgCeo", false],
|
|
||||||
["source", "credit_line"],
|
|
||||||
["amount", 250],
|
|
||||||
["commit", true]
|
|
||||||
];
|
|
||||||
|
|
||||||
"forge_server" callExtension ["org:hot:charge_checkout", [toJSON _charge]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _payload = _result select 0;
|
|
||||||
if (_payload find "Error:" == 0) exitWith {
|
|
||||||
systemChat format ["Organization error: %1", _payload];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
@ -1,158 +0,0 @@
|
|||||||
# Owned Storage Usage Guide
|
|
||||||
|
|
||||||
Owned storage covers the `owned:locker` and `owned:garage` extension command
|
|
||||||
groups. These modules store unlock lists rather than physical item or vehicle
|
|
||||||
instances.
|
|
||||||
|
|
||||||
Use these modules for virtual arsenal and virtual garage unlocks. Use
|
|
||||||
[Locker Usage Guide](/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];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
@ -1,136 +0,0 @@
|
|||||||
# Phone Usage Guide
|
|
||||||
|
|
||||||
The phone module stores contacts, messages, and emails for each UID. It is a
|
|
||||||
server-extension state module backed by SurrealDB.
|
|
||||||
|
|
||||||
## Storage Model
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"contacts": ["76561198000000000", "field_commander"],
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"id": "phone-message:sender:receiver:1",
|
|
||||||
"from": "sender",
|
|
||||||
"to": "receiver",
|
|
||||||
"message": "Text body",
|
|
||||||
"timestamp": 123.45,
|
|
||||||
"read": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"emails": [
|
|
||||||
{
|
|
||||||
"id": "phone-email:sender:receiver:2",
|
|
||||||
"from": "sender",
|
|
||||||
"to": "receiver",
|
|
||||||
"subject": "Subject",
|
|
||||||
"body": "Email body",
|
|
||||||
"timestamp": 123.45,
|
|
||||||
"read": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Rules validated by the Rust service:
|
|
||||||
|
|
||||||
- UID arguments cannot be empty.
|
|
||||||
- Message and email bodies cannot be empty.
|
|
||||||
- Empty email subjects become `No subject`.
|
|
||||||
- Player messages and emails cannot target `field_commander`.
|
|
||||||
- `field_commander` can send messages or emails to players.
|
|
||||||
- Deleting a message or email removes it only from the requesting UID's index.
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
| Command | Arguments | Returns |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `phone:init` | `uid` | Full phone payload. |
|
|
||||||
| `phone:contacts:list` | `uid` | Contact UID array. |
|
|
||||||
| `phone:contacts:add` | `uid`, `contact_uid` | `true` or `false`. |
|
|
||||||
| `phone:contacts:remove` | `uid`, `contact_uid` | `true` or `false`. |
|
|
||||||
| `phone:messages:list` | `uid` | Message array. |
|
|
||||||
| `phone:messages:thread` | `uid`, `other_uid` | Message array for both participants. |
|
|
||||||
| `phone:messages:send` | `from_uid`, `to_uid`, `message`, `timestamp` | Message JSON. |
|
|
||||||
| `phone:messages:mark_read` | `uid`, `message_id` | `true` or `false`. |
|
|
||||||
| `phone:messages:delete` | `uid`, `message_id` | `true` or `false`. |
|
|
||||||
| `phone:emails:list` | `uid` | Email array. |
|
|
||||||
| `phone:emails:send` | `from_uid`, `to_uid`, `subject`, `body`, `timestamp` | Email JSON. |
|
|
||||||
| `phone:emails:mark_read` | `uid`, `email_id` | `true` or `false`. |
|
|
||||||
| `phone:emails:delete` | `uid`, `email_id` | `true` or `false`. |
|
|
||||||
| `phone:remove` | `uid` | `OK`. |
|
|
||||||
|
|
||||||
## Initialize Phone State
|
|
||||||
|
|
||||||
`phone:init` creates phone state if needed and seeds self-contact plus
|
|
||||||
`field_commander`.
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _result = "forge_server" callExtension ["phone:init", [getPlayerUID player]];
|
|
||||||
private _payload = _result select 0;
|
|
||||||
|
|
||||||
if (_payload find "Error:" == 0) exitWith {
|
|
||||||
systemChat format ["Phone init failed: %1", _payload];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _phone = fromJSON _payload;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Send a Message
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _timestamp = str diag_tickTime;
|
|
||||||
|
|
||||||
private _result = "forge_server" callExtension ["phone:messages:send", [
|
|
||||||
getPlayerUID player,
|
|
||||||
_targetUid,
|
|
||||||
"Move to checkpoint Alpha.",
|
|
||||||
_timestamp
|
|
||||||
]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Read a Conversation
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _result = "forge_server" callExtension ["phone:messages:thread", [
|
|
||||||
getPlayerUID player,
|
|
||||||
_otherUid
|
|
||||||
]];
|
|
||||||
|
|
||||||
private _messages = fromJSON (_result select 0);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Send an Email
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _result = "forge_server" callExtension ["phone:emails:send", [
|
|
||||||
getPlayerUID player,
|
|
||||||
_targetUid,
|
|
||||||
"Supply Request",
|
|
||||||
"Requesting resupply at grid 123456.",
|
|
||||||
str diag_tickTime
|
|
||||||
]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Mark and Delete Records
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
"forge_server" callExtension ["phone:messages:mark_read", [
|
|
||||||
getPlayerUID player,
|
|
||||||
_messageId
|
|
||||||
]];
|
|
||||||
|
|
||||||
"forge_server" callExtension ["phone:emails:delete", [
|
|
||||||
getPlayerUID player,
|
|
||||||
_emailId
|
|
||||||
]];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
```sqf
|
|
||||||
private _payload = (_result select 0);
|
|
||||||
if (_payload find "Error:" == 0) then {
|
|
||||||
systemChat format ["Phone error: %1", _payload];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
title: Client Addons
|
|
||||||
icon: i-lucide-monitor-smartphone
|
|
||||||
@ -1,125 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@ -1,100 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@ -1,114 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@ -1,106 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@ -1,172 +0,0 @@
|
|||||||
---
|
|
||||||
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
1
docus/dist/200.html
vendored
@ -1 +0,0 @@
|
|||||||
<!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
1
docus/dist/404.html
vendored
@ -1 +0,0 @@
|
|||||||
<!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>
|
|
||||||
1
docus/dist/__nuxt_content/docs/sql_dump.txt
vendored
1
docus/dist/__nuxt_content/docs/sql_dump.txt
vendored
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/-nV3O3Hd.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/2h0Q0JkN.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/4YIHfatS.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/AFG10c_a.js
vendored
File diff suppressed because one or more lines are too long
9
docus/dist/_nuxt/B2gqyXxK.js
vendored
9
docus/dist/_nuxt/B2gqyXxK.js
vendored
@ -1,9 +0,0 @@
|
|||||||
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
41
docus/dist/_nuxt/B3fabVUf.js
vendored
File diff suppressed because one or more lines are too long
1
docus/dist/_nuxt/B4xqMpdT.js
vendored
1
docus/dist/_nuxt/B4xqMpdT.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/B6bUjJBd.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/B7V4_Oof.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/B91fwRQf.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BKwruWNQ.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BLnzyn6S.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BN_7HF1G.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BOynLmEf.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BRj7a3jo.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BXokzvm8.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BeOsfPZ5.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BePYYsBE.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BecbEIqG.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BfXwXGCj.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/Bkurqz2d.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BlIgAVvl.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BlxJeUnt.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BnHu5O7o.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BnW3dU0n.js
vendored
File diff suppressed because one or more lines are too long
1
docus/dist/_nuxt/Bnc4fItI.js
vendored
1
docus/dist/_nuxt/Bnc4fItI.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BosS-MS4.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/Bqoc_FpL.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BscxgZ9w.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/BvtDsFck.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/Bzhn7Nlk.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/C-sCiTCq.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/C0AA5UMg.js
vendored
File diff suppressed because one or more lines are too long
1
docus/dist/_nuxt/C1fSKR9D.js
vendored
1
docus/dist/_nuxt/C1fSKR9D.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/C2GbCjp9.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/C3Ly8WAT.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/C5B99YjC.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/C6Z5zZ8h.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/C8-8Pk2V.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/C8-Mg-oX.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/C8791Vr7.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/C919_JS_.js
vendored
@ -1 +0,0 @@
|
|||||||
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
1
docus/dist/_nuxt/CArNbJ6I.js
vendored
@ -1 +0,0 @@
|
|||||||
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
Loading…
x
Reference in New Issue
Block a user