Add player guide and roster member map focus
- add player-facing docs and synced screenshots - let CAD roster entries center the map on a member - refresh garage and economy UI bridges and docs
@ -172,6 +172,14 @@ switch (_event) do {
|
|||||||
|
|
||||||
GVAR(CADUIBridge) call ["focusGroup", [_groupID]];
|
GVAR(CADUIBridge) call ["focusGroup", [_groupID]];
|
||||||
};
|
};
|
||||||
|
case "cad::members::focus": {
|
||||||
|
private _uid = "";
|
||||||
|
if (_data isEqualType createHashMap) then {
|
||||||
|
_uid = _data getOrDefault ["uid", ""];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["focusMember", [_uid]];
|
||||||
|
};
|
||||||
case "cad::tasks::focus": {
|
case "cad::tasks::focus": {
|
||||||
private _taskID = "";
|
private _taskID = "";
|
||||||
if (_data isEqualType createHashMap) then {
|
if (_data isEqualType createHashMap) then {
|
||||||
|
|||||||
@ -319,6 +319,33 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
ctrlMapAnimCommit _mapCtrl;
|
ctrlMapAnimCommit _mapCtrl;
|
||||||
true
|
true
|
||||||
}],
|
}],
|
||||||
|
["focusMember", compileFinal {
|
||||||
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "") exitWith { false };
|
||||||
|
if (isNil QGVAR(CADRepository)) exitWith { false };
|
||||||
|
|
||||||
|
private _groups = GVAR(CADRepository) getOrDefault ["groups", []];
|
||||||
|
private _position = [];
|
||||||
|
{
|
||||||
|
private _members = _x getOrDefault ["members", []];
|
||||||
|
private _memberIndex = _members findIf { (_x getOrDefault ["uid", ""]) isEqualTo _uid };
|
||||||
|
if (_memberIndex >= 0) exitWith {
|
||||||
|
_position = (_members # _memberIndex) getOrDefault ["position", []];
|
||||||
|
};
|
||||||
|
} forEach _groups;
|
||||||
|
|
||||||
|
if !(_position isEqualType []) exitWith { false };
|
||||||
|
if ((count _position) < 2) exitWith { false };
|
||||||
|
|
||||||
|
private _mapCtrl = _self call ["getMapControl", []];
|
||||||
|
if (isNull _mapCtrl) exitWith { false };
|
||||||
|
|
||||||
|
private _targetPosition = [_position # 0, _position # 1, 0];
|
||||||
|
_mapCtrl ctrlMapAnimAdd [0.35, ctrlMapScale _mapCtrl, _targetPosition];
|
||||||
|
ctrlMapAnimCommit _mapCtrl;
|
||||||
|
true
|
||||||
|
}],
|
||||||
["focusTask", compileFinal {
|
["focusTask", compileFinal {
|
||||||
params [["_taskID", "", [""]]];
|
params [["_taskID", "", [""]]];
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ window.cadTasks = {
|
|||||||
selectedDispatchGroupId: "",
|
selectedDispatchGroupId: "",
|
||||||
selectedDispatchTaskId: "",
|
selectedDispatchTaskId: "",
|
||||||
selectedDispatchRequestId: "",
|
selectedDispatchRequestId: "",
|
||||||
|
selectedRosterMemberUid: "",
|
||||||
focusStatusTimer: null,
|
focusStatusTimer: null,
|
||||||
requestModalType: "",
|
requestModalType: "",
|
||||||
statuses: [
|
statuses: [
|
||||||
@ -431,6 +432,19 @@ window.cadTasks = {
|
|||||||
this.selectedDispatchGroupId = "";
|
this.selectedDispatchGroupId = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.selectedRosterMemberUid) {
|
||||||
|
const memberExists = this.groups.some((group) =>
|
||||||
|
this.normalizeCollection(group.members).some(
|
||||||
|
(member) =>
|
||||||
|
(member.uid || "") === this.selectedRosterMemberUid,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!memberExists) {
|
||||||
|
this.selectedRosterMemberUid = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.selectedDispatchTaskId &&
|
this.selectedDispatchTaskId &&
|
||||||
!this.contracts.some((task) => {
|
!this.contracts.some((task) => {
|
||||||
@ -746,8 +760,18 @@ window.cadTasks = {
|
|||||||
const requestActionLabel = this.isDispatchMode()
|
const requestActionLabel = this.isDispatchMode()
|
||||||
? "Close"
|
? "Close"
|
||||||
: "Cancel";
|
: "Cancel";
|
||||||
|
const requestID = request.requestId || "";
|
||||||
|
const isSelected =
|
||||||
|
requestID === this.selectedDispatchRequestId;
|
||||||
return `
|
return `
|
||||||
<div class="task-card cad-request-card">
|
<div
|
||||||
|
class="task-card cad-request-card dispatch-map-card ${isSelected ? "is-selected" : ""}"
|
||||||
|
data-request-id="${requestID}"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
onclick="window.cadTasks.focusRequest('${requestID}')"
|
||||||
|
onkeydown="if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); window.cadTasks.focusRequest('${requestID}'); }"
|
||||||
|
>
|
||||||
<div class="task-card-header">
|
<div class="task-card-header">
|
||||||
<strong>${request.title || this.getRequestTypeLabel(request.type || "")}</strong>
|
<strong>${request.title || this.getRequestTypeLabel(request.type || "")}</strong>
|
||||||
<span class="task-type">${(request.priority || "priority").replaceAll("_", " ")}</span>
|
<span class="task-type">${(request.priority || "priority").replaceAll("_", " ")}</span>
|
||||||
@ -760,7 +784,7 @@ window.cadTasks = {
|
|||||||
${
|
${
|
||||||
canClose
|
canClose
|
||||||
? `<div class="task-action-row">
|
? `<div class="task-action-row">
|
||||||
<button type="button" class="task-secondary-btn" onclick="window.cadTasks.closeSupportRequest('${request.requestId || ""}')">${requestActionLabel}</button>
|
<button type="button" class="task-secondary-btn" onclick="event.stopPropagation(); window.cadTasks.closeSupportRequest('${requestID}')">${requestActionLabel}</button>
|
||||||
</div>`
|
</div>`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
@ -875,6 +899,7 @@ window.cadTasks = {
|
|||||||
this.selectedDispatchGroupId = groupID;
|
this.selectedDispatchGroupId = groupID;
|
||||||
this.selectedDispatchTaskId = "";
|
this.selectedDispatchTaskId = "";
|
||||||
this.selectedDispatchRequestId = "";
|
this.selectedDispatchRequestId = "";
|
||||||
|
this.selectedRosterMemberUid = "";
|
||||||
const statusMessage = `Centering map on ${group.callsign || group.groupId || "group"}...`;
|
const statusMessage = `Centering map on ${group.callsign || group.groupId || "group"}...`;
|
||||||
this.setStatus(statusMessage, "info");
|
this.setStatus(statusMessage, "info");
|
||||||
this.clearFocusStatusSoon(statusMessage);
|
this.clearFocusStatusSoon(statusMessage);
|
||||||
@ -883,6 +908,51 @@ window.cadTasks = {
|
|||||||
});
|
});
|
||||||
this.render();
|
this.render();
|
||||||
},
|
},
|
||||||
|
focusMember(uid) {
|
||||||
|
let selectedMember = null;
|
||||||
|
|
||||||
|
this.groups.some((group) =>
|
||||||
|
this.normalizeCollection(group.members).some((member) => {
|
||||||
|
if ((member.uid || "") !== uid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedMember = member;
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!selectedMember) {
|
||||||
|
this.setStatus(
|
||||||
|
"Selected group member is no longer available.",
|
||||||
|
"error",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const position = Array.isArray(selectedMember.position)
|
||||||
|
? selectedMember.position
|
||||||
|
: [];
|
||||||
|
if (position.length < 2) {
|
||||||
|
this.setStatus(
|
||||||
|
"Selected group member has no map position.",
|
||||||
|
"error",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedRosterMemberUid = uid;
|
||||||
|
this.selectedDispatchGroupId = "";
|
||||||
|
this.selectedDispatchTaskId = "";
|
||||||
|
this.selectedDispatchRequestId = "";
|
||||||
|
const statusMessage = `Centering map on ${selectedMember.name || "group member"}...`;
|
||||||
|
this.setStatus(statusMessage, "info");
|
||||||
|
this.clearFocusStatusSoon(statusMessage);
|
||||||
|
window.mapUI.sendEvent("cad::members::focus", {
|
||||||
|
uid: uid,
|
||||||
|
});
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
focusTask(taskID) {
|
focusTask(taskID) {
|
||||||
const task = this.contracts.find((entry) => {
|
const task = this.contracts.find((entry) => {
|
||||||
const entryTaskID = entry.taskId || entry.taskID || "";
|
const entryTaskID = entry.taskId || entry.taskID || "";
|
||||||
@ -899,6 +969,7 @@ window.cadTasks = {
|
|||||||
this.selectedDispatchTaskId = taskID;
|
this.selectedDispatchTaskId = taskID;
|
||||||
this.selectedDispatchGroupId = "";
|
this.selectedDispatchGroupId = "";
|
||||||
this.selectedDispatchRequestId = "";
|
this.selectedDispatchRequestId = "";
|
||||||
|
this.selectedRosterMemberUid = "";
|
||||||
const statusMessage = `Centering map on ${task.title || taskID}...`;
|
const statusMessage = `Centering map on ${task.title || taskID}...`;
|
||||||
this.setStatus(statusMessage, "info");
|
this.setStatus(statusMessage, "info");
|
||||||
this.clearFocusStatusSoon(statusMessage);
|
this.clearFocusStatusSoon(statusMessage);
|
||||||
@ -927,6 +998,7 @@ window.cadTasks = {
|
|||||||
this.selectedDispatchRequestId = requestID;
|
this.selectedDispatchRequestId = requestID;
|
||||||
this.selectedDispatchGroupId = "";
|
this.selectedDispatchGroupId = "";
|
||||||
this.selectedDispatchTaskId = "";
|
this.selectedDispatchTaskId = "";
|
||||||
|
this.selectedRosterMemberUid = "";
|
||||||
const statusMessage = `Centering map on ${request.title || requestID}...`;
|
const statusMessage = `Centering map on ${request.title || requestID}...`;
|
||||||
this.setStatus(statusMessage, "info");
|
this.setStatus(statusMessage, "info");
|
||||||
this.clearFocusStatusSoon(statusMessage);
|
this.clearFocusStatusSoon(statusMessage);
|
||||||
@ -1067,9 +1139,17 @@ window.cadTasks = {
|
|||||||
);
|
);
|
||||||
const isAssignedToLeader =
|
const isAssignedToLeader =
|
||||||
this.isLeader() && assignedGroupId === currentGroupId;
|
this.isLeader() && assignedGroupId === currentGroupId;
|
||||||
|
const isSelected = taskId === this.selectedDispatchTaskId;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="task-card" data-task-id="${taskId}">
|
<div
|
||||||
|
class="task-card dispatch-map-card ${isSelected ? "is-selected" : ""}"
|
||||||
|
data-task-id="${taskId}"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
onclick="window.cadTasks.focusTask('${taskId}')"
|
||||||
|
onkeydown="if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); window.cadTasks.focusTask('${taskId}'); }"
|
||||||
|
>
|
||||||
<div class="task-card-header">
|
<div class="task-card-header">
|
||||||
<strong>${task.title || taskId}</strong>
|
<strong>${task.title || taskId}</strong>
|
||||||
<span class="task-type">${this.formatTypeLabel(task)}</span>
|
<span class="task-type">${this.formatTypeLabel(task)}</span>
|
||||||
@ -1082,8 +1162,8 @@ window.cadTasks = {
|
|||||||
${
|
${
|
||||||
isAssignedToLeader && assignmentState === "assigned"
|
isAssignedToLeader && assignmentState === "assigned"
|
||||||
? `<div class="task-action-row">
|
? `<div class="task-action-row">
|
||||||
<button type="button" class="task-accept-btn" onclick="window.cadTasks.acknowledgeTask('${taskId}')">Acknowledge</button>
|
<button type="button" class="task-accept-btn" onclick="event.stopPropagation(); window.cadTasks.acknowledgeTask('${taskId}')">Acknowledge</button>
|
||||||
<button type="button" class="task-secondary-btn" onclick="window.cadTasks.declineTask('${taskId}')">Decline</button>
|
<button type="button" class="task-secondary-btn" onclick="event.stopPropagation(); window.cadTasks.declineTask('${taskId}')">Decline</button>
|
||||||
</div>`
|
</div>`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
@ -1177,9 +1257,19 @@ window.cadTasks = {
|
|||||||
const leaderBadge = member.isLeader
|
const leaderBadge = member.isLeader
|
||||||
? '<span class="roster-leader-badge">Leader</span>'
|
? '<span class="roster-leader-badge">Leader</span>'
|
||||||
: "";
|
: "";
|
||||||
|
const memberUid = member.uid || "";
|
||||||
|
const isSelected =
|
||||||
|
memberUid && memberUid === this.selectedRosterMemberUid;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="task-card roster-member-card" data-member-id="${member.uid || ""}">
|
<div
|
||||||
|
class="task-card roster-member-card dispatch-map-group-card ${isSelected ? "is-selected" : ""}"
|
||||||
|
data-member-id="${memberUid}"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
onclick="window.cadTasks.focusMember('${memberUid}')"
|
||||||
|
onkeydown="if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); window.cadTasks.focusMember('${memberUid}'); }"
|
||||||
|
>
|
||||||
<div class="task-card-header">
|
<div class="task-card-header">
|
||||||
<strong>${member.name || "Unknown Operator"}</strong>
|
<strong>${member.name || "Unknown Operator"}</strong>
|
||||||
<span class="task-type">${lifeState}</span>
|
<span class="task-type">${lifeState}</span>
|
||||||
|
|||||||
@ -63,6 +63,11 @@ switch (_event) do {
|
|||||||
GVAR(GarageActionService) call ["handleRepairRequest", [_data]];
|
GVAR(GarageActionService) call ["handleRepairRequest", [_data]];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
case "garage::vehicle::rearm::request": {
|
||||||
|
if !(isNil QGVAR(GarageActionService)) then {
|
||||||
|
GVAR(GarageActionService) call ["handleRearmRequest", [_data]];
|
||||||
|
};
|
||||||
|
};
|
||||||
case "garage::refresh": {
|
case "garage::refresh": {
|
||||||
if !(isNil QGVAR(GarageUIBridge)) then {
|
if !(isNil QGVAR(GarageUIBridge)) then {
|
||||||
GVAR(GarageUIBridge) call ["refreshGarage", []];
|
GVAR(GarageUIBridge) call ["refreshGarage", []];
|
||||||
|
|||||||
@ -8,8 +8,8 @@
|
|||||||
* Public: No
|
* Public: No
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
* Initializes the garage action service for retrieve, store, refuel, and
|
* Initializes the garage action service for retrieve, store, refuel, rearm,
|
||||||
* repair world actions.
|
* and repair world actions.
|
||||||
*
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* None
|
* None
|
||||||
@ -184,6 +184,17 @@ GVAR(GarageActionServiceBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
_self call ["refreshAfterService", []];
|
_self call ["refreshAfterService", []];
|
||||||
true
|
true
|
||||||
}],
|
}],
|
||||||
|
["handleRearmRequest", compileFinal {
|
||||||
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _vehicle = _self call ["resolveServiceVehicle", [_data, "rearm"]];
|
||||||
|
if (isNull _vehicle) exitWith { false };
|
||||||
|
|
||||||
|
[SRPC(economy,RearmService), [_vehicle, player, -1]] call CFUNC(serverEvent);
|
||||||
|
_self call ["sendServiceResult", ["rearm", true, "Rearm request sent. Billing result will appear as a notification."]];
|
||||||
|
_self call ["refreshAfterService", []];
|
||||||
|
true
|
||||||
|
}],
|
||||||
["handleActionResponse", compileFinal {
|
["handleActionResponse", compileFinal {
|
||||||
params [["_payload", createHashMap, [createHashMap]]];
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
|||||||
@ -31,6 +31,10 @@
|
|||||||
return bridge.send("garage::vehicle::repair::request", payload);
|
return bridge.send("garage::vehicle::repair::request", payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestRearm(payload) {
|
||||||
|
return bridge.send("garage::vehicle::rearm::request", payload);
|
||||||
|
}
|
||||||
|
|
||||||
function notifyReady() {
|
function notifyReady() {
|
||||||
return bridge.ready({ loaded: true });
|
return bridge.ready({ loaded: true });
|
||||||
}
|
}
|
||||||
@ -108,6 +112,7 @@
|
|||||||
receive: bridge.receive,
|
receive: bridge.receive,
|
||||||
requestClose,
|
requestClose,
|
||||||
requestRefresh,
|
requestRefresh,
|
||||||
|
requestRearm,
|
||||||
requestRefuel,
|
requestRefuel,
|
||||||
requestRepair,
|
requestRepair,
|
||||||
requestRetrieve,
|
requestRetrieve,
|
||||||
|
|||||||
@ -353,6 +353,7 @@
|
|||||||
!isStored &&
|
!isStored &&
|
||||||
Number(currentSelection.health || 0) < 0.999 &&
|
Number(currentSelection.health || 0) < 0.999 &&
|
||||||
!isBusy;
|
!isBusy;
|
||||||
|
const canRearm = !isStored && !isBusy;
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
"section",
|
"section",
|
||||||
@ -500,6 +501,20 @@
|
|||||||
type: "button",
|
type: "button",
|
||||||
className:
|
className:
|
||||||
"garage-btn garage-btn-secondary",
|
"garage-btn garage-btn-secondary",
|
||||||
|
disabled: !canRearm,
|
||||||
|
onClick: () =>
|
||||||
|
actions.requestRearmSelected(),
|
||||||
|
},
|
||||||
|
pendingAction === "rearm"
|
||||||
|
? "Rearming..."
|
||||||
|
: "Rearm",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className:
|
||||||
|
"garage-btn garage-btn-secondary garage-action-refresh",
|
||||||
disabled: isBusy,
|
disabled: isBusy,
|
||||||
onClick: () => actions.refreshGarage(),
|
onClick: () => actions.refreshGarage(),
|
||||||
},
|
},
|
||||||
@ -512,10 +527,10 @@
|
|||||||
isStored
|
isStored
|
||||||
? session.spawnBlocked
|
? session.spawnBlocked
|
||||||
? "The garage spawn lane is currently blocked."
|
? "The garage spawn lane is currently blocked."
|
||||||
: "Retrieve this stored vehicle into the active spawn lane before refuel or repair service."
|
: "Retrieve this stored vehicle into the active spawn lane before refuel, rearm, or repair service."
|
||||||
: currentSelection.isEmpty === false
|
: currentSelection.isEmpty === false
|
||||||
? "Only empty nearby vehicles can be stored."
|
? "Only empty nearby vehicles can be stored."
|
||||||
: "Store this nearby vehicle or request organization-billed refuel and repair service.",
|
: "Store this nearby vehicle or request organization-billed refuel, rearm, and repair service.",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
|
|||||||
@ -223,6 +223,33 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestRearmSelected() {
|
||||||
|
const selectedEntry = getSelectedEntry();
|
||||||
|
if (!selectedEntry || selectedEntry.entryKind !== "nearby") {
|
||||||
|
showNotice("error", "Select a nearby vehicle to rearm.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bridge = GarageApp.bridge;
|
||||||
|
if (!bridge || typeof bridge.requestRearm !== "function") {
|
||||||
|
showNotice("error", "Garage rearm bridge is unavailable.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.startAction("rearm");
|
||||||
|
const sent = bridge.requestRearm({
|
||||||
|
netId: selectedEntry.netId || "",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!sent) {
|
||||||
|
store.finishAction();
|
||||||
|
showNotice("error", "Garage rearm bridge is unavailable.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
GarageApp.actions = {
|
GarageApp.actions = {
|
||||||
showNotice,
|
showNotice,
|
||||||
closeGarage,
|
closeGarage,
|
||||||
@ -232,6 +259,7 @@
|
|||||||
selectCategory,
|
selectCategory,
|
||||||
selectEntry,
|
selectEntry,
|
||||||
getSelectedEntry,
|
getSelectedEntry,
|
||||||
|
requestRearmSelected,
|
||||||
requestRefuelSelected,
|
requestRefuelSelected,
|
||||||
requestRepairSelected,
|
requestRepairSelected,
|
||||||
requestRetrieveSelected,
|
requestRetrieveSelected,
|
||||||
|
|||||||
@ -217,6 +217,10 @@ button:disabled {
|
|||||||
gap: 0.65rem;
|
gap: 0.65rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.garage-action-refresh {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
.garage-footer-bar {
|
.garage-footer-bar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-top: 1px solid rgb(18 54 93 / 0.1);
|
border-top: 1px solid rgb(18 54 93 / 0.1);
|
||||||
|
|||||||
@ -99,7 +99,8 @@ GVAR(GroupRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["uid", _memberUid],
|
["uid", _memberUid],
|
||||||
["name", name _x],
|
["name", name _x],
|
||||||
["lifeState", _memberState],
|
["lifeState", _memberState],
|
||||||
["isLeader", _x isEqualTo _leader]
|
["isLeader", _x isEqualTo _leader],
|
||||||
|
["position", getPosATL _x]
|
||||||
]);
|
]);
|
||||||
} forEach _members;
|
} forEach _members;
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ refueling sessions, medical spawn occupancy, respawn placement, and death
|
|||||||
inventory handling.
|
inventory handling.
|
||||||
|
|
||||||
Current stores cover fuel tracking, medical service behavior, and service
|
Current stores cover fuel tracking, medical service behavior, and service
|
||||||
charges such as repairs.
|
charges such as repairs and rearming.
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
- `forge_server_main`
|
- `forge_server_main`
|
||||||
@ -27,8 +27,9 @@ Note: Bank and Org are runtime-only dependencies (not compile-time requiredAddon
|
|||||||
respawn placement, death inventory handling, and body-bag transfer. Medical
|
respawn placement, death inventory handling, and body-bag transfer. Medical
|
||||||
charges use player bank/cash first, then organization funds with repayable
|
charges use player bank/cash first, then organization funds with repayable
|
||||||
member debt only when the player cannot cover the service.
|
member debt only when the player cannot cover the service.
|
||||||
- `fnc_initSEconomyStore.sqf` handles organization-funded service charges and
|
- `fnc_initSEconomyStore.sqf` handles organization-funded service charges,
|
||||||
repairs. Repairs only apply after the organization charge succeeds. The
|
repairs, and rearming. Vehicle services only apply after the organization
|
||||||
|
charge succeeds. The
|
||||||
shared org-charge helper can also record member debt for medical fallback.
|
shared org-charge helper can also record member debt for medical fallback.
|
||||||
|
|
||||||
## Event Surface
|
## Event Surface
|
||||||
@ -50,6 +51,16 @@ Repair service requests use:
|
|||||||
|
|
||||||
`_cost` is optional. Passing `-1` uses the configured service repair cost.
|
`_cost` is optional. Passing `-1` uses the configured service repair cost.
|
||||||
|
|
||||||
|
Rearm service requests use:
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
[QEGVAR(economy,RearmService), [_target, _unit, _cost]] call CBA_fnc_serverEvent;
|
||||||
|
```
|
||||||
|
|
||||||
|
`_cost` is optional. Passing `-1` uses the configured service rearm cost.
|
||||||
|
`setVehicleAmmo` has global effects, but only adds ammo to local turrets, so
|
||||||
|
the ammo reset is broadcast after billing succeeds.
|
||||||
|
|
||||||
Garage refuel service requests use:
|
Garage refuel service requests use:
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
@ -70,7 +81,7 @@ Fuel and repair services are organization-funded:
|
|||||||
`commit = true`, and member service charging enabled.
|
`commit = true`, and member service charging enabled.
|
||||||
4. Send the returned organization patch to online members.
|
4. Send the returned organization patch to online members.
|
||||||
5. If the charge fails, do not complete the service. Refueling rolls the target
|
5. If the charge fails, do not complete the service. Refueling rolls the target
|
||||||
back to its starting fuel level; repairs are not applied.
|
back to its starting fuel level; repairs and rearming are not applied.
|
||||||
|
|
||||||
Direct refuel service requests, such as those from the garage UI, calculate
|
Direct refuel service requests, such as those from the garage UI, calculate
|
||||||
the missing fuel from `fuelCapacity`, charge the organization, and fill the
|
the missing fuel from `fuelCapacity`, charge the organization, and fill the
|
||||||
|
|||||||
@ -33,6 +33,11 @@ if (isNil QGVAR(SEconomyStore)) then { call FUNC(initSEconomyStore); };
|
|||||||
GVAR(SEconomyStore) call ["repair", [_target, _unit, _cost]];
|
GVAR(SEconomyStore) call ["repair", [_target, _unit, _cost]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(RearmService), {
|
||||||
|
params ["_target", "_unit", ["_cost", -1, [0]]];
|
||||||
|
GVAR(SEconomyStore) call ["rearm", [_target, _unit, _cost]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(RefuelService), {
|
[QGVAR(RefuelService), {
|
||||||
params ["_target", "_unit"];
|
params ["_target", "_unit"];
|
||||||
GVAR(FEconomyStore) call ["refuel", [_target, _unit]];
|
GVAR(FEconomyStore) call ["refuel", [_target, _unit]];
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
* File: fnc_initSEconomyStore.sqf
|
* File: fnc_initSEconomyStore.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2025-12-20
|
* Date: 2025-12-20
|
||||||
* Last Update: 2026-05-15
|
* Last Update: 2026-05-19
|
||||||
* Public: No
|
* Public: No
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
@ -27,6 +27,7 @@ GVAR(SEconomyStore) = createHashMapObject [[
|
|||||||
["#type", "IServiceEconomy"],
|
["#type", "IServiceEconomy"],
|
||||||
["#create", {
|
["#create", {
|
||||||
GVAR(ServiceRepairCost) = 500;
|
GVAR(ServiceRepairCost) = 500;
|
||||||
|
GVAR(ServiceRearmCost) = 500;
|
||||||
["INFO", "Service Store Initialized!", nil, nil] call EFUNC(common,log);
|
["INFO", "Service Store Initialized!", nil, nil] call EFUNC(common,log);
|
||||||
}],
|
}],
|
||||||
["notify", {
|
["notify", {
|
||||||
@ -158,6 +159,22 @@ GVAR(SEconomyStore) = createHashMapObject [[
|
|||||||
_self call ["notify", [_unit, "info", "Repair", format ["Repair complete. Organization charged $%1.", [_repairCost] call EFUNC(common,formatNumber)]]];
|
_self call ["notify", [_unit, "info", "Repair", format ["Repair complete. Organization charged $%1.", [_repairCost] call EFUNC(common,formatNumber)]]];
|
||||||
true
|
true
|
||||||
}],
|
}],
|
||||||
|
["rearm", {
|
||||||
|
params [["_target", objNull, [objNull]], ["_unit", objNull, [objNull]], ["_cost", -1, [0]]];
|
||||||
|
|
||||||
|
if (isNull _target || { isNull _unit }) exitWith { false };
|
||||||
|
|
||||||
|
private _rearmCost = [_cost, GVAR(ServiceRearmCost)] select (_cost < 0);
|
||||||
|
private _charge = _self call ["chargeOrg", [_unit, _rearmCost, "Rearm"]];
|
||||||
|
if !(_charge getOrDefault ["success", false]) exitWith {
|
||||||
|
_self call ["notify", [_unit, "danger", "Rearm", _charge getOrDefault ["message", "Organization funds cannot cover this rearm."]]];
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
[_target, 1] remoteExecCall ["setVehicleAmmo", 0];
|
||||||
|
_self call ["notify", [_unit, "info", "Rearm", format ["Rearm complete. Organization charged $%1.", [_rearmCost] call EFUNC(common,formatNumber)]]];
|
||||||
|
true
|
||||||
|
}],
|
||||||
["init", {}]
|
["init", {}]
|
||||||
]];
|
]];
|
||||||
|
|
||||||
|
|||||||
@ -37,6 +37,16 @@ assignments.
|
|||||||
- group status, role, and profile requests
|
- group status, role, and profile requests
|
||||||
- map focus actions
|
- map focus actions
|
||||||
|
|
||||||
|
## Map Focus Behavior
|
||||||
|
|
||||||
|
CAD list entries can drive the native map position without duplicating map
|
||||||
|
logic in the browser UI. In operations mode, assigned or accepted task cards,
|
||||||
|
roster member cards, and support request cards send focus events. In dispatch
|
||||||
|
map mode, group, contract, and support request cards use the same focus path.
|
||||||
|
|
||||||
|
Task and support request focus uses the stored record position. Roster member
|
||||||
|
focus uses the member position included in the hydrated group roster.
|
||||||
|
|
||||||
## Browser Events
|
## Browser Events
|
||||||
|
|
||||||
| Event | Client behavior |
|
| Event | Client behavior |
|
||||||
@ -58,6 +68,7 @@ assignments.
|
|||||||
| `cad::groups::role` | Update group role. |
|
| `cad::groups::role` | Update group role. |
|
||||||
| `cad::groups::profile` | Update status and role together. |
|
| `cad::groups::profile` | Update status and role together. |
|
||||||
| `cad::groups::focus` | Center map on a group. |
|
| `cad::groups::focus` | Center map on a group. |
|
||||||
|
| `cad::members::focus` | Center map on a group member. |
|
||||||
| `cad::tasks::focus` | Center map on a task. |
|
| `cad::tasks::focus` | Center map on a task. |
|
||||||
| `cad::requests::focus` | Center map on a support request. |
|
| `cad::requests::focus` | Center map on a support request. |
|
||||||
| `map::zoomIn` | Zoom native map in. |
|
| `map::zoomIn` | Zoom native map in. |
|
||||||
|
|||||||
@ -56,21 +56,21 @@ is finalized and spawned onto the resolved lane.
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `garage::hydrate` | Initial vehicle and session payload. |
|
| `garage::hydrate` | Initial vehicle and session payload. |
|
||||||
| `garage::sync` | Refreshed vehicle payload. |
|
| `garage::sync` | Refreshed vehicle payload. |
|
||||||
| `garage::service::success` | Browser notice for accepted refuel/repair requests. |
|
| `garage::service::success` | Browser notice for accepted refuel/rearm/repair requests. |
|
||||||
| `garage::service::failure` | Browser notice for rejected refuel/repair requests. |
|
| `garage::service::failure` | Browser notice for rejected refuel/rearm/repair requests. |
|
||||||
|
|
||||||
Server action responses are handled by the action service and notification
|
Server action responses are handled by the action service and notification
|
||||||
flow.
|
flow.
|
||||||
|
|
||||||
## Vehicle Service
|
## Vehicle Service
|
||||||
|
|
||||||
The selected vehicle detail panel includes refuel and repair actions for nearby
|
The selected vehicle detail panel includes refuel, rearm, and repair actions for nearby
|
||||||
world vehicles. Stored records must be retrieved first because server economy
|
world vehicles. Stored records must be retrieved first because server economy
|
||||||
services operate on live vehicle objects, not stored garage records.
|
services operate on live vehicle objects, not stored garage records.
|
||||||
|
|
||||||
Refuel requests use the server economy `RefuelService` event. Repair requests
|
Refuel requests use the server economy `RefuelService` event. Rearm requests
|
||||||
use the server economy `RepairService` event. Both services are billed by the
|
use `RearmService`. Repair requests use `RepairService`. These services are
|
||||||
server economy addon through organization funds.
|
billed by the server economy addon through organization funds.
|
||||||
|
|
||||||
## Mission Setup
|
## Mission Setup
|
||||||
|
|
||||||
|
|||||||
@ -45,6 +45,24 @@ The target is only repaired after the organization charge succeeds.
|
|||||||
The client garage UI forwards selected nearby vehicle repair requests through
|
The client garage UI forwards selected nearby vehicle repair requests through
|
||||||
the same event.
|
the same event.
|
||||||
|
|
||||||
|
## Rearm
|
||||||
|
|
||||||
|
Rearm is organization-funded.
|
||||||
|
|
||||||
|
Use the rearm service event:
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
[QEGVAR(economy,RearmService), [_target, _unit, _cost]] call CBA_fnc_serverEvent;
|
||||||
|
```
|
||||||
|
|
||||||
|
`_cost` is optional. Passing `-1` uses the configured service rearm cost.
|
||||||
|
The target is only rearmed after the organization charge succeeds.
|
||||||
|
`setVehicleAmmo` has global effects, but the ammo is only added to local
|
||||||
|
turrets, so the service broadcasts the ammo reset after billing succeeds.
|
||||||
|
|
||||||
|
The client garage UI forwards selected nearby vehicle rearm requests through
|
||||||
|
the same event.
|
||||||
|
|
||||||
## Medical
|
## Medical
|
||||||
|
|
||||||
Medical is player-funded first.
|
Medical is player-funded first.
|
||||||
|
|||||||
321
docs/PLAYER_GUIDE.md
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
# Player Guide
|
||||||
|
|
||||||
|
Use this guide as the player-facing overview for Forge systems. It explains
|
||||||
|
what players interact with during normal missions, how task assignment works,
|
||||||
|
and what persistent storage limits apply.
|
||||||
|
|
||||||
|
Player-guide screenshots are stored as JPG files under
|
||||||
|
`docus/public/images/player`.
|
||||||
|
|
||||||
|
## Opening Forge Interactions
|
||||||
|
|
||||||
|
Most Forge actions are opened from the actor interaction menu while standing
|
||||||
|
near a configured mission object.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Press `Tab` by default to open the custom interaction menu. Server settings or
|
||||||
|
local keybind changes may use a different key.
|
||||||
|
|
||||||
|
Known current behavior: after closing the custom interaction menu, players may
|
||||||
|
need to press `Tab` twice before it opens again. Treat this as a temporary
|
||||||
|
workaround until the interaction menu focus behavior is investigated further.
|
||||||
|
|
||||||
|
Players usually need to be within 5 meters of an interaction object such as a
|
||||||
|
bank terminal, ATM, store counter, garage terminal, or locker.
|
||||||
|
|
||||||
|
## CAD and Tasks
|
||||||
|
|
||||||
|
CAD is the main task and dispatch system. It is used for mission contracts,
|
||||||
|
group status, support requests, dispatch orders, and task assignment.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Player workflow:
|
||||||
|
|
||||||
|
1. Open CAD from the available interaction path.
|
||||||
|
2. Review available or assigned tasks.
|
||||||
|
3. If a dispatcher assigns a task to your group, the group leader must
|
||||||
|
acknowledge or decline it.
|
||||||
|
4. Once acknowledged, the task becomes active for the assigned group.
|
||||||
|
5. Complete the task objective shown by CAD, map task state, and mission
|
||||||
|
instructions.
|
||||||
|
|
||||||
|
Map focus behavior:
|
||||||
|
|
||||||
|
- Click an assigned or accepted task in the operations task board to center the
|
||||||
|
map on that task.
|
||||||
|
- Click a roster member to center the map on that player.
|
||||||
|
- Click a support request to center the map on the request location.
|
||||||
|
- Dispatch map mode supports the same focus behavior for groups, contracts,
|
||||||
|
and support requests.
|
||||||
|
|
||||||
|
Dispatch workflow:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. Open CAD with a dispatcher-enabled slot or permission.
|
||||||
|
2. Use dispatch mode to review groups, open contracts, assigned contracts, and
|
||||||
|
support requests.
|
||||||
|
3. Assign available contracts to active groups.
|
||||||
|
4. Send dispatch orders or close completed orders as needed.
|
||||||
|
5. Track group status and recent CAD activity.
|
||||||
|
|
||||||
|
Dispatch access:
|
||||||
|
|
||||||
|
- The CEO slot can administer the default organization and use CAD dispatch
|
||||||
|
permissions.
|
||||||
|
- The Dispatch slot grants CAD dispatch permissions without default
|
||||||
|
organization administration rights.
|
||||||
|
- Players who are the CEO or owner of their own organization also receive CAD
|
||||||
|
dispatch permissions.
|
||||||
|
|
||||||
|
Important task behavior:
|
||||||
|
|
||||||
|
- CAD assignment reserves a task for a group.
|
||||||
|
- The task starts after the assigned group leader acknowledges it.
|
||||||
|
- If the leader declines, the task returns to the open contract board.
|
||||||
|
- Some task timers wait for group-leader acknowledgment before counting down.
|
||||||
|
|
||||||
|
## Phone
|
||||||
|
|
||||||
|
The phone provides contacts, messages, email, and local utility apps.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Contacts
|
||||||
|
|
||||||
|
Use Contacts to keep track of other players by phone number or email address.
|
||||||
|
Adding contacts makes it easier to start messages and emails without manually
|
||||||
|
entering recipient details every time.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Messages
|
||||||
|
|
||||||
|
Messages are short player-to-player conversations.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Use Messages to:
|
||||||
|
|
||||||
|
- start or continue a conversation with a contact
|
||||||
|
- read incoming messages
|
||||||
|
- mark messages as read
|
||||||
|
- delete messages you no longer need
|
||||||
|
|
||||||
|
### Email
|
||||||
|
|
||||||
|
Email is used for longer player-to-player communication.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Use Email to:
|
||||||
|
|
||||||
|
- send a subject and body to another player
|
||||||
|
- read incoming mail
|
||||||
|
- mark email as read
|
||||||
|
- delete old email
|
||||||
|
|
||||||
|
### Local Phone Apps
|
||||||
|
|
||||||
|
Notes, calendar events, clocks, alarms, and theme preferences are local utility
|
||||||
|
features. They are saved for the local player profile and should not be treated
|
||||||
|
as shared multiplayer data.
|
||||||
|
|
||||||
|
## Bank and ATM
|
||||||
|
|
||||||
|
Bank and ATM access are separate.
|
||||||
|
|
||||||
|
Use a bank object for full banking:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- view account information
|
||||||
|
- transfer funds
|
||||||
|
- deposit earnings
|
||||||
|
- change PIN
|
||||||
|
|
||||||
|
Use an ATM for limited account access:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- PIN-gated account actions
|
||||||
|
- ATM banking workflows
|
||||||
|
- no PIN changes
|
||||||
|
|
||||||
|
If a PIN prompt appears, enter the correct PIN before attempting account
|
||||||
|
actions.
|
||||||
|
|
||||||
|
## Organizations
|
||||||
|
|
||||||
|
Players start in the default organization. A player can create a player-owned
|
||||||
|
organization only if they have `$50,000` available for the registration fee.
|
||||||
|
Organization access depends on the player's role.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Default organization:
|
||||||
|
|
||||||
|
- The `ceo` slot can administer the default organization.
|
||||||
|
- The `dispatch` slot receives CAD dispatch permissions, but does not receive
|
||||||
|
default organization administration rights.
|
||||||
|
|
||||||
|
Player-owned organizations:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- The player who created the organization is its owner or CEO.
|
||||||
|
- The owner can administer the organization, including treasury and roster
|
||||||
|
actions exposed by the organization interface.
|
||||||
|
- Organization owners can invite players, manage members, assign credit lines,
|
||||||
|
transfer funds or run payroll when funds are available, and disband the
|
||||||
|
organization.
|
||||||
|
- Organization owners can use organization funds for supported store purchases.
|
||||||
|
- Members may receive assigned credit lines, accept or decline organization
|
||||||
|
invites, and leave the organization.
|
||||||
|
- The organization CEO or owner cannot leave their own organization directly.
|
||||||
|
They must disband the organization if they want to leave it.
|
||||||
|
|
||||||
|
Organization actions are server-authoritative. If an organization action fails,
|
||||||
|
check that the player has the correct role, the player or organization has
|
||||||
|
enough funds, and the target player is eligible for the action.
|
||||||
|
|
||||||
|
## Store
|
||||||
|
|
||||||
|
Stores sell unlocks and equipment through the configured server-side catalog.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Store purchases may grant:
|
||||||
|
|
||||||
|
- items or equipment added to the locker
|
||||||
|
- matching gear unlocks in the virtual arsenal
|
||||||
|
- vehicle unlocks in the virtual garage
|
||||||
|
- other mission-configured rewards
|
||||||
|
|
||||||
|
Store purchases are server-authoritative. If a purchase succeeds, the relevant
|
||||||
|
bank, locker, virtual arsenal, virtual garage, or organization state updates
|
||||||
|
from the server.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Vehicle purchases unlock the vehicle in the virtual garage. They do not place a
|
||||||
|
physical vehicle into the player's 5-slot garage. Use the virtual garage to
|
||||||
|
spawn an unlocked vehicle, and use the garage to store or retrieve live world
|
||||||
|
vehicles.
|
||||||
|
|
||||||
|
## Locker and Virtual Arsenal
|
||||||
|
|
||||||
|
The locker is personal item storage.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Locker rules:
|
||||||
|
|
||||||
|
- Up to 25 items can be stored.
|
||||||
|
- The locker saves when the locker container is closed.
|
||||||
|
- Over-capacity storage can warn or fail depending on server handling.
|
||||||
|
|
||||||
|
The virtual arsenal is locked down. Players only see gear they have been
|
||||||
|
granted or have unlocked through systems such as the store. The virtual arsenal
|
||||||
|
is not intended to expose the full unrestricted Arma arsenal.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Garage and Virtual Garage
|
||||||
|
|
||||||
|
The garage stores physical player vehicles that have been saved from the world.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Garage rules:
|
||||||
|
|
||||||
|
- Up to 5 vehicles can be stored.
|
||||||
|
- Stored vehicles can be retrieved from a garage interaction point.
|
||||||
|
- Retrieved vehicles become live world vehicles again.
|
||||||
|
- Vehicle service actions operate on live nearby vehicles, not vehicles that
|
||||||
|
are still stored.
|
||||||
|
|
||||||
|
The virtual garage is locked down. Players only see vehicles they have been
|
||||||
|
granted or have unlocked through systems such as the store. Virtual garage
|
||||||
|
unlocks are separate from the 5 physical vehicle slots in the garage. The
|
||||||
|
virtual garage uses mission-configured spawn lanes, and spawning may be blocked
|
||||||
|
if the spawn position is occupied.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Economy Services
|
||||||
|
|
||||||
|
Economy services are server-controlled. Charges must succeed before the world
|
||||||
|
effect is applied.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Medical
|
||||||
|
|
||||||
|
Medical services are player-funded first.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Billing order:
|
||||||
|
|
||||||
|
1. Player bank balance.
|
||||||
|
2. Player cash.
|
||||||
|
3. Organization funds, when allowed by the server.
|
||||||
|
4. Organization credit-line debt for the player when organization fallback is
|
||||||
|
used.
|
||||||
|
|
||||||
|
Medical respawn placement uses mission-configured medical spawn objects.
|
||||||
|
|
||||||
|
### Refuel
|
||||||
|
|
||||||
|
Refuel service is organization-funded. If the organization cannot cover the
|
||||||
|
cost, the vehicle is not refueled or the fuel level is rolled back.
|
||||||
|
|
||||||
|
Refuel is available from the garage app dashboard shown above.
|
||||||
|
|
||||||
|
### Repair
|
||||||
|
|
||||||
|
Repair service is organization-funded. The repair is only applied after the
|
||||||
|
organization charge succeeds.
|
||||||
|
|
||||||
|
Repair is available from the garage app dashboard shown above.
|
||||||
|
|
||||||
|
### Rearm
|
||||||
|
|
||||||
|
If the mission exposes rearm service through the economy or support workflow,
|
||||||
|
expect it to follow the same server-authoritative pattern: the service request
|
||||||
|
must be accepted and billed before equipment or vehicle state changes are
|
||||||
|
applied.
|
||||||
|
|
||||||
|
Rearm is available from the garage app dashboard shown above.
|
||||||
|
|
||||||
|
## Common Player Checks
|
||||||
|
|
||||||
|
If a system does not appear or does not work:
|
||||||
|
|
||||||
|
- Move closer to the interaction object.
|
||||||
|
- Confirm you are using the correct object type, such as ATM vs bank.
|
||||||
|
- Confirm your group leader has acknowledged an assigned CAD task.
|
||||||
|
- Confirm the needed store unlock has been purchased before checking VA or VG.
|
||||||
|
- Confirm the garage spawn point is clear before using the virtual garage.
|
||||||
|
- Confirm your player, cash, bank, or organization funds can cover the service.
|
||||||
|
|
||||||
|
## Related Guides
|
||||||
|
|
||||||
|
- [Mission Designer Guide](./MISSION_DESIGNER_GUIDE.md)
|
||||||
|
- [Client CAD Usage Guide](./CLIENT_CAD_USAGE_GUIDE.md)
|
||||||
|
- [Client Phone Usage Guide](./CLIENT_PHONE_USAGE_GUIDE.md)
|
||||||
|
- [Client Bank Usage Guide](./CLIENT_BANK_USAGE_GUIDE.md)
|
||||||
|
- [Client Garage Usage Guide](./CLIENT_GARAGE_USAGE_GUIDE.md)
|
||||||
|
- [Client Locker Usage Guide](./CLIENT_LOCKER_USAGE_GUIDE.md)
|
||||||
|
- [Organization Usage Guide](./ORG_USAGE_GUIDE.md)
|
||||||
|
- [Store Usage Guide](./STORE_USAGE_GUIDE.md)
|
||||||
|
- [Economy Usage Guide](./ECONOMY_USAGE_GUIDE.md)
|
||||||
@ -30,6 +30,8 @@ See [SurrealDB Setup](./surrealdb-setup.md) for the full setup path.
|
|||||||
- [Mission Designer Guide](./MISSION_DESIGNER_GUIDE.md): how to place Eden
|
- [Mission Designer Guide](./MISSION_DESIGNER_GUIDE.md): how to place Eden
|
||||||
objects, garage markers, and CAD-compatible task modules for playable
|
objects, garage markers, and CAD-compatible task modules for playable
|
||||||
missions.
|
missions.
|
||||||
|
- [Player Guide](./PLAYER_GUIDE.md): how players use CAD, phone, bank, store,
|
||||||
|
locker, garage, and economy services during missions.
|
||||||
- [SurrealDB Setup](./surrealdb-setup.md): where to get SurrealDB or
|
- [SurrealDB Setup](./surrealdb-setup.md): where to get SurrealDB or
|
||||||
Surrealist and how to connect Forge to it for local or live use.
|
Surrealist and how to connect Forge to it for local or live use.
|
||||||
|
|
||||||
|
|||||||
@ -70,6 +70,16 @@ npm run build:webui
|
|||||||
playable missions.
|
playable missions.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::u-page-card
|
||||||
|
---
|
||||||
|
icon: i-lucide-user-round-check
|
||||||
|
title: Player Guide
|
||||||
|
to: /getting-started/player-guide
|
||||||
|
---
|
||||||
|
Learn the player-facing CAD, phone, bank, store, locker, garage, and economy
|
||||||
|
workflows.
|
||||||
|
:::
|
||||||
|
|
||||||
:::u-page-card
|
:::u-page-card
|
||||||
---
|
---
|
||||||
icon: i-lucide-database
|
icon: i-lucide-database
|
||||||
|
|||||||
320
docus/content/1.getting-started/5.player-guide.md
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
---
|
||||||
|
title: "Player Guide"
|
||||||
|
description: "Use this guide as the player-facing overview for Forge systems. It explains what players interact with during normal missions, how task assignment works, and what persistent storage limits apply."
|
||||||
|
---
|
||||||
|
|
||||||
|
Player-guide screenshots are stored as JPG files under
|
||||||
|
`docus/public/images/player`.
|
||||||
|
|
||||||
|
## Opening Forge Interactions
|
||||||
|
|
||||||
|
Most Forge actions are opened from the actor interaction menu while standing
|
||||||
|
near a configured mission object.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Press `Tab` by default to open the custom interaction menu. Server settings or
|
||||||
|
local keybind changes may use a different key.
|
||||||
|
|
||||||
|
Known current behavior: after closing the custom interaction menu, players may
|
||||||
|
need to press `Tab` twice before it opens again. Treat this as a temporary
|
||||||
|
workaround until the interaction menu focus behavior is investigated further.
|
||||||
|
|
||||||
|
Players usually need to be within 5 meters of an interaction object such as a
|
||||||
|
bank terminal, ATM, store counter, garage terminal, or locker.
|
||||||
|
|
||||||
|
## CAD and Tasks
|
||||||
|
|
||||||
|
CAD is the main task and dispatch system. It is used for mission contracts,
|
||||||
|
group status, support requests, dispatch orders, and task assignment.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Player workflow:
|
||||||
|
|
||||||
|
1. Open CAD from the available interaction path.
|
||||||
|
2. Review available or assigned tasks.
|
||||||
|
3. If a dispatcher assigns a task to your group, the group leader must
|
||||||
|
acknowledge or decline it.
|
||||||
|
4. Once acknowledged, the task becomes active for the assigned group.
|
||||||
|
5. Complete the task objective shown by CAD, map task state, and mission
|
||||||
|
instructions.
|
||||||
|
|
||||||
|
Map focus behavior:
|
||||||
|
|
||||||
|
- Click an assigned or accepted task in the operations task board to center the
|
||||||
|
map on that task.
|
||||||
|
- Click a roster member to center the map on that player.
|
||||||
|
- Click a support request to center the map on the request location.
|
||||||
|
- Dispatch map mode supports the same focus behavior for groups, contracts,
|
||||||
|
and support requests.
|
||||||
|
|
||||||
|
Dispatch workflow:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. Open CAD with a dispatcher-enabled slot or permission.
|
||||||
|
2. Use dispatch mode to review groups, open contracts, assigned contracts, and
|
||||||
|
support requests.
|
||||||
|
3. Assign available contracts to active groups.
|
||||||
|
4. Send dispatch orders or close completed orders as needed.
|
||||||
|
5. Track group status and recent CAD activity.
|
||||||
|
|
||||||
|
Dispatch access:
|
||||||
|
|
||||||
|
- The CEO slot can administer the default organization and use CAD dispatch
|
||||||
|
permissions.
|
||||||
|
- The Dispatch slot grants CAD dispatch permissions without default
|
||||||
|
organization administration rights.
|
||||||
|
- Players who are the CEO or owner of their own organization also receive CAD
|
||||||
|
dispatch permissions.
|
||||||
|
|
||||||
|
Important task behavior:
|
||||||
|
|
||||||
|
- CAD assignment reserves a task for a group.
|
||||||
|
- The task starts after the assigned group leader acknowledges it.
|
||||||
|
- If the leader declines, the task returns to the open contract board.
|
||||||
|
- Some task timers wait for group-leader acknowledgment before counting down.
|
||||||
|
|
||||||
|
## Phone
|
||||||
|
|
||||||
|
The phone provides contacts, messages, email, and local utility apps.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Contacts
|
||||||
|
|
||||||
|
Use Contacts to keep track of other players by phone number or email address.
|
||||||
|
Adding contacts makes it easier to start messages and emails without manually
|
||||||
|
entering recipient details every time.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Messages
|
||||||
|
|
||||||
|
Messages are short player-to-player conversations.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Use Messages to:
|
||||||
|
|
||||||
|
- start or continue a conversation with a contact
|
||||||
|
- read incoming messages
|
||||||
|
- mark messages as read
|
||||||
|
- delete messages you no longer need
|
||||||
|
|
||||||
|
### Email
|
||||||
|
|
||||||
|
Email is used for longer player-to-player communication.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Use Email to:
|
||||||
|
|
||||||
|
- send a subject and body to another player
|
||||||
|
- read incoming mail
|
||||||
|
- mark email as read
|
||||||
|
- delete old email
|
||||||
|
|
||||||
|
### Local Phone Apps
|
||||||
|
|
||||||
|
Notes, calendar events, clocks, alarms, and theme preferences are local utility
|
||||||
|
features. They are saved for the local player profile and should not be treated
|
||||||
|
as shared multiplayer data.
|
||||||
|
|
||||||
|
## Bank and ATM
|
||||||
|
|
||||||
|
Bank and ATM access are separate.
|
||||||
|
|
||||||
|
Use a bank object for full banking:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- view account information
|
||||||
|
- transfer funds
|
||||||
|
- deposit earnings
|
||||||
|
- change PIN
|
||||||
|
|
||||||
|
Use an ATM for limited account access:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- PIN-gated account actions
|
||||||
|
- ATM banking workflows
|
||||||
|
- no PIN changes
|
||||||
|
|
||||||
|
If a PIN prompt appears, enter the correct PIN before attempting account
|
||||||
|
actions.
|
||||||
|
|
||||||
|
## Organizations
|
||||||
|
|
||||||
|
Players start in the default organization. A player can create a player-owned
|
||||||
|
organization only if they have `$50,000` available for the registration fee.
|
||||||
|
Organization access depends on the player's role.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Default organization:
|
||||||
|
|
||||||
|
- The `ceo` slot can administer the default organization.
|
||||||
|
- The `dispatch` slot receives CAD dispatch permissions, but does not receive
|
||||||
|
default organization administration rights.
|
||||||
|
|
||||||
|
Player-owned organizations:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- The player who created the organization is its owner or CEO.
|
||||||
|
- The owner can administer the organization, including treasury and roster
|
||||||
|
actions exposed by the organization interface.
|
||||||
|
- Organization owners can invite players, manage members, assign credit lines,
|
||||||
|
transfer funds or run payroll when funds are available, and disband the
|
||||||
|
organization.
|
||||||
|
- Organization owners can use organization funds for supported store purchases.
|
||||||
|
- Members may receive assigned credit lines, accept or decline organization
|
||||||
|
invites, and leave the organization.
|
||||||
|
- The organization CEO or owner cannot leave their own organization directly.
|
||||||
|
They must disband the organization if they want to leave it.
|
||||||
|
|
||||||
|
Organization actions are server-authoritative. If an organization action fails,
|
||||||
|
check that the player has the correct role, the player or organization has
|
||||||
|
enough funds, and the target player is eligible for the action.
|
||||||
|
|
||||||
|
## Store
|
||||||
|
|
||||||
|
Stores sell unlocks and equipment through the configured server-side catalog.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Store purchases may grant:
|
||||||
|
|
||||||
|
- items or equipment added to the locker
|
||||||
|
- matching gear unlocks in the virtual arsenal
|
||||||
|
- vehicle unlocks in the virtual garage
|
||||||
|
- other mission-configured rewards
|
||||||
|
|
||||||
|
Store purchases are server-authoritative. If a purchase succeeds, the relevant
|
||||||
|
bank, locker, virtual arsenal, virtual garage, or organization state updates
|
||||||
|
from the server.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Vehicle purchases unlock the vehicle in the virtual garage. They do not place a
|
||||||
|
physical vehicle into the player's 5-slot garage. Use the virtual garage to
|
||||||
|
spawn an unlocked vehicle, and use the garage to store or retrieve live world
|
||||||
|
vehicles.
|
||||||
|
|
||||||
|
## Locker and Virtual Arsenal
|
||||||
|
|
||||||
|
The locker is personal item storage.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Locker rules:
|
||||||
|
|
||||||
|
- Up to 25 items can be stored.
|
||||||
|
- The locker saves when the locker container is closed.
|
||||||
|
- Over-capacity storage can warn or fail depending on server handling.
|
||||||
|
|
||||||
|
The virtual arsenal is locked down. Players only see gear they have been
|
||||||
|
granted or have unlocked through systems such as the store. The virtual arsenal
|
||||||
|
is not intended to expose the full unrestricted Arma arsenal.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Garage and Virtual Garage
|
||||||
|
|
||||||
|
The garage stores physical player vehicles that have been saved from the world.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Garage rules:
|
||||||
|
|
||||||
|
- Up to 5 vehicles can be stored.
|
||||||
|
- Stored vehicles can be retrieved from a garage interaction point.
|
||||||
|
- Retrieved vehicles become live world vehicles again.
|
||||||
|
- Vehicle service actions operate on live nearby vehicles, not vehicles that
|
||||||
|
are still stored.
|
||||||
|
|
||||||
|
The virtual garage is locked down. Players only see vehicles they have been
|
||||||
|
granted or have unlocked through systems such as the store. Virtual garage
|
||||||
|
unlocks are separate from the 5 physical vehicle slots in the garage. The
|
||||||
|
virtual garage uses mission-configured spawn lanes, and spawning may be blocked
|
||||||
|
if the spawn position is occupied.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Economy Services
|
||||||
|
|
||||||
|
Economy services are server-controlled. Charges must succeed before the world
|
||||||
|
effect is applied.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Medical
|
||||||
|
|
||||||
|
Medical services are player-funded first.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Billing order:
|
||||||
|
|
||||||
|
1. Player bank balance.
|
||||||
|
2. Player cash.
|
||||||
|
3. Organization funds, when allowed by the server.
|
||||||
|
4. Organization credit-line debt for the player when organization fallback is
|
||||||
|
used.
|
||||||
|
|
||||||
|
Medical respawn placement uses mission-configured medical spawn objects.
|
||||||
|
|
||||||
|
### Refuel
|
||||||
|
|
||||||
|
Refuel service is organization-funded. If the organization cannot cover the
|
||||||
|
cost, the vehicle is not refueled or the fuel level is rolled back.
|
||||||
|
|
||||||
|
Refuel is available from the garage app dashboard shown above.
|
||||||
|
|
||||||
|
### Repair
|
||||||
|
|
||||||
|
Repair service is organization-funded. The repair is only applied after the
|
||||||
|
organization charge succeeds.
|
||||||
|
|
||||||
|
Repair is available from the garage app dashboard shown above.
|
||||||
|
|
||||||
|
### Rearm
|
||||||
|
|
||||||
|
If the mission exposes rearm service through the economy or support workflow,
|
||||||
|
expect it to follow the same server-authoritative pattern: the service request
|
||||||
|
must be accepted and billed before equipment or vehicle state changes are
|
||||||
|
applied.
|
||||||
|
|
||||||
|
Rearm is available from the garage app dashboard shown above.
|
||||||
|
|
||||||
|
## Common Player Checks
|
||||||
|
|
||||||
|
If a system does not appear or does not work:
|
||||||
|
|
||||||
|
- Move closer to the interaction object.
|
||||||
|
- Confirm you are using the correct object type, such as ATM vs bank.
|
||||||
|
- Confirm your group leader has acknowledged an assigned CAD task.
|
||||||
|
- Confirm the needed store unlock has been purchased before checking VA or VG.
|
||||||
|
- Confirm the garage spawn point is clear before using the virtual garage.
|
||||||
|
- Confirm your player, cash, bank, or organization funds can cover the service.
|
||||||
|
|
||||||
|
## Related Guides
|
||||||
|
|
||||||
|
- [Mission Designer Guide](/getting-started/mission-designer)
|
||||||
|
- [Client CAD Usage Guide](/client-addons/cad)
|
||||||
|
- [Client Phone Usage Guide](/client-addons/phone)
|
||||||
|
- [Client Bank Usage Guide](/client-addons/bank)
|
||||||
|
- [Client Garage Usage Guide](/client-addons/garage)
|
||||||
|
- [Client Locker Usage Guide](/client-addons/locker)
|
||||||
|
- [Organization Usage Guide](/server-modules/organization)
|
||||||
|
- [Store Usage Guide](/server-modules/store)
|
||||||
|
- [Economy Usage Guide](/server-modules/economy)
|
||||||
@ -43,6 +43,24 @@ The target is only repaired after the organization charge succeeds.
|
|||||||
The client garage UI forwards selected nearby vehicle repair requests through
|
The client garage UI forwards selected nearby vehicle repair requests through
|
||||||
the same event.
|
the same event.
|
||||||
|
|
||||||
|
## Rearm
|
||||||
|
|
||||||
|
Rearm is organization-funded.
|
||||||
|
|
||||||
|
Use the rearm service event:
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
[QEGVAR(economy,RearmService), [_target, _unit, _cost]] call CBA_fnc_serverEvent;
|
||||||
|
```
|
||||||
|
|
||||||
|
`_cost` is optional. Passing `-1` uses the configured service rearm cost.
|
||||||
|
The target is only rearmed after the organization charge succeeds.
|
||||||
|
`setVehicleAmmo` has global effects, but the ammo is only added to local
|
||||||
|
turrets, so the service broadcasts the ammo reset after billing succeeds.
|
||||||
|
|
||||||
|
The client garage UI forwards selected nearby vehicle rearm requests through
|
||||||
|
the same event.
|
||||||
|
|
||||||
## Medical
|
## Medical
|
||||||
|
|
||||||
Medical is player-funded first.
|
Medical is player-funded first.
|
||||||
|
|||||||
@ -36,6 +36,16 @@ assignments.
|
|||||||
- group status, role, and profile requests
|
- group status, role, and profile requests
|
||||||
- map focus actions
|
- map focus actions
|
||||||
|
|
||||||
|
## Map Focus Behavior
|
||||||
|
|
||||||
|
CAD list entries can drive the native map position without duplicating map
|
||||||
|
logic in the browser UI. In operations mode, assigned or accepted task cards,
|
||||||
|
roster member cards, and support request cards send focus events. In dispatch
|
||||||
|
map mode, group, contract, and support request cards use the same focus path.
|
||||||
|
|
||||||
|
Task and support request focus uses the stored record position. Roster member
|
||||||
|
focus uses the member position included in the hydrated group roster.
|
||||||
|
|
||||||
## Browser Events
|
## Browser Events
|
||||||
|
|
||||||
| Event | Client behavior |
|
| Event | Client behavior |
|
||||||
@ -57,6 +67,7 @@ assignments.
|
|||||||
| `cad::groups::role` | Update group role. |
|
| `cad::groups::role` | Update group role. |
|
||||||
| `cad::groups::profile` | Update status and role together. |
|
| `cad::groups::profile` | Update status and role together. |
|
||||||
| `cad::groups::focus` | Center map on a group. |
|
| `cad::groups::focus` | Center map on a group. |
|
||||||
|
| `cad::members::focus` | Center map on a group member. |
|
||||||
| `cad::tasks::focus` | Center map on a task. |
|
| `cad::tasks::focus` | Center map on a task. |
|
||||||
| `cad::requests::focus` | Center map on a support request. |
|
| `cad::requests::focus` | Center map on a support request. |
|
||||||
| `map::zoomIn` | Zoom native map in. |
|
| `map::zoomIn` | Zoom native map in. |
|
||||||
|
|||||||
@ -55,21 +55,21 @@ is finalized and spawned onto the resolved lane.
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `garage::hydrate` | Initial vehicle and session payload. |
|
| `garage::hydrate` | Initial vehicle and session payload. |
|
||||||
| `garage::sync` | Refreshed vehicle payload. |
|
| `garage::sync` | Refreshed vehicle payload. |
|
||||||
| `garage::service::success` | Browser notice for accepted refuel/repair requests. |
|
| `garage::service::success` | Browser notice for accepted refuel/rearm/repair requests. |
|
||||||
| `garage::service::failure` | Browser notice for rejected refuel/repair requests. |
|
| `garage::service::failure` | Browser notice for rejected refuel/rearm/repair requests. |
|
||||||
|
|
||||||
Server action responses are handled by the action service and notification
|
Server action responses are handled by the action service and notification
|
||||||
flow.
|
flow.
|
||||||
|
|
||||||
## Vehicle Service
|
## Vehicle Service
|
||||||
|
|
||||||
The selected vehicle detail panel includes refuel and repair actions for nearby
|
The selected vehicle detail panel includes refuel, rearm, and repair actions for nearby
|
||||||
world vehicles. Stored records must be retrieved first because server economy
|
world vehicles. Stored records must be retrieved first because server economy
|
||||||
services operate on live vehicle objects, not stored garage records.
|
services operate on live vehicle objects, not stored garage records.
|
||||||
|
|
||||||
Refuel requests use the server economy `RefuelService` event. Repair requests
|
Refuel requests use the server economy `RefuelService` event. Rearm requests
|
||||||
use the server economy `RepairService` event. Both services are billed by the
|
use `RearmService`. Repair requests use `RepairService`. These services are
|
||||||
server economy addon through organization funds.
|
billed by the server economy addon through organization funds.
|
||||||
|
|
||||||
## Mission Setup
|
## Mission Setup
|
||||||
|
|
||||||
|
|||||||
BIN
docus/public/images/player/atm_app_home.jpg
Normal file
|
After Width: | Height: | Size: 974 KiB |
BIN
docus/public/images/player/atm_app_pin.jpg
Normal file
|
After Width: | Height: | Size: 964 KiB |
BIN
docus/public/images/player/bank_app.jpg
Normal file
|
After Width: | Height: | Size: 183 KiB |
BIN
docus/public/images/player/cad_dispatch_board.jpg
Normal file
|
After Width: | Height: | Size: 450 KiB |
BIN
docus/public/images/player/cad_ops_board.jpg
Normal file
|
After Width: | Height: | Size: 569 KiB |
BIN
docus/public/images/player/garage.jpg
Normal file
|
After Width: | Height: | Size: 203 KiB |
BIN
docus/public/images/player/interaction_menu.jpg
Normal file
|
After Width: | Height: | Size: 417 KiB |
BIN
docus/public/images/player/locker.jpg
Normal file
|
After Width: | Height: | Size: 383 KiB |
BIN
docus/public/images/player/medical_respawn.jpg
Normal file
|
After Width: | Height: | Size: 759 KiB |
BIN
docus/public/images/player/org_dashboard.jpg
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
docus/public/images/player/org_home.jpg
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
docus/public/images/player/org_registration.jpg
Normal file
|
After Width: | Height: | Size: 164 KiB |
BIN
docus/public/images/player/org_treasury.jpg
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
docus/public/images/player/phone_contacts.jpg
Normal file
|
After Width: | Height: | Size: 954 KiB |
BIN
docus/public/images/player/phone_email.jpg
Normal file
|
After Width: | Height: | Size: 949 KiB |
BIN
docus/public/images/player/phone_home.jpg
Normal file
|
After Width: | Height: | Size: 966 KiB |
BIN
docus/public/images/player/phone_messages.jpg
Normal file
|
After Width: | Height: | Size: 951 KiB |
BIN
docus/public/images/player/store_catalog.jpg
Normal file
|
After Width: | Height: | Size: 237 KiB |
BIN
docus/public/images/player/store_checkout.jpg
Normal file
|
After Width: | Height: | Size: 248 KiB |
BIN
docus/public/images/player/virtual_arsenal.jpg
Normal file
|
After Width: | Height: | Size: 654 KiB |
BIN
docus/public/images/player/virtual_garage.jpg
Normal file
|
After Width: | Height: | Size: 1020 KiB |
@ -23,9 +23,13 @@ const generatedPages = [
|
|||||||
source: 'docs/MISSION_DESIGNER_GUIDE.md',
|
source: 'docs/MISSION_DESIGNER_GUIDE.md',
|
||||||
target: '1.getting-started/4.mission-designer.md'
|
target: '1.getting-started/4.mission-designer.md'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: 'docs/PLAYER_GUIDE.md',
|
||||||
|
target: '1.getting-started/5.player-guide.md'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
source: 'docs/surrealdb-setup.md',
|
source: 'docs/surrealdb-setup.md',
|
||||||
target: '1.getting-started/5.surrealdb-setup.md'
|
target: '1.getting-started/6.surrealdb-setup.md'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: 'arma/server/docs/README.md',
|
source: 'arma/server/docs/README.md',
|
||||||
@ -435,6 +439,16 @@ npm run build:webui
|
|||||||
playable missions.
|
playable missions.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::u-page-card
|
||||||
|
---
|
||||||
|
icon: i-lucide-user-round-check
|
||||||
|
title: Player Guide
|
||||||
|
to: /getting-started/player-guide
|
||||||
|
---
|
||||||
|
Learn the player-facing CAD, phone, bank, store, locker, garage, and economy
|
||||||
|
workflows.
|
||||||
|
:::
|
||||||
|
|
||||||
:::u-page-card
|
:::u-page-card
|
||||||
---
|
---
|
||||||
icon: i-lucide-database
|
icon: i-lucide-database
|
||||||
|
|||||||