Updated Docs #9
@ -1,30 +1,46 @@
|
||||
<h1 align="center">Forge Client</h1>
|
||||
<p align="center">
|
||||
<a href="https://gitea.innovativedevsolutions.org/IDSolutions/forge/releases/latest"><img src="https://img.shields.io/gitea/v/release/IDSolutions/forge?gitea_url=https%3A%2F%2Fgitea.innovativedevsolutions.org&label=Version" alt="Version"></a>
|
||||
<a href="https://gitea.innovativedevsolutions.org/IDSolutions/forge/issues"><img src="https://img.shields.io/gitea/issues/open/IDSolutions/forge?gitea_url=https%3A%2F%2Fgitea.innovativedevsolutions.org&label=Issues" alt="Issues"></a>
|
||||
<!-- <a href="https://steamcommunity.com/sharedfiles/filedetails/?id=MOD_ID"><img src="https://img.shields.io/steam/downloads/MOD_ID.svg?&label=Downloads" alt="Downloads"></a> -->
|
||||
<a href="https://gitea.innovativedevsolutions.org/IDSolutions/forge/src/branch/master/arma/server/LICENSE.md"><img src="https://img.shields.io/badge/License-APL%20SA-red?label=License" alt="License"></a>
|
||||
<br>
|
||||
<img src="https://img.shields.io/github/v/release/brettmayson/hemtt?label=HEMTT" alt="HEMTT">
|
||||
<img src="https://img.shields.io/github/v/release/cbateam/cba_a3?label=CBA%20A3" alt="CBA A3">
|
||||
</p>
|
||||
# Forge Client
|
||||
|
||||
<p align="center">
|
||||
<b>Requires the latest version of <a href="https://github.com/CBATeam/CBA_A3/releases/latest">CBA A3</a></b>
|
||||
</p>
|
||||
Forge Client contains the Arma client-side addons for Forge. It owns player UI,
|
||||
browser bridges, client repositories, local event handling, and client-to-server
|
||||
CBA RPC requests.
|
||||
|
||||
**Forge Client** aims to...
|
||||
The client mod pairs with `arma/server`: client addons collect player input and
|
||||
render state, while server addons and the Rust extension own authoritative
|
||||
state and persistence.
|
||||
|
||||
The project is entirely **open-source** and any contributions are welcome.
|
||||
## Requirements
|
||||
- CBA A3
|
||||
- ACE3 for features that use ACE interactions, arsenal, spectator, or medical
|
||||
integrations
|
||||
- Forge Server running the matching server-side addons
|
||||
|
||||
## Core Features
|
||||
## Addons
|
||||
- `main`: shared client mod config and macros
|
||||
- `common`: shared browser UI bridge helpers
|
||||
- `actor`: player interaction menu and actor repository
|
||||
- `bank`: banking UI and account request bridge
|
||||
- `cad`: map/CAD UI for dispatch, groups, tasks, and support requests
|
||||
- `garage`: vehicle storage and virtual garage UI
|
||||
- `locker`: locker and virtual arsenal repositories
|
||||
- `notifications`: notification HUD and sounds
|
||||
- `org`: organization portal UI
|
||||
- `phone`: phone, contacts, messages, and email UI
|
||||
- `store`: storefront catalog and checkout UI
|
||||
|
||||
- Feature
|
||||
## UI Pattern
|
||||
Most feature UIs use an Arma display with a `CT_WEBBROWSER` control. JavaScript
|
||||
sends JSON events through A3API, SQF handles them in `fnc_handleUIEvents.sqf`,
|
||||
and response events are sent back into the browser with `ctrlWebBrowserAction
|
||||
["ExecJS", ...]`.
|
||||
|
||||
## Contributing
|
||||
Client repositories cache the most recent state for display only. Server addons
|
||||
and the extension remain authoritative.
|
||||
|
||||
For new contributers, see the [Contributing Setup & Guidelines](./.github/CONTRIBUTING.md).
|
||||
## Documentation
|
||||
- [Root client usage guide](../../docs/CLIENT_USAGE_GUIDE.md)
|
||||
- [Client docs](./docs/README.md)
|
||||
- [Common web UI framework notes](./addons/common/WEB_UI_FRAMEWORK.md)
|
||||
- [CAD map integration notes](./addons/cad/MAP_README.md)
|
||||
|
||||
## License
|
||||
|
||||
Forge Client is licensed under [APL-SA](./LICENSE.md).
|
||||
|
||||
@ -1,3 +1,28 @@
|
||||
# forge_client_actor
|
||||
# Forge Client Actor
|
||||
|
||||
Description for this addon
|
||||
## Overview
|
||||
The actor addon owns the player interaction menu and client-side actor
|
||||
repository. It initializes actor state from the server, tracks client-visible
|
||||
actor fields, and routes menu actions to other Forge UIs.
|
||||
|
||||
## Dependencies
|
||||
- `forge_client_main`
|
||||
- server actor events from `forge_server_actor`
|
||||
- runtime integrations with bank, CAD, garage, org, phone, store, locker, and
|
||||
notifications addons
|
||||
|
||||
## Main Components
|
||||
- `fnc_initRepository.sqf` manages client actor state and server init/save
|
||||
requests.
|
||||
- `fnc_openUI.sqf` opens `RscActorMenu`.
|
||||
- `fnc_handleUIEvents.sqf` handles browser menu actions.
|
||||
|
||||
## Event Surface
|
||||
The actor menu can open bank, ATM mode, CAD, garage, virtual garage, org, phone,
|
||||
store, and ACE arsenal interactions. Client post-init also wires player killed
|
||||
and respawn handlers into the server economy flow.
|
||||
|
||||
## Runtime Notes
|
||||
Actor state is loaded before dependent systems initialize. When the server sends
|
||||
actor sync data, the repository updates local view state and clears the loading
|
||||
screen.
|
||||
|
||||
@ -1,3 +1,34 @@
|
||||
# forge_client_bank
|
||||
# Forge Client Bank
|
||||
|
||||
Description for this addon
|
||||
## Overview
|
||||
The bank addon provides the client banking UI and browser bridge for account
|
||||
hydrate, deposits, withdrawals, transfers, PIN entry, earnings deposits, and
|
||||
credit-line repayment.
|
||||
|
||||
## Dependencies
|
||||
- `forge_client_common`
|
||||
- `forge_client_main`
|
||||
- server bank events from `forge_server_bank`
|
||||
- notifications for server-driven messages
|
||||
|
||||
## Main Components
|
||||
- `fnc_initRepository.sqf` tracks account load state.
|
||||
- `fnc_initUIBridge.sqf` translates browser requests into server RPCs and sends
|
||||
server responses back to the browser.
|
||||
- `fnc_handleUIEvents.sqf` handles `bank::*` browser events.
|
||||
- `fnc_openUI.sqf` opens `RscBank`; ATM mode is supported by passing `true`.
|
||||
|
||||
## Browser Events
|
||||
- `bank::ready`
|
||||
- `bank::refresh`
|
||||
- `bank::deposit::request`
|
||||
- `bank::withdraw::request`
|
||||
- `bank::transfer::request`
|
||||
- `bank::depositEarnings::request`
|
||||
- `bank::repayCreditLine::request`
|
||||
- `bank::pin::request`
|
||||
- `bank::close`
|
||||
|
||||
## Runtime Notes
|
||||
The client only displays and requests account changes. The server bank addon and
|
||||
extension own validation, balances, authorization, and persistence.
|
||||
|
||||
37
arma/client/addons/cad/README.md
Normal file
37
arma/client/addons/cad/README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# Forge Client CAD
|
||||
|
||||
## Overview
|
||||
The CAD addon provides the client map and dispatch interface for task
|
||||
assignment, dispatch orders, support requests, group status, group roles, and
|
||||
task acknowledge/decline actions.
|
||||
|
||||
## Dependencies
|
||||
- `forge_client_main`
|
||||
- server CAD events from `forge_server_cad`
|
||||
- server task catalog data exposed through CAD hydrate payloads
|
||||
|
||||
## Main Components
|
||||
- `fnc_initRepository.sqf` caches hydrated CAD view state.
|
||||
- `fnc_initUI.sqf` wires the native map, top bar, bottom bar, side panel, and
|
||||
dispatcher browser controls.
|
||||
- `fnc_initUIBridge.sqf` sends browser actions to server CAD RPCs and pushes
|
||||
state back to the UI.
|
||||
- `fnc_handleUIEvents.sqf` handles `cad::*` browser events.
|
||||
- `fnc_openUI.sqf` opens the CAD display.
|
||||
|
||||
## Supported Actions
|
||||
- hydrate CAD state
|
||||
- assign active tasks to groups
|
||||
- create and close dispatch orders
|
||||
- submit and close support requests
|
||||
- acknowledge or decline assigned tasks
|
||||
- update group status, role, and profile
|
||||
- focus map requests and toggle panels
|
||||
|
||||
## Notes
|
||||
CAD task visibility depends on server-side task catalog entries. Tasks created
|
||||
through Forge task modules or `forge_server_task_fnc_startTask` are the normal
|
||||
CAD-compatible task sources.
|
||||
|
||||
See [MAP_README.md](./MAP_README.md) for details on the integrated native map
|
||||
and browser layout.
|
||||
@ -1,5 +1,18 @@
|
||||
# forge_client_common
|
||||
# Forge Client Common
|
||||
|
||||
Common functionality shared between addons.
|
||||
## Overview
|
||||
The common addon contains shared client-side UI bridge helpers and common
|
||||
configuration used by browser-based feature addons.
|
||||
|
||||
See [WEB_UI_FRAMEWORK.md](./WEB_UI_FRAMEWORK.md) for the proposed shared `CT_WEBBROWSER` UI framework layout and API.
|
||||
## Dependencies
|
||||
- `forge_client_main`
|
||||
|
||||
## Main Components
|
||||
- `fnc_initWebUIBridge.sqf` provides shared bridge behavior for web browser UI
|
||||
controls.
|
||||
- `WEB_UI_FRAMEWORK.md` documents the proposed shared browser runtime and event
|
||||
API for Forge web UIs.
|
||||
|
||||
## Notes
|
||||
Keep feature-specific behavior in the owning addon. Common should hold reusable
|
||||
browser bridge patterns, not copied application logic.
|
||||
|
||||
@ -1,3 +1,43 @@
|
||||
# forge_client_garage
|
||||
# Forge Client Garage
|
||||
|
||||
Description for this addon
|
||||
## Overview
|
||||
The garage addon provides player vehicle storage UI, vehicle store/retrieve
|
||||
actions, selected nearby vehicle service requests, and virtual garage state on
|
||||
the client.
|
||||
|
||||
## Dependencies
|
||||
- `forge_client_common`
|
||||
- `forge_client_main`
|
||||
- server garage events from `forge_server_garage`
|
||||
- notifications for action feedback
|
||||
|
||||
## Main Components
|
||||
- `fnc_initRepository.sqf` manages player garage view state.
|
||||
- `fnc_initVGRepository.sqf` manages virtual garage view state.
|
||||
- `fnc_initHelperService.sqf` resolves vehicle names, hit points, and payload
|
||||
details.
|
||||
- `fnc_initContextService.sqf` gathers nearby/current vehicle context.
|
||||
- `fnc_initPayloadService.sqf` builds browser hydrate payloads.
|
||||
- `fnc_initActionService.sqf` sends store/retrieve requests, forwards selected
|
||||
nearby vehicle refuel/repair service requests, and handles action responses.
|
||||
- `fnc_initUIBridge.sqf` pushes hydrate/sync events to the browser.
|
||||
- `fnc_openUI.sqf` opens `RscGarage`.
|
||||
- `fnc_openVG.sqf` opens the Arma garage-style virtual garage view.
|
||||
|
||||
## Browser Events
|
||||
- `garage::ready`
|
||||
- `garage::refresh`
|
||||
- `garage::vehicle::retrieve::request`
|
||||
- `garage::vehicle::store::request`
|
||||
- `garage::vehicle::refuel::request`
|
||||
- `garage::vehicle::repair::request`
|
||||
- `garage::close`
|
||||
|
||||
## Runtime Notes
|
||||
The client builds vehicle context and sends requests. The server garage addon
|
||||
and extension own stored vehicle state.
|
||||
|
||||
Refuel and repair buttons are available from the selected vehicle detail panel
|
||||
for nearby world vehicles. Stored records must be retrieved before they can be
|
||||
serviced because fuel and repair operate on live vehicle objects. Service
|
||||
billing is handled by the server economy addon and charges organization funds.
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* File: fnc_handleUIEvents.sqf
|
||||
* Author: IDSolutions
|
||||
* Date: 2025-12-16
|
||||
* Last Update: 2026-01-30
|
||||
* Last Update: 2026-04-18
|
||||
* Public: No
|
||||
*
|
||||
* Description:
|
||||
@ -53,6 +53,16 @@ switch (_event) do {
|
||||
GVAR(GarageActionService) call ["handleStoreRequest", [_data]];
|
||||
};
|
||||
};
|
||||
case "garage::vehicle::refuel::request": {
|
||||
if !(isNil QGVAR(GarageActionService)) then {
|
||||
GVAR(GarageActionService) call ["handleRefuelRequest", [_data]];
|
||||
};
|
||||
};
|
||||
case "garage::vehicle::repair::request": {
|
||||
if !(isNil QGVAR(GarageActionService)) then {
|
||||
GVAR(GarageActionService) call ["handleRepairRequest", [_data]];
|
||||
};
|
||||
};
|
||||
case "garage::refresh": {
|
||||
if !(isNil QGVAR(GarageUIBridge)) then {
|
||||
GVAR(GarageUIBridge) call ["refreshGarage", []];
|
||||
|
||||
@ -4,10 +4,12 @@
|
||||
* File: fnc_initActionService.sqf
|
||||
* Author: IDSolutions
|
||||
* Date: 2026-03-27
|
||||
* Last Update: 2026-04-18
|
||||
* Public: No
|
||||
*
|
||||
* Description:
|
||||
* Initializes the garage action service for retrieve and store world actions.
|
||||
* Initializes the garage action service for retrieve, store, refuel, and
|
||||
* repair world actions.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
@ -26,6 +28,52 @@ GVAR(GarageActionServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||
_self set ["pendingStoreVehicle", objNull];
|
||||
_self set ["pendingRetrieve", createHashMap];
|
||||
}],
|
||||
["sendServiceResult", compileFinal {
|
||||
params [["_action", "", [""]], ["_success", false, [false]], ["_message", "", [""]]];
|
||||
|
||||
private _event = ["garage::service::failure", "garage::service::success"] select _success;
|
||||
GVAR(GarageUIBridge) call ["sendEvent", [_event, createHashMapFromArray [["action", _action], ["message", _message]]]];
|
||||
}],
|
||||
["refreshAfterService", compileFinal {
|
||||
[] spawn {
|
||||
sleep 0.75;
|
||||
if !(isNil QGVAR(GarageUIBridge)) then {
|
||||
GVAR(GarageUIBridge) call ["refreshGarage", []];
|
||||
};
|
||||
};
|
||||
}],
|
||||
["resolveServiceVehicle", compileFinal {
|
||||
params [["_data", createHashMap, [createHashMap]], ["_action", "service", [""]]];
|
||||
|
||||
private _netId = _data getOrDefault ["netId", ""];
|
||||
if (_netId isEqualTo "") exitWith {
|
||||
_self call ["sendServiceResult", [_action, false, "Select a nearby vehicle first."]];
|
||||
objNull
|
||||
};
|
||||
|
||||
private _vehicle = objectFromNetId _netId;
|
||||
if (isNull _vehicle) exitWith {
|
||||
_self call ["sendServiceResult", [_action, false, "The selected vehicle is no longer available."]];
|
||||
objNull
|
||||
};
|
||||
|
||||
if !(_vehicle isKindOf "Car" || { _vehicle isKindOf "Tank" } || { _vehicle isKindOf "Air" } || { _vehicle isKindOf "Ship" }) exitWith {
|
||||
_self call ["sendServiceResult", [_action, false, "Selected object is not a serviceable vehicle."]];
|
||||
objNull
|
||||
};
|
||||
|
||||
_vehicle
|
||||
}],
|
||||
["vehicleNeedsRepair", compileFinal {
|
||||
params [["_vehicle", objNull, [objNull]]];
|
||||
|
||||
if (isNull _vehicle) exitWith { false };
|
||||
if ((damage _vehicle) > 0.001) exitWith { true };
|
||||
|
||||
private _rawHitPoints = getAllHitPointsDamage _vehicle;
|
||||
private _hitPointValues = if (_rawHitPoints isEqualType [] && { count _rawHitPoints >= 3 }) then { _rawHitPoints param [2, []] } else { [] };
|
||||
({ _x > 0.001 } count _hitPointValues) > 0
|
||||
}],
|
||||
["handleRetrieveRequest", compileFinal {
|
||||
params [["_data", createHashMap, [createHashMap]]];
|
||||
|
||||
@ -97,6 +145,38 @@ GVAR(GarageActionServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||
_self set ["pendingStoreVehicle", _vehicle];
|
||||
[SRPC(garage,requestStoreVehicle), [getPlayerUID player, typeOf _vehicle, fuel _vehicle, damage _vehicle, _hitPointsJson]] call CFUNC(serverEvent);
|
||||
}],
|
||||
["handleRefuelRequest", compileFinal {
|
||||
params [["_data", createHashMap, [createHashMap]]];
|
||||
|
||||
private _vehicle = _self call ["resolveServiceVehicle", [_data, "refuel"]];
|
||||
if (isNull _vehicle) exitWith { false };
|
||||
|
||||
if ((fuel _vehicle) >= 0.999) exitWith {
|
||||
_self call ["sendServiceResult", ["refuel", false, "Vehicle fuel tank is already full."]];
|
||||
false
|
||||
};
|
||||
|
||||
[SRPC(economy,RefuelService), [_vehicle, player]] call CFUNC(serverEvent);
|
||||
_self call ["sendServiceResult", ["refuel", true, "Refuel request sent. Billing result will appear as a notification."]];
|
||||
_self call ["refreshAfterService", []];
|
||||
true
|
||||
}],
|
||||
["handleRepairRequest", compileFinal {
|
||||
params [["_data", createHashMap, [createHashMap]]];
|
||||
|
||||
private _vehicle = _self call ["resolveServiceVehicle", [_data, "repair"]];
|
||||
if (isNull _vehicle) exitWith { false };
|
||||
|
||||
if !(_self call ["vehicleNeedsRepair", [_vehicle]]) exitWith {
|
||||
_self call ["sendServiceResult", ["repair", false, "Vehicle has no reported damage."]];
|
||||
false
|
||||
};
|
||||
|
||||
[SRPC(economy,RepairService), [_vehicle, player, -1]] call CFUNC(serverEvent);
|
||||
_self call ["sendServiceResult", ["repair", true, "Repair request sent. Billing result will appear as a notification."]];
|
||||
_self call ["refreshAfterService", []];
|
||||
true
|
||||
}],
|
||||
["handleActionResponse", compileFinal {
|
||||
params [["_payload", createHashMap, [createHashMap]]];
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -23,6 +23,14 @@
|
||||
return bridge.send("garage::vehicle::store::request", payload);
|
||||
}
|
||||
|
||||
function requestRefuel(payload) {
|
||||
return bridge.send("garage::vehicle::refuel::request", payload);
|
||||
}
|
||||
|
||||
function requestRepair(payload) {
|
||||
return bridge.send("garage::vehicle::repair::request", payload);
|
||||
}
|
||||
|
||||
function notifyReady() {
|
||||
return bridge.ready({ loaded: true });
|
||||
}
|
||||
@ -75,11 +83,33 @@
|
||||
}
|
||||
});
|
||||
|
||||
bridge.on("garage::service::success", (payloadData) => {
|
||||
store.finishAction();
|
||||
if (GarageApp.actions) {
|
||||
GarageApp.actions.showNotice(
|
||||
"success",
|
||||
payloadData.message || "Service request sent.",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
bridge.on("garage::service::failure", (payloadData) => {
|
||||
store.finishAction();
|
||||
if (GarageApp.actions) {
|
||||
GarageApp.actions.showNotice(
|
||||
"error",
|
||||
payloadData.message || "Unable to service vehicle.",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
GarageApp.bridge = {
|
||||
notifyReady,
|
||||
receive: bridge.receive,
|
||||
requestClose,
|
||||
requestRefresh,
|
||||
requestRefuel,
|
||||
requestRepair,
|
||||
requestRetrieve,
|
||||
requestStore,
|
||||
sendEvent: bridge.send,
|
||||
|
||||
@ -343,11 +343,16 @@
|
||||
|
||||
const isStored = currentSelection.entryKind === "stored";
|
||||
const pendingAction = String(state.pendingAction || "");
|
||||
const isBusy =
|
||||
pendingAction === "retrieve" || pendingAction === "store";
|
||||
const isBusy = Boolean(pendingAction);
|
||||
const canRetrieve = isStored && !session.spawnBlocked && !isBusy;
|
||||
const canStore =
|
||||
!isStored && currentSelection.isEmpty !== false && !isBusy;
|
||||
const canRefuel =
|
||||
!isStored && Number(currentSelection.fuel || 0) < 0.999 && !isBusy;
|
||||
const canRepair =
|
||||
!isStored &&
|
||||
Number(currentSelection.health || 0) < 0.999 &&
|
||||
!isBusy;
|
||||
|
||||
return h(
|
||||
"section",
|
||||
@ -461,6 +466,34 @@
|
||||
? "Storing..."
|
||||
: "Store Vehicle",
|
||||
),
|
||||
h(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
className:
|
||||
"garage-btn garage-btn-secondary",
|
||||
disabled: !canRefuel,
|
||||
onClick: () =>
|
||||
actions.requestRefuelSelected(),
|
||||
},
|
||||
pendingAction === "refuel"
|
||||
? "Refueling..."
|
||||
: "Refuel",
|
||||
),
|
||||
h(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
className:
|
||||
"garage-btn garage-btn-secondary",
|
||||
disabled: !canRepair,
|
||||
onClick: () =>
|
||||
actions.requestRepairSelected(),
|
||||
},
|
||||
pendingAction === "repair"
|
||||
? "Repairing..."
|
||||
: "Repair",
|
||||
),
|
||||
h(
|
||||
"button",
|
||||
{
|
||||
@ -479,10 +512,10 @@
|
||||
isStored
|
||||
? session.spawnBlocked
|
||||
? "The garage spawn lane is currently blocked."
|
||||
: "Retrieve this stored vehicle into the active spawn lane."
|
||||
: "Retrieve this stored vehicle into the active spawn lane before refuel or repair service."
|
||||
: currentSelection.isEmpty === false
|
||||
? "Only empty nearby vehicles can be stored."
|
||||
: "Store this nearby vehicle back into persistent garage storage.",
|
||||
: "Store this nearby vehicle or request organization-billed refuel and repair service.",
|
||||
),
|
||||
),
|
||||
h(
|
||||
|
||||
@ -159,6 +159,70 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
function requestRefuelSelected() {
|
||||
const selectedEntry = getSelectedEntry();
|
||||
if (!selectedEntry || selectedEntry.entryKind !== "nearby") {
|
||||
showNotice("error", "Select a nearby vehicle to refuel.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Number(selectedEntry.fuel || 0) >= 0.999) {
|
||||
showNotice("error", "Vehicle fuel tank is already full.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const bridge = GarageApp.bridge;
|
||||
if (!bridge || typeof bridge.requestRefuel !== "function") {
|
||||
showNotice("error", "Garage refuel bridge is unavailable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
store.startAction("refuel");
|
||||
const sent = bridge.requestRefuel({
|
||||
netId: selectedEntry.netId || "",
|
||||
});
|
||||
|
||||
if (!sent) {
|
||||
store.finishAction();
|
||||
showNotice("error", "Garage refuel bridge is unavailable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function requestRepairSelected() {
|
||||
const selectedEntry = getSelectedEntry();
|
||||
if (!selectedEntry || selectedEntry.entryKind !== "nearby") {
|
||||
showNotice("error", "Select a nearby vehicle to repair.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Number(selectedEntry.health || 0) >= 0.999) {
|
||||
showNotice("error", "Vehicle has no reported damage.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const bridge = GarageApp.bridge;
|
||||
if (!bridge || typeof bridge.requestRepair !== "function") {
|
||||
showNotice("error", "Garage repair bridge is unavailable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
store.startAction("repair");
|
||||
const sent = bridge.requestRepair({
|
||||
netId: selectedEntry.netId || "",
|
||||
});
|
||||
|
||||
if (!sent) {
|
||||
store.finishAction();
|
||||
showNotice("error", "Garage repair bridge is unavailable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
GarageApp.actions = {
|
||||
showNotice,
|
||||
closeGarage,
|
||||
@ -168,6 +232,8 @@
|
||||
selectCategory,
|
||||
selectEntry,
|
||||
getSelectedEntry,
|
||||
requestRefuelSelected,
|
||||
requestRepairSelected,
|
||||
requestRetrieveSelected,
|
||||
requestStoreSelected,
|
||||
};
|
||||
|
||||
@ -1,3 +1,27 @@
|
||||
# forge_client_locker
|
||||
# Forge Client Locker
|
||||
|
||||
Description for this addon
|
||||
## Overview
|
||||
The locker addon manages client repositories for personal locker state and
|
||||
virtual arsenal unlock state. It also integrates with ACE Arsenal display
|
||||
behavior.
|
||||
|
||||
## Dependencies
|
||||
- `forge_client_main`
|
||||
- ACE Arsenal
|
||||
- server locker events from `forge_server_locker`
|
||||
|
||||
## Main Components
|
||||
- `fnc_initRepository.sqf` manages locker state, container open/close behavior,
|
||||
and server sync requests.
|
||||
- `fnc_initVARepository.sqf` manages virtual arsenal state.
|
||||
|
||||
## Runtime Behavior
|
||||
- Requests locker and virtual arsenal state after actor load.
|
||||
- Syncs server responses into client repositories.
|
||||
- Sends locker override data to the server when a managed locker container is
|
||||
closed.
|
||||
- Hides selected ACE Arsenal controls when the arsenal display opens.
|
||||
|
||||
## Notes
|
||||
The client repository is display/input state. The server locker addon and
|
||||
extension own saved locker and virtual arsenal data.
|
||||
|
||||
@ -1,3 +1,18 @@
|
||||
# forge_client_main
|
||||
# Forge Client Main
|
||||
|
||||
Main Addon for forge-client
|
||||
## Overview
|
||||
The main addon provides shared mod metadata, macros, settings, and compile
|
||||
infrastructure for Forge client addons.
|
||||
|
||||
## Dependencies
|
||||
- `cba_main`
|
||||
|
||||
## Main Components
|
||||
- `script_macros.hpp` defines shared function, RPC, path, variable, and compile
|
||||
macros.
|
||||
- `script_mod.hpp` and `script_version.hpp` define mod identity and version.
|
||||
- `CfgSettings.hpp` contains client-side CBA settings.
|
||||
|
||||
## Notes
|
||||
Feature logic should live in the owning addon. Main is the shared foundation for
|
||||
configuration, macros, and mod-level metadata.
|
||||
|
||||
@ -1,3 +1,27 @@
|
||||
# forge_client_notifications
|
||||
# Forge Client Notifications
|
||||
|
||||
Description for this addon
|
||||
## Overview
|
||||
The notifications addon owns the client notification HUD, notification sound,
|
||||
and local notification service used by other Forge client and server modules.
|
||||
|
||||
## Dependencies
|
||||
- `forge_client_main`
|
||||
|
||||
## Main Components
|
||||
- `fnc_initService.sqf` manages queued and visible notifications.
|
||||
- `fnc_openUI.sqf` opens the notification HUD display.
|
||||
- `fnc_handleUIEvents.sqf` handles browser/HUD events.
|
||||
- `CfgSounds.hpp` defines the notification sound.
|
||||
|
||||
## Event Surface
|
||||
`forge_client_notifications_recieveNotification` accepts:
|
||||
|
||||
```sqf
|
||||
[_type, _title, _content, _duration]
|
||||
```
|
||||
|
||||
The event plays the configured sound and adds the notification to the HUD.
|
||||
|
||||
## Runtime Notes
|
||||
The HUD opens after the virtual arsenal repository is loaded. Other addons
|
||||
should use this notification event instead of creating their own transient UI.
|
||||
|
||||
@ -1,85 +1,33 @@
|
||||
# forge_client_org
|
||||
# Forge Client Organization
|
||||
|
||||
Player organization UI and client integration.
|
||||
## Overview
|
||||
The organization addon provides the client organization portal UI and bridge for
|
||||
organization hydrate, registration, membership, invitations, credit lines,
|
||||
leave/disband actions, assets, fleet, and treasury display.
|
||||
|
||||
## UI Login Contract
|
||||
## Dependencies
|
||||
- `forge_client_common`
|
||||
- `forge_client_main`
|
||||
- server organization events from `forge_server_org`
|
||||
- notifications for user feedback
|
||||
|
||||
The web UI sends the following request through `A3API.SendAlert`:
|
||||
## Main Components
|
||||
- `fnc_initRepository.sqf` caches organization portal state.
|
||||
- `fnc_initUIBridge.sqf` sends browser requests to server org RPCs and pushes
|
||||
hydrate/sync events back to the browser.
|
||||
- `fnc_handleUIEvents.sqf` handles `org::*` browser events.
|
||||
- `fnc_openUI.sqf` opens `RscOrg`.
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "org::login::request",
|
||||
"data": {
|
||||
"email": "admin@spearnet.mil",
|
||||
"password": "secret"
|
||||
}
|
||||
}
|
||||
```
|
||||
## Browser Events
|
||||
- `org::login::request`
|
||||
- `org::create::request`
|
||||
- `org::disband::request`
|
||||
- `org::leave::request`
|
||||
- `org::credit::request`
|
||||
- `org::invite::request`
|
||||
- `org::invite::accept`
|
||||
- `org::invite::decline`
|
||||
|
||||
On success, SQF should call the browser bridge with:
|
||||
|
||||
```sqf
|
||||
private _payload = createHashMapFromArray [
|
||||
["session", createHashMapFromArray [
|
||||
["actorName", name player],
|
||||
["role", "Leader"]
|
||||
]],
|
||||
["portalData", createHashMapFromArray [
|
||||
["org", createHashMapFromArray [
|
||||
["name", "Black Rifle Company"],
|
||||
["tag", "BRC-0160566824"],
|
||||
["type", "Private Military Company"],
|
||||
["status", "Operational"],
|
||||
["headquarters", "Georgetown Command Annex"],
|
||||
["owner", "Jacob Schmidt"]
|
||||
]],
|
||||
["funds", 482750],
|
||||
["reputation", 72],
|
||||
["members", [
|
||||
createHashMapFromArray [["name", "Jacob Schmidt"]],
|
||||
createHashMapFromArray [["name", "Mara Velez"]]
|
||||
]],
|
||||
["fleet", [
|
||||
createHashMapFromArray [
|
||||
["name", "UH-80 Ghost Hawk"],
|
||||
["type", "helicopter"],
|
||||
["status", "Ready"],
|
||||
["damage", "16%"]
|
||||
]
|
||||
]],
|
||||
["assets", [
|
||||
createHashMapFromArray [
|
||||
["name", "First Aid Kits"],
|
||||
["type", "items"],
|
||||
["quantity", "36"]
|
||||
]
|
||||
]],
|
||||
["activity", []],
|
||||
["roadmap", []]
|
||||
]]
|
||||
];
|
||||
|
||||
_control ctrlWebBrowserAction [
|
||||
"ExecJS",
|
||||
format ["OrgUIBridge.receiveLoginSuccess(%1)", toJSON _payload]
|
||||
];
|
||||
```
|
||||
|
||||
On failure:
|
||||
|
||||
```sqf
|
||||
private _payload = createHashMapFromArray [
|
||||
["message", "Invalid credentials."]
|
||||
];
|
||||
|
||||
_control ctrlWebBrowserAction [
|
||||
"ExecJS",
|
||||
format ["OrgUIBridge.receiveLoginFailure(%1)", toJSON _payload]
|
||||
];
|
||||
```
|
||||
|
||||
Current implementation:
|
||||
|
||||
- `fnc_handleUIEvents.sqf` now handles `org::login::request`
|
||||
- success hydrates the portal with `session` + `portalData`
|
||||
- failure returns a single `message` string for inline UI feedback
|
||||
## Runtime Notes
|
||||
The client portal is a view/controller. Organization state, funds, reputation,
|
||||
credit lines, assets, fleet, and membership are authoritative on the server.
|
||||
|
||||
@ -1,4 +1,29 @@
|
||||
forge_client_phone
|
||||
===================
|
||||
# Forge Client Phone
|
||||
|
||||
This addon provides the phone user interface and functionality for the in-game phone system. It handles all phone-related features including the UI display, interactions, and core phone operations.
|
||||
## Overview
|
||||
The phone addon provides the in-game phone UI for contacts, SMS messages, and
|
||||
email. It keeps a local `PhoneClass` facade for view state and sends all
|
||||
authoritative operations to the server phone addon.
|
||||
|
||||
## Dependencies
|
||||
- `forge_client_main`
|
||||
- server phone events from `forge_server_phone`
|
||||
- notifications for contact/message/email feedback
|
||||
|
||||
## Main Components
|
||||
- `fnc_initClass.sqf` initializes the local phone facade.
|
||||
- `fnc_handleUIEvents.sqf` translates browser events into server phone RPCs.
|
||||
- `fnc_openUI.sqf` opens `RscPhone`.
|
||||
- `ui/_site` contains the browser phone UI source.
|
||||
|
||||
## Supported Operations
|
||||
- initialize and sync phone state
|
||||
- refresh contacts
|
||||
- add/remove contacts by UID, phone number, or email
|
||||
- send, read, and delete SMS messages
|
||||
- send, read, and delete email
|
||||
- push incoming message/email updates into the browser UI
|
||||
|
||||
## Runtime Notes
|
||||
Phone data is owned by the server extension. Client state is only used to render
|
||||
the phone UI and provide immediate feedback.
|
||||
|
||||
@ -1,3 +1,28 @@
|
||||
# forge_client_store
|
||||
# Forge Client Store
|
||||
|
||||
Description for this addon
|
||||
## Overview
|
||||
The store addon provides the client storefront UI for catalog browsing,
|
||||
category loading, payment-source display, cart handling, and checkout requests.
|
||||
|
||||
## Dependencies
|
||||
- `forge_client_common`
|
||||
- `forge_client_main`
|
||||
- server store events from `forge_server_store`
|
||||
- bank/org/locker/garage server state through checkout results
|
||||
|
||||
## Main Components
|
||||
- `fnc_initUIBridge.sqf` handles browser readiness, category requests, checkout
|
||||
requests, and server responses.
|
||||
- `fnc_handleUIEvents.sqf` handles `store::*` browser events.
|
||||
- `fnc_openUI.sqf` opens `RscStore`.
|
||||
|
||||
## Browser Events
|
||||
- `store::ready`
|
||||
- `store::category::request`
|
||||
- `store::checkout::request`
|
||||
- `store::close`
|
||||
|
||||
## Runtime Notes
|
||||
The client never calculates authoritative checkout results. The server store
|
||||
addon and extension validate prices, charge payment sources, grant assets, and
|
||||
return patches for the UI.
|
||||
|
||||
@ -1,54 +1,47 @@
|
||||
<!-- If you want to make changes to this README, you need to also modify the README.md in the docs folder as well -->
|
||||
# Forge Client Documentation
|
||||
|
||||
<h1 align="center">forge-client</h1>
|
||||
<p align="center">
|
||||
<a href="https://github.com/IDSolutions/MOD_REPO/releases/latest">
|
||||
<img src="https://img.shields.io/badge/Version-0.0.0-blue?style=flat-square" alt="forge-client Version">
|
||||
</a>
|
||||
<a href="https://github.com/IDSolutions/MOD_REPO/issues">
|
||||
<img src="https://img.shields.io/github/issues-raw/IDSolutions/MOD_REPO.svg?style=flat-square&label=Issues" alt="forge-client Issues">
|
||||
</a>
|
||||
<a href="https://steamcommunity.com/sharedfiles/filedetails/?id=MOD_ID">
|
||||
<img src="https://img.shields.io/steam/downloads/MOD_ID.svg?style=flat-square&label=Downloads" alt="forge-client Downloads">
|
||||
</a>
|
||||
<a href="https://github.com/IDSolutions/MOD_REPO/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/badge/License-APL ND-red?style=flat-square" alt="forge-client License">
|
||||
</a>
|
||||
<br>
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/IDSolutions/MOD_REPO/check.yml?style=flat-square&label=HEMTT" alt="HEMTT">
|
||||
</p>
|
||||
This folder documents the Arma client mod. The client side is responsible for
|
||||
displaying UI, handling player input, caching client-visible state, and sending
|
||||
CBA events to server addons.
|
||||
|
||||
<p align="center">
|
||||
<b>Requires the latest version of <a href="https://github.com/CBATeam/CBA_A3/releases/latest">CBA A3</a></b>
|
||||
</p>
|
||||
Authoritative gameplay state lives on the server side or in the Rust extension.
|
||||
Client repositories should be treated as view state, not durable storage.
|
||||
|
||||
# Initial Project Setup!
|
||||
## Architecture
|
||||
- Each addon declares its own UI resources and CBA extended event handlers.
|
||||
- `XEH_preStart.sqf`/`XEH_preInit.sqf` compile functions.
|
||||
- `XEH_postInitClient.sqf` initializes client repositories, UI bridges, and
|
||||
response event handlers.
|
||||
- Browser UIs send JSON events through A3API.
|
||||
- SQF handlers translate browser events into local actions or server RPCs.
|
||||
- Server responses update repositories and push browser events back into the UI.
|
||||
|
||||
Delete this section after the project has been initially set up:
|
||||
## Addon Docs
|
||||
- [Main](../addons/main/README.md)
|
||||
- [Common](../addons/common/README.md)
|
||||
- [Actor](../addons/actor/README.md)
|
||||
- [Bank](../addons/bank/README.md)
|
||||
- [CAD](../addons/cad/README.md)
|
||||
- [Garage](../addons/garage/README.md)
|
||||
- [Locker](../addons/locker/README.md)
|
||||
- [Notifications](../addons/notifications/README.md)
|
||||
- [Organization](../addons/org/README.md)
|
||||
- [Phone](../addons/phone/README.md)
|
||||
- [Store](../addons/store/README.md)
|
||||
|
||||
1. Find and replace all instances of `forge-client` with the mod's name.
|
||||
2. Find and replace all instances of `MOD_REPO` with the mod's name _and no spaces_.
|
||||
- This should be the name of the repository on GitHub.
|
||||
3. Find and replace all instances of `forge_client` with the mod's prefix.
|
||||
- This should be all lowercase.
|
||||
4. Find and replace all instances of `MOD_ACRONYM` with the mod's acronym.
|
||||
- This should be all uppercase.
|
||||
5. After the initial Steam upload, find and replace all instances of `MOD_ID` with the mod's Steam Workshop id.
|
||||
|
||||
For third parties, make sure to also replace `IDSolutions` with your Github username / organization name, and to replace `DartRuffian` with your username.
|
||||
|
||||
**forge-client** (MOD_ACRONYM) aims to...
|
||||
|
||||
The project is entirely **open-source** and any contributions are welcome.
|
||||
|
||||
## Core Features
|
||||
|
||||
- Feature
|
||||
|
||||
## Contributing
|
||||
|
||||
For new contributers, see the [Contributing Setup & Guidelines](./.github/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
forge-client is licensed under [APL-ND](./LICENSE.md).
|
||||
## Related Docs
|
||||
- [Root Client Usage Guide](../../../docs/CLIENT_USAGE_GUIDE.md)
|
||||
- [Root Client Main Usage Guide](../../../docs/CLIENT_MAIN_USAGE_GUIDE.md)
|
||||
- [Root Client Common Usage Guide](../../../docs/CLIENT_COMMON_USAGE_GUIDE.md)
|
||||
- [Root Client Actor Usage Guide](../../../docs/CLIENT_ACTOR_USAGE_GUIDE.md)
|
||||
- [Root Client Bank Usage Guide](../../../docs/CLIENT_BANK_USAGE_GUIDE.md)
|
||||
- [Root Client CAD Usage Guide](../../../docs/CLIENT_CAD_USAGE_GUIDE.md)
|
||||
- [Root Client Garage Usage Guide](../../../docs/CLIENT_GARAGE_USAGE_GUIDE.md)
|
||||
- [Root Client Locker Usage Guide](../../../docs/CLIENT_LOCKER_USAGE_GUIDE.md)
|
||||
- [Root Client Notifications Usage Guide](../../../docs/CLIENT_NOTIFICATIONS_USAGE_GUIDE.md)
|
||||
- [Root Client Organization Usage Guide](../../../docs/CLIENT_ORG_USAGE_GUIDE.md)
|
||||
- [Root Client Phone Usage Guide](../../../docs/CLIENT_PHONE_USAGE_GUIDE.md)
|
||||
- [Root Client Store Usage Guide](../../../docs/CLIENT_STORE_USAGE_GUIDE.md)
|
||||
- [Shared web UI framework notes](../addons/common/WEB_UI_FRAMEWORK.md)
|
||||
- [CAD map integration notes](../addons/cad/MAP_README.md)
|
||||
- [Root framework docs](../../../docs/README.md)
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
# forge_client_addonName
|
||||
# Forge Client Example Addon
|
||||
|
||||
Description for this addon
|
||||
This directory is a template for creating a new Forge client addon.
|
||||
|
||||
Use it as a starting point for addon structure, config layout, event handler
|
||||
files, and function preparation. Replace the component names, display strings,
|
||||
and placeholder implementation with the new addon's real feature behavior.
|
||||
|
||||
Do not ship this example addon as a gameplay module.
|
||||
|
||||
@ -2,30 +2,93 @@
|
||||
|
||||
## Overview
|
||||
The economy addon contains server-side systems for world economic interactions
|
||||
that are still implemented in SQF.
|
||||
that are still implemented in SQF. It owns Arma-world behavior such as active
|
||||
refueling sessions, medical spawn occupancy, respawn placement, and death
|
||||
inventory handling.
|
||||
|
||||
Current stores cover fuel tracking, medical service behavior, and a placeholder
|
||||
service economy store.
|
||||
Current stores cover fuel tracking, medical service behavior, and service
|
||||
charges such as repairs.
|
||||
|
||||
## Dependencies
|
||||
- `forge_server_main`
|
||||
- `forge_server_common` at runtime for logging, formatting, and player lookup
|
||||
- `forge_server_bank` at runtime for medical service charges
|
||||
- `forge_server_common` for logging, formatting, and player lookup
|
||||
- `forge_server_bank` for player-funded medical billing
|
||||
- `forge_server_org` for extension-backed organization hot-cache charges
|
||||
- `forge_client_actor` and `forge_client_notifications` for response RPCs
|
||||
|
||||
## Main Components
|
||||
- `fnc_initFEconomyStore.sqf` tracks active refueling sessions and reports fuel
|
||||
totals.
|
||||
- `fnc_initFEconomyStore.sqf` tracks active refueling sessions, calculates fuel
|
||||
totals, charges the player's organization through `OrgStore`, syncs the org
|
||||
patch, and rolls fuel back to the starting level when organization funds
|
||||
cannot cover the refuel.
|
||||
- `fnc_initMEconomyStore.sqf` manages medical spawn occupancy, healing charges,
|
||||
respawn placement, death inventory handling, and body-bag transfer.
|
||||
- `fnc_initSEconomyStore.sqf` initializes the service economy placeholder.
|
||||
respawn placement, death inventory handling, and body-bag transfer. Medical
|
||||
charges use player bank/cash first, then organization funds with repayable
|
||||
member debt only when the player cannot cover the service.
|
||||
- `fnc_initSEconomyStore.sqf` handles organization-funded service charges and
|
||||
repairs. Repairs only apply after the organization charge succeeds. The
|
||||
shared org-charge helper can also record member debt for medical fallback.
|
||||
|
||||
## Event Surface
|
||||
The addon registers CBA server events for fuel start/tick/stop, player killed,
|
||||
player respawn, and healing. Medical store initialization runs after post-init
|
||||
to discover configured medical spawn objects.
|
||||
The addon registers CBA server events for fuel start/tick/stop, direct refuel
|
||||
service, repair service, player killed, player respawn, and healing. Medical
|
||||
store initialization runs after post-init to discover configured medical spawn
|
||||
objects.
|
||||
|
||||
Repair service requests use:
|
||||
|
||||
```sqf
|
||||
[QEGVAR(economy,RepairService), [_target, _unit, _cost]] call CBA_fnc_serverEvent;
|
||||
```
|
||||
|
||||
`_cost` is optional. Passing `-1` uses the configured service repair cost.
|
||||
|
||||
Garage refuel service requests use:
|
||||
|
||||
```sqf
|
||||
[QEGVAR(economy,RefuelService), [_target, _unit]] call CBA_fnc_serverEvent;
|
||||
```
|
||||
|
||||
This fills the selected live vehicle after organization billing succeeds.
|
||||
|
||||
## Billing Rules
|
||||
Economy does not own durable money state. It coordinates Arma-world effects
|
||||
after the relevant hot-cache charge succeeds.
|
||||
|
||||
Fuel and repair services are organization-funded:
|
||||
|
||||
1. Resolve the player's organization from actor state.
|
||||
2. Ensure the player is a member of that organization hot record.
|
||||
3. Call `OrgStore chargeCheckout` with `source = "org_funds"`,
|
||||
`commit = true`, and member service charging enabled.
|
||||
4. Send the returned organization patch to online members.
|
||||
5. If the charge fails, do not complete the service. Refueling rolls the target
|
||||
back to its starting fuel level; repairs are not applied.
|
||||
|
||||
Direct refuel service requests, such as those from the garage UI, calculate
|
||||
the missing fuel from `fuelCapacity`, charge the organization, and fill the
|
||||
vehicle only after the charge succeeds.
|
||||
|
||||
Medical services are player-funded first:
|
||||
|
||||
1. Load the player's bank hot state.
|
||||
2. Charge the player's bank balance when it can cover the medical bill.
|
||||
3. Otherwise charge the player's cash when it can cover the bill.
|
||||
4. If neither personal balance can cover the bill, charge organization funds
|
||||
and record the same amount as a debt on the player's organization credit
|
||||
line.
|
||||
5. If personal billing is unavailable, or both personal and organization funds
|
||||
fail, do not complete the heal.
|
||||
|
||||
The organization fallback reduces org funds immediately and adds the medical
|
||||
cost to the player's credit-line balance due. Repayment uses the normal bank
|
||||
credit-line repayment flow, which moves player bank funds back into the
|
||||
organization treasury.
|
||||
|
||||
This keeps money mutation rules in the extension-backed organization service
|
||||
and bank service while leaving world interactions in SQF.
|
||||
|
||||
## Notes
|
||||
The service economy store is currently a stub. Fuel and medical behavior should
|
||||
stay server-authoritative because they mutate money, inventory, and respawn
|
||||
state.
|
||||
Fuel, medical, and service world behavior should stay server-authoritative
|
||||
because it mutates inventory, vehicles, and respawn state. Money mutations
|
||||
should continue to use extension-backed bank and organization hot state.
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
PREP(initFEconomyStore);
|
||||
PREP(initMEconomyStore);
|
||||
PREP(initSEconomyStore);
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
#include "script_component.hpp"
|
||||
|
||||
if !(isNil QGVAR(MEconomyStore)) then {
|
||||
GVAR(MEconomyStore) call ["init", []];
|
||||
};
|
||||
|
||||
@ -8,7 +8,7 @@ PREP_RECOMPILE_END;
|
||||
|
||||
if (isNil QGVAR(MEconomyStore)) then { call FUNC(initMEconomyStore); };
|
||||
if (isNil QGVAR(FEconomyStore)) then { call FUNC(initFEconomyStore); };
|
||||
// if (isNil QGVAR(SEconomyStore)) then { call FUNC(initSEconomyStore); };
|
||||
if (isNil QGVAR(SEconomyStore)) then { call FUNC(initSEconomyStore); };
|
||||
|
||||
[QGVAR(FuelStart), {
|
||||
params ["_source", "_target", "_unit"];
|
||||
@ -28,6 +28,16 @@ if (isNil QGVAR(FEconomyStore)) then { call FUNC(initFEconomyStore); };
|
||||
GVAR(FEconomyStore) call ["stop", [_source, _target]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(RepairService), {
|
||||
params ["_target", "_unit", ["_cost", -1, [0]]];
|
||||
GVAR(SEconomyStore) call ["repair", [_target, _unit, _cost]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(RefuelService), {
|
||||
params ["_target", "_unit"];
|
||||
GVAR(FEconomyStore) call ["refuel", [_target, _unit]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(onKilled), {
|
||||
params ["_unit"];
|
||||
GVAR(MEconomyStore) call ["onKilled", [_unit]];
|
||||
|
||||
@ -8,7 +8,8 @@ class CfgPatches {
|
||||
name = COMPONENT_NAME;
|
||||
requiredVersion = REQUIRED_VERSION;
|
||||
requiredAddons[] = {
|
||||
"forge_server_main"
|
||||
"forge_server_main",
|
||||
"forge_server_common"
|
||||
};
|
||||
units[] = {};
|
||||
weapons[] = {};
|
||||
|
||||
@ -4,20 +4,23 @@
|
||||
* File: fnc_initFEconomyStore.sqf
|
||||
* Author: IDSolutions
|
||||
* Date: 2025-12-20
|
||||
* Last Update: 2026-01-03
|
||||
* Last Update: 2026-04-18
|
||||
* Public: No
|
||||
*
|
||||
* Description:
|
||||
* No description added yet.
|
||||
* Initializes the fuel economy store. Active refueling sessions remain
|
||||
* server-local; payment is routed through the organization extension hot
|
||||
* cache. Garage service refuels use the same organization billing path
|
||||
* and only fill the vehicle after the charge succeeds.
|
||||
*
|
||||
* Parameter(s):
|
||||
* N/A
|
||||
*
|
||||
* Returns:
|
||||
* Something [BOOL]
|
||||
* Fuel economy store object [HASHMAP OBJECT]
|
||||
*
|
||||
* Example(s):
|
||||
* [parameter] call forge_x_component_fnc_myFunction
|
||||
* call forge_server_economy_fnc_initFEconomyStore
|
||||
*/
|
||||
|
||||
#pragma hemtt ignore_variables ["_self"]
|
||||
@ -36,23 +39,103 @@ GVAR(FEconomyStore) = createHashMapObject [[
|
||||
private _uid = getPlayerUID _unit;
|
||||
private _fuelRegistry = _self getOrDefault ["fuelRegistry", createHashMap];
|
||||
|
||||
_fuelRegistry set [_index, _uid];
|
||||
_fuelRegistry set [_index, createHashMapFromArray [
|
||||
["uid", _uid],
|
||||
["initialFuel", fuel _target]
|
||||
]];
|
||||
SETVAR(_target,liters,0);
|
||||
}],
|
||||
["rollbackFuel", {
|
||||
params [["_target", objNull, [objNull]], ["_initialFuel", 0, [0]]];
|
||||
|
||||
if (isNull _target) exitWith { false };
|
||||
|
||||
_target setFuel (_initialFuel max 0 min 1);
|
||||
SETVAR(_target,liters,0);
|
||||
true
|
||||
}],
|
||||
["refuel", {
|
||||
params [["_target", objNull, [objNull]], ["_unit", objNull, [objNull]]];
|
||||
|
||||
if (isNull _target || { isNull _unit }) exitWith { false };
|
||||
|
||||
private _currentFuel = fuel _target;
|
||||
private _missingFuel = (1 - _currentFuel) max 0 min 1;
|
||||
if (_missingFuel <= 0.001) exitWith {
|
||||
[CRPC(notifications,recieveNotification), ["info", "Refueling", "Vehicle fuel tank is already full."], _unit] call CFUNC(targetEvent);
|
||||
false
|
||||
};
|
||||
|
||||
if (isNil QGVAR(SEconomyStore)) exitWith {
|
||||
["ERROR", "Service economy store unavailable for garage refueling charge.", nil, nil] call EFUNC(common,log);
|
||||
[CRPC(notifications,recieveNotification), ["danger", "Refueling", "Organization billing is unavailable. Refueling was not completed."], _unit] call CFUNC(targetEvent);
|
||||
false
|
||||
};
|
||||
|
||||
private _fuelCapacity = getNumber (configOf _target >> "fuelCapacity");
|
||||
if (_fuelCapacity <= 0) then { _fuelCapacity = 100; };
|
||||
|
||||
private _totalLiters = _missingFuel * _fuelCapacity;
|
||||
private _totalCost = _totalLiters * GVAR(FuelCost);
|
||||
private _chargeResult = GVAR(SEconomyStore) call ["chargeOrg", [_unit, _totalCost, "Refueling"]];
|
||||
if !(_chargeResult getOrDefault ["success", false]) exitWith {
|
||||
[CRPC(notifications,recieveNotification), ["danger", "Refueling", _chargeResult getOrDefault ["message", "Organization funds cannot cover this refuel. Refueling was not completed."]], _unit] call CFUNC(targetEvent);
|
||||
false
|
||||
};
|
||||
|
||||
_target setFuel 1;
|
||||
SETVAR(_target,liters,0);
|
||||
|
||||
private _formattedTotalCost = [_totalCost] call EFUNC(common,formatNumber);
|
||||
private _formattedTotalLiters = _totalLiters toFixed 2;
|
||||
[CRPC(notifications,recieveNotification), ["info", "Refueling", format ["Refueling complete: %1L<br />Organization charged $%2.", _formattedTotalLiters, _formattedTotalCost]], _unit] call CFUNC(targetEvent);
|
||||
true
|
||||
}],
|
||||
["stop", {
|
||||
params ["_source", "_target"];
|
||||
|
||||
private _index = netId _target;
|
||||
private _fuelRegistry = _self getOrDefault ["fuelRegistry", createHashMap];
|
||||
private _uid = _fuelRegistry get _index;
|
||||
private _session = _fuelRegistry getOrDefault [_index, createHashMap];
|
||||
if (_session isEqualType "") then {
|
||||
_session = createHashMapFromArray [["uid", _session], ["initialFuel", fuel _target]];
|
||||
};
|
||||
|
||||
private _uid = _session getOrDefault ["uid", ""];
|
||||
private _initialFuel = _session getOrDefault ["initialFuel", fuel _target];
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
|
||||
private _totalLiters = GETVAR(_target,liters,0);
|
||||
private _totalCost = _totalLiters * 5;
|
||||
private _totalCost = _totalLiters * GVAR(FuelCost);
|
||||
private _formattedTotalCost = [_totalCost] call EFUNC(common,formatNumber);
|
||||
private _formattedTotalLiters = _totalLiters toFixed 2;
|
||||
|
||||
[CRPC(notifications,recieveNotification), ["info", "Refueling", format ["Refueling complete: %1L<br />Total Cost: $%2", _formattedTotalLiters, _formattedTotalCost]], _player] call CFUNC(targetEvent);
|
||||
if (isNull _player || { _uid isEqualTo "" }) exitWith {
|
||||
["WARNING", format ["Unable to resolve refueling player for vehicle %1.", _index], nil, nil] call EFUNC(common,log);
|
||||
_self call ["rollbackFuel", [_target, _initialFuel]];
|
||||
_fuelRegistry deleteAt _index;
|
||||
};
|
||||
|
||||
if (_totalCost <= 0) exitWith {
|
||||
[CRPC(notifications,recieveNotification), ["info", "Refueling", format ["Refueling complete: %1L", _formattedTotalLiters]], _player] call CFUNC(targetEvent);
|
||||
_fuelRegistry deleteAt _index;
|
||||
};
|
||||
|
||||
if (isNil QGVAR(SEconomyStore)) exitWith {
|
||||
["ERROR", "Service economy store unavailable for refueling charge.", nil, nil] call EFUNC(common,log);
|
||||
[CRPC(notifications,recieveNotification), ["danger", "Refueling", "Organization billing is unavailable. Refueling was not completed."], _player] call CFUNC(targetEvent);
|
||||
_self call ["rollbackFuel", [_target, _initialFuel]];
|
||||
_fuelRegistry deleteAt _index;
|
||||
};
|
||||
|
||||
private _chargeResult = GVAR(SEconomyStore) call ["chargeOrg", [_player, _totalCost, "Refueling"]];
|
||||
if !(_chargeResult getOrDefault ["success", false]) exitWith {
|
||||
[CRPC(notifications,recieveNotification), ["danger", "Refueling", _chargeResult getOrDefault ["message", "Organization funds cannot cover this refuel. Refueling was not completed."]], _player] call CFUNC(targetEvent);
|
||||
_self call ["rollbackFuel", [_target, _initialFuel]];
|
||||
_fuelRegistry deleteAt _index;
|
||||
};
|
||||
|
||||
[CRPC(notifications,recieveNotification), ["info", "Refueling", format ["Refueling complete: %1L<br />Organization charged $%2.", _formattedTotalLiters, _formattedTotalCost]], _player] call CFUNC(targetEvent);
|
||||
_fuelRegistry deleteAt _index;
|
||||
}]
|
||||
]];
|
||||
|
||||
@ -4,20 +4,23 @@
|
||||
* File: fnc_initMEconomyStore.sqf
|
||||
* Author: IDSolutions
|
||||
* Date: 2025-12-20
|
||||
* Last Update: 2026-02-13
|
||||
* Last Update: 2026-04-18
|
||||
* Public: No
|
||||
*
|
||||
* Description:
|
||||
* No description added yet.
|
||||
* Initializes the medical economy store. Respawn, body-bag, and spawn
|
||||
* occupancy behavior remains server-local, while money mutations are
|
||||
* routed through player bank hot state first, then organization hot state
|
||||
* with a repayable member debt when personal funds cannot cover the bill.
|
||||
*
|
||||
* Parameter(s):
|
||||
* N/A
|
||||
*
|
||||
* Returns:
|
||||
* Something [BOOL]
|
||||
* Medical economy store object [HASHMAP OBJECT]
|
||||
*
|
||||
* Example(s):
|
||||
* [parameter] call forge_x_component_fnc_myFunction
|
||||
* call forge_server_economy_fnc_initMEconomyStore
|
||||
*/
|
||||
|
||||
#pragma hemtt ignore_variables ["_self"]
|
||||
@ -63,37 +66,105 @@ GVAR(MEconomyStore) = createHashMapObject [[
|
||||
} forEach _mSpawns;
|
||||
};
|
||||
}],
|
||||
["chargePlayer", {
|
||||
params [["_uid", "", [""]], ["_amount", 0, [0]]];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["success", false],
|
||||
["fallbackEligible", false],
|
||||
["source", ""],
|
||||
["message", "Unable to charge personal funds."]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "") exitWith {
|
||||
_result set ["message", "A valid player UID is required for medical billing."];
|
||||
_result
|
||||
};
|
||||
if (_amount <= 0) exitWith {
|
||||
_result set ["success", true];
|
||||
_result
|
||||
};
|
||||
if (isNil QEGVAR(bank,BankStore)) exitWith {
|
||||
_result set ["message", "Personal billing is unavailable. Medical service cannot complete."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _account = EGVAR(bank,BankStore) call ["get", [_uid, ""]];
|
||||
if (_account isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Personal account could not be loaded for medical billing."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _source = "";
|
||||
if ((_account getOrDefault ["bank", 0]) >= _amount) then {
|
||||
_source = "bank";
|
||||
} else {
|
||||
if ((_account getOrDefault ["cash", 0]) >= _amount) then {
|
||||
_source = "cash";
|
||||
};
|
||||
};
|
||||
|
||||
if (_source isEqualTo "") exitWith {
|
||||
_result set ["fallbackEligible", true];
|
||||
_result set ["message", "Personal bank and cash balances cannot cover this medical service."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _charge = EGVAR(bank,BankStore) call ["chargeCheckout", [_uid, _source, _amount, true]];
|
||||
if !(_charge getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _charge getOrDefault ["message", "Personal funds could not be charged for medical service."]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _patch = _charge getOrDefault ["patch", createHashMap];
|
||||
if (_patch isNotEqualTo createHashMap && { !(isNil QEGVAR(bank,BankMessenger)) }) then {
|
||||
EGVAR(bank,BankMessenger) call ["sendAccountSync", [_uid, _patch]];
|
||||
};
|
||||
|
||||
private _savedAccount = EGVAR(bank,BankStore) call ["save", [_uid]];
|
||||
if (_savedAccount isEqualTo createHashMap) then {
|
||||
["ERROR", format ["Medical charge for %1 succeeded in hot bank state, but durable bank save failed.", _uid]] call EFUNC(common,log);
|
||||
};
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["source", _source];
|
||||
_result set ["message", ""];
|
||||
_result
|
||||
}],
|
||||
["onHealed", {
|
||||
params [["_unit", objNull, [objNull]]];
|
||||
|
||||
if (isNull _unit) exitWith { ["WARNING", format ["Invalid unit provided: %1", (name _unit)], nil, nil] call EFUNC(common,log); };
|
||||
|
||||
private _uid = getPlayerUID _unit;
|
||||
private _account = EGVAR(bank,BankStore) call ["get", [_uid, ""]];
|
||||
if (_account isEqualTo createHashMap) then {
|
||||
_account = EGVAR(bank,BankStore) call ["init", [_uid]];
|
||||
};
|
||||
|
||||
if (_account isEqualTo createHashMap) exitWith { ["ERROR", format ["No account found for %1. UID: %2", (name _unit), _uid], nil, nil] call EFUNC(common,log); };
|
||||
|
||||
private _bank = _account get "bank";
|
||||
private _cash = _account get "cash";
|
||||
if (_uid isEqualTo "") exitWith { ["WARNING", "Unable to charge medical service for unit without UID.", nil, nil] call EFUNC(common,log); };
|
||||
|
||||
private _healCost = 100;
|
||||
private _newBalance = 0;
|
||||
|
||||
if (_bank < _healCost && _cash < _healCost) exitWith {
|
||||
[CRPC(notifications,recieveNotification), ["danger", "Insufficient Funds", format ["Insufficient funds for %1. Bank: $%2, Cash: $%3, Required: $%4", (name _unit), [_bank] call EFUNC(common,formatNumber), [_cash] call EFUNC(common,formatNumber), [_healCost] call EFUNC(common,formatNumber)]], _unit] call CFUNC(targetEvent);
|
||||
private _personalCharge = _self call ["chargePlayer", [_uid, _healCost]];
|
||||
if (_personalCharge getOrDefault ["success", false]) exitWith {
|
||||
private _sourceLabel = ["cash", "bank"] select ((_personalCharge getOrDefault ["source", "bank"]) isEqualTo "bank");
|
||||
[CRPC(notifications,recieveNotification), ["info", "Medical Billing", format ["Medical service charged $%1 from your %2.", [_healCost] call EFUNC(common,formatNumber), _sourceLabel]], _unit] call CFUNC(targetEvent);
|
||||
[CRPC(actor,onActorHealed), [], _unit] call CFUNC(targetEvent);
|
||||
};
|
||||
|
||||
if (_bank >= _healCost) then {
|
||||
_newBalance = _bank - _healCost;
|
||||
_account set ["bank", _newBalance];
|
||||
} else {
|
||||
_newBalance = _cash - _healCost;
|
||||
_account set ["cash", _newBalance];
|
||||
if !(_personalCharge getOrDefault ["fallbackEligible", false]) exitWith {
|
||||
private _message = _personalCharge getOrDefault ["message", "Personal funds could not be charged for medical service."];
|
||||
[CRPC(notifications,recieveNotification), ["danger", "Medical Billing", _message], _unit] call CFUNC(targetEvent);
|
||||
};
|
||||
|
||||
if (isNil QGVAR(SEconomyStore)) exitWith {
|
||||
["ERROR", "Service economy store unavailable for medical organization fallback charge.", nil, nil] call EFUNC(common,log);
|
||||
[CRPC(notifications,recieveNotification), ["danger", "Medical Billing", "Organization billing is unavailable. Medical service cannot complete."], _unit] call CFUNC(targetEvent);
|
||||
};
|
||||
|
||||
private _chargeResult = GVAR(SEconomyStore) call ["chargeOrg", [_unit, _healCost, "Medical", true]];
|
||||
if !(_chargeResult getOrDefault ["success", false]) exitWith {
|
||||
private _message = _chargeResult getOrDefault ["message", "Organization funds cannot cover this medical service."];
|
||||
[CRPC(notifications,recieveNotification), ["danger", "Medical Billing", _message], _unit] call CFUNC(targetEvent);
|
||||
};
|
||||
|
||||
[CRPC(notifications,recieveNotification), ["info", "Medical Billing", format ["Personal funds could not cover medical service. Organization charged $%1; repay it through your organization credit line.", [_healCost] call EFUNC(common,formatNumber)]], _unit] call CFUNC(targetEvent);
|
||||
[CRPC(actor,onActorHealed), [], _unit] call CFUNC(targetEvent);
|
||||
}],
|
||||
["onRespawn", {
|
||||
|
||||
@ -1,31 +1,136 @@
|
||||
#include "..\script_component.hpp"
|
||||
|
||||
/*
|
||||
* File: initSEconomyStore.sqf
|
||||
* File: fnc_initSEconomyStore.sqf
|
||||
* Author: IDSolutions
|
||||
* Date: 2025-12-20
|
||||
* Last Update: 2026-01-03
|
||||
* Last Update: 2026-04-18
|
||||
* Public: No
|
||||
*
|
||||
* Description:
|
||||
* No description added yet.
|
||||
* Initializes the service economy store for organization-funded world
|
||||
* services such as repairs, with optional member debt recording for
|
||||
* organization-covered medical fallback charges.
|
||||
*
|
||||
* Parameter(s):
|
||||
* N/A
|
||||
*
|
||||
* Returns:
|
||||
* Something [BOOL]
|
||||
* Service economy store object [HASHMAP OBJECT]
|
||||
*
|
||||
* Example(s):
|
||||
* [parameter] call forge_x_component_fnc_myFunction
|
||||
* call forge_server_economy_fnc_initSEconomyStore
|
||||
*/
|
||||
|
||||
#pragma hemtt ignore_variables ["_self"]
|
||||
GVAR(SEconomyStore) = createHashMapObject [[
|
||||
["#type", "IServiceEconomy"],
|
||||
["#create", {
|
||||
GVAR(ServiceRepairCost) = 500;
|
||||
["INFO", "Service Store Initialized!", nil, nil] call EFUNC(common,log);
|
||||
}],
|
||||
["notify", {
|
||||
params [["_unit", objNull, [objNull]], ["_type", "info", [""]], ["_title", "Service", [""]], ["_message", "", [""]]];
|
||||
|
||||
if (isNull _unit || { _message isEqualTo "" }) exitWith { false };
|
||||
|
||||
[CRPC(notifications,recieveNotification), [_type, _title, _message], _unit] call CFUNC(targetEvent);
|
||||
true
|
||||
}],
|
||||
["syncOrgPatch", {
|
||||
params [["_result", createHashMap, [createHashMap]]];
|
||||
|
||||
private _patch = _result getOrDefault ["patch", createHashMap];
|
||||
if ((keys _patch) isEqualTo []) exitWith { false };
|
||||
|
||||
{
|
||||
private _memberPlayer = [_x] call EFUNC(common,getPlayer);
|
||||
if (_memberPlayer isNotEqualTo objNull) then {
|
||||
[CRPC(org,responseSyncOrg), [_patch], _memberPlayer] call CFUNC(targetEvent);
|
||||
};
|
||||
} forEach (_result getOrDefault ["memberUids", []]);
|
||||
|
||||
true
|
||||
}],
|
||||
["chargeOrg", {
|
||||
params [
|
||||
["_unit", objNull, [objNull]],
|
||||
["_amount", 0, [0]],
|
||||
["_label", "Service", [""]],
|
||||
["_recordDebt", false, [false]]
|
||||
];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["success", false],
|
||||
["message", "Unable to charge organization funds."],
|
||||
["patch", createHashMap],
|
||||
["memberUids", []],
|
||||
["persisted", false],
|
||||
["persistenceMessage", ""]
|
||||
];
|
||||
|
||||
if (isNull _unit) exitWith {
|
||||
_result set ["message", "A valid player is required for organization billing."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _uid = getPlayerUID _unit;
|
||||
if (_uid isEqualTo "") exitWith {
|
||||
_result set ["message", "A valid player UID is required for organization billing."];
|
||||
_result
|
||||
};
|
||||
|
||||
if (_amount <= 0) exitWith {
|
||||
_result set ["success", true];
|
||||
_result set ["message", ""];
|
||||
_result
|
||||
};
|
||||
|
||||
if (isNil QEGVAR(org,OrgStore)) exitWith {
|
||||
_result set ["message", "Organization service is unavailable."];
|
||||
["ERROR", format ["Org store unavailable for %1 charge.", _label], nil, nil] call EFUNC(common,log);
|
||||
_result
|
||||
};
|
||||
|
||||
private _orgID = EGVAR(org,OrgStore) call ["resolveOrgIdForUid", [_uid]];
|
||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
||||
|
||||
private _actor = createHashMap;
|
||||
if !(isNil QEGVAR(actor,ActorStore)) then {
|
||||
_actor = EGVAR(actor,ActorStore) call ["load", [_uid]];
|
||||
};
|
||||
private _memberName = EGVAR(org,OrgStore) call ["resolveActorName", [_uid, _unit, _actor]];
|
||||
private _org = EGVAR(org,OrgStore) call ["ensureMember", [_orgID, _uid, _memberName]];
|
||||
if (_org isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Organization membership could not be verified."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _charge = EGVAR(org,OrgStore) call ["chargeCheckout", [_uid, _unit, "org_funds", _amount, true, true, _recordDebt]];
|
||||
if !(_charge getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _charge getOrDefault ["message", "Organization funds cannot cover this service."]];
|
||||
_result
|
||||
};
|
||||
|
||||
_self call ["syncOrgPatch", [_charge]];
|
||||
_charge
|
||||
}],
|
||||
["repair", {
|
||||
params [["_target", objNull, [objNull]], ["_unit", objNull, [objNull]], ["_cost", -1, [0]]];
|
||||
|
||||
if (isNull _target || { isNull _unit }) exitWith { false };
|
||||
|
||||
private _repairCost = [_cost, GVAR(ServiceRepairCost)] select (_cost < 0);
|
||||
private _charge = _self call ["chargeOrg", [_unit, _repairCost, "Repair"]];
|
||||
if !(_charge getOrDefault ["success", false]) exitWith {
|
||||
_self call ["notify", [_unit, "danger", "Repair", _charge getOrDefault ["message", "Organization funds cannot cover this repair."]]];
|
||||
false
|
||||
};
|
||||
|
||||
_target setDamage 0;
|
||||
_self call ["notify", [_unit, "info", "Repair", format ["Repair complete. Organization charged $%1.", [_repairCost] call EFUNC(common,formatNumber)]]];
|
||||
true
|
||||
}],
|
||||
["init", {}]
|
||||
]];
|
||||
|
||||
|
||||
@ -4,12 +4,13 @@
|
||||
* File: fnc_initOrgStore.sqf
|
||||
* Author: IDSolutions
|
||||
* Date: 2026-02-13
|
||||
* Last Update: 2026-04-04
|
||||
* Last Update: 2026-04-18
|
||||
* Public: Yes
|
||||
*
|
||||
* Description:
|
||||
* Initializes the org store for managing player organizations.
|
||||
* Org hot state is owned by the extension; SQF acts as the bridge.
|
||||
* Org hot state is owned by the extension; SQF acts as the bridge for
|
||||
* treasury charges, credit lines, and service debt recording.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
@ -800,7 +801,15 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
||||
_result
|
||||
}],
|
||||
["chargeCheckout", compileFinal {
|
||||
params [["_requesterUid", "", [""]], ["_requesterPlayer", objNull, [objNull]], ["_source", "org_funds", [""]], ["_amount", 0, [0]], ["_commit", false, [false]]];
|
||||
params [
|
||||
["_requesterUid", "", [""]],
|
||||
["_requesterPlayer", objNull, [objNull]],
|
||||
["_source", "org_funds", [""]],
|
||||
["_amount", 0, [0]],
|
||||
["_commit", false, [false]],
|
||||
["_allowMemberCharge", false, [false]],
|
||||
["_recordMemberDebt", false, [false]]
|
||||
];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["success", false],
|
||||
@ -822,6 +831,8 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
||||
["requesterUid", _requesterUid],
|
||||
["orgId", _orgID],
|
||||
["requesterIsDefaultOrgCeo", _requesterIsDefaultOrgCeo],
|
||||
["allowMemberCharge", _allowMemberCharge],
|
||||
["recordMemberDebt", _recordMemberDebt],
|
||||
["source", _source],
|
||||
["amount", _amount],
|
||||
["commit", _commit]
|
||||
|
||||
@ -63,8 +63,105 @@ Task time limits use `0` for no limit on attack, destroy, delivery, hostage,
|
||||
and HVT tasks. Defuse IED timers are different: each IED must have a positive
|
||||
countdown value.
|
||||
|
||||
Mission designers can create tasks in four ways:
|
||||
|
||||
- Eden modules for editor-authored tasks.
|
||||
- `fnc_startTask.sqf` for script-authored tasks.
|
||||
- `fnc_handler.sqf` 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 `fnc_startTask.sqf`
|
||||
- `fnc_startTask.sqf`: compatible because it registers the catalog entry,
|
||||
creates the BIS task, and dispatches through `fnc_handler.sqf`
|
||||
- dynamic mission manager attack tasks: compatible because the mission manager
|
||||
uses `fnc_startTask.sqf`
|
||||
|
||||
Limited or incompatible paths:
|
||||
- `fnc_handler.sqf`: 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
|
||||
`fnc_startTask.sqf` 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 `fnc_startTask.sqf` create the BIS task
|
||||
automatically through `BIS_fnc_taskCreate`.
|
||||
|
||||
If a mission uses `fnc_handler.sqf` 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. `fnc_startTask.sqf` is the preferred Forge
|
||||
path because it handles BIS task creation, Forge catalog registration, entity
|
||||
registration, and handler dispatch together.
|
||||
|
||||
### Create With 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 `fnc_startTask.sqf`.
|
||||
|
||||
### Start Through `fnc_startTask.sqf`
|
||||
Use `fnc_startTask.sqf` for script-authored tasks. It registers task entities,
|
||||
creates the BIS task, stores the catalog entry, and dispatches through
|
||||
`fnc_handler.sqf`.
|
||||
|
||||
```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;
|
||||
```
|
||||
|
||||
### Start Through The Handler
|
||||
Use the handler when you want reputation gating and task ownership binding.
|
||||
Create the BIS task and catalog entry separately if this task should appear in
|
||||
the map task tab or CAD.
|
||||
|
||||
```sqf
|
||||
["attack", ["task_attack_1", 1, 2, 1500000, -75, 375, false, false], 250, getPlayerUID player] call forge_server_task_fnc_handler;
|
||||
@ -79,6 +176,7 @@ Arguments:
|
||||
|
||||
### Start Task Functions Directly
|
||||
Direct task calls still work, but they do not provide a requester UID. That means task ownership falls back to the `default` org.
|
||||
Create the BIS task separately if this task should appear in the map task tab.
|
||||
|
||||
Use direct starts only when that behavior is intended, such as:
|
||||
- mission-authored tasks
|
||||
|
||||
98
docs/CLIENT_ACTOR_USAGE_GUIDE.md
Normal file
98
docs/CLIENT_ACTOR_USAGE_GUIDE.md
Normal file
@ -0,0 +1,98 @@
|
||||
# Client Actor Usage Guide
|
||||
|
||||
The client actor addon owns the player interaction menu and client-side actor
|
||||
repository. It is the main launcher for nearby player actions and other Forge
|
||||
client UIs.
|
||||
|
||||
## Open the Actor Menu
|
||||
|
||||
```sqf
|
||||
call forge_client_actor_fnc_openUI;
|
||||
```
|
||||
|
||||
The actor menu opens `RscActorMenu`, loads `ui/_site/index.html`, and routes
|
||||
browser alerts through `forge_client_actor_fnc_handleUIEvents`.
|
||||
|
||||
## Repository
|
||||
|
||||
`forge_client_actor_fnc_initRepository` creates `GVAR(ActorRepository)`.
|
||||
|
||||
The repository:
|
||||
|
||||
- requests actor initialization from the server
|
||||
- saves actor state through the server actor addon
|
||||
- caches client-visible actor fields
|
||||
- applies position, direction, stance, rank, and loadout on JIP sync when the
|
||||
relevant settings allow it
|
||||
- provides nearby interaction actions to the browser UI
|
||||
|
||||
Initialize actor state through the repository:
|
||||
|
||||
```sqf
|
||||
GVAR(ActorRepository) call ["init", []];
|
||||
```
|
||||
|
||||
Save actor state through the server:
|
||||
|
||||
```sqf
|
||||
GVAR(ActorRepository) call ["save", [true]];
|
||||
```
|
||||
|
||||
## Nearby Actions
|
||||
|
||||
The menu asks for nearby actions with:
|
||||
|
||||
```text
|
||||
actor::get::actions
|
||||
```
|
||||
|
||||
The repository scans objects within 5 meters and returns actions based on
|
||||
mission object variables:
|
||||
|
||||
| Variable | Action |
|
||||
| --- | --- |
|
||||
| `storeType` | store |
|
||||
| `isAtm` | ATM |
|
||||
| `isBank` | bank |
|
||||
| `isGarage` | garage |
|
||||
| `garageType` | garage subtype |
|
||||
| `isLocker` | virtual arsenal action when VA is enabled |
|
||||
| `deviceType` | device action placeholder |
|
||||
| nearby player unit | player interaction placeholder |
|
||||
|
||||
The response is pushed into the browser with `updateAvailableActions(...)`.
|
||||
|
||||
## Browser Events
|
||||
|
||||
| Event | Client behavior |
|
||||
| --- | --- |
|
||||
| `actor::get::actions` | Refresh nearby actions. |
|
||||
| `actor::close::menu` | Close actor menu. |
|
||||
| `actor::open::atm` | Open bank UI in ATM mode. |
|
||||
| `actor::open::bank` | Open bank UI in bank mode. |
|
||||
| `actor::open::cad` | Open CAD UI. |
|
||||
| `actor::open::garage` | Open garage UI. |
|
||||
| `actor::open::vgarage` | Open virtual garage. |
|
||||
| `actor::open::org` | Open organization UI. |
|
||||
| `actor::open::vlocker` | Open ACE arsenal on `FORGE_Locker_Box`. |
|
||||
| `actor::open::phone` | Open phone UI. |
|
||||
| `actor::open::store` | Open store UI. |
|
||||
|
||||
Device and player interaction events currently display placeholder feedback.
|
||||
|
||||
## Authoritative State
|
||||
|
||||
Actor persistence is server-owned. The client repository requests and displays
|
||||
actor data, but actor creation, durable updates, and hot-state behavior are
|
||||
handled by the server actor addon and extension.
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Actor Usage Guide](./ACTOR_USAGE_GUIDE.md)
|
||||
- [Client Bank Usage Guide](./CLIENT_BANK_USAGE_GUIDE.md)
|
||||
- [Client CAD Usage Guide](./CLIENT_CAD_USAGE_GUIDE.md)
|
||||
- [Client Garage Usage Guide](./CLIENT_GARAGE_USAGE_GUIDE.md)
|
||||
- [Client Locker Usage Guide](./CLIENT_LOCKER_USAGE_GUIDE.md)
|
||||
- [Client Organization Usage Guide](./CLIENT_ORG_USAGE_GUIDE.md)
|
||||
- [Client Phone Usage Guide](./CLIENT_PHONE_USAGE_GUIDE.md)
|
||||
- [Client Store Usage Guide](./CLIENT_STORE_USAGE_GUIDE.md)
|
||||
84
docs/CLIENT_BANK_USAGE_GUIDE.md
Normal file
84
docs/CLIENT_BANK_USAGE_GUIDE.md
Normal file
@ -0,0 +1,84 @@
|
||||
# Client Bank Usage Guide
|
||||
|
||||
The client bank addon opens the bank and ATM browser UI, forwards banking
|
||||
requests to the server bank addon, and pushes account updates back into the
|
||||
browser.
|
||||
|
||||
## Open Bank UI
|
||||
|
||||
Open full bank mode:
|
||||
|
||||
```sqf
|
||||
call forge_client_bank_fnc_openUI;
|
||||
```
|
||||
|
||||
Open ATM mode:
|
||||
|
||||
```sqf
|
||||
[true] call forge_client_bank_fnc_openUI;
|
||||
```
|
||||
|
||||
The open function creates `RscBank`, sets the bridge mode to `bank` or `atm`,
|
||||
loads `ui/_site/index.html`, and routes browser events through
|
||||
`forge_client_bank_fnc_handleUIEvents`.
|
||||
|
||||
## Bridge and Repository
|
||||
|
||||
`forge_client_bank_fnc_initRepository` tracks account load and cached account
|
||||
state.
|
||||
|
||||
`forge_client_bank_fnc_initUIBridge` owns:
|
||||
|
||||
- active browser control tracking
|
||||
- bank/ATM mode
|
||||
- browser ready handling
|
||||
- account hydrate and sync responses
|
||||
- deposit, withdrawal, transfer, earnings deposit, credit repayment, and PIN
|
||||
requests
|
||||
- browser notice delivery
|
||||
|
||||
## Browser Events
|
||||
|
||||
| Event | Client behavior |
|
||||
| --- | --- |
|
||||
| `bank::ready` | Mark browser ready and request hydrate from the server. |
|
||||
| `bank::refresh` | Request fresh bank hydrate data. |
|
||||
| `bank::deposit::request` | Forward deposit amount to the server. |
|
||||
| `bank::withdraw::request` | Forward withdrawal amount to the server. |
|
||||
| `bank::transfer::request` | Forward target, source field, and amount. |
|
||||
| `bank::depositEarnings::request` | Request earnings deposit. |
|
||||
| `bank::repayCreditLine::request` | Request credit-line repayment. |
|
||||
| `bank::pin::request` | Forward PIN validation request. |
|
||||
| `bank::close` | Dispose bridge screen state and close the display. |
|
||||
|
||||
## Browser Response Events
|
||||
|
||||
The bridge sends:
|
||||
|
||||
| Event | Purpose |
|
||||
| --- | --- |
|
||||
| `bank::hydrate` | Full session/account payload. |
|
||||
| `bank::sync` | Account patch or sync data. |
|
||||
| `bank::notice` | UI-visible notice payload. |
|
||||
|
||||
## Request Flow
|
||||
|
||||
Example deposit flow:
|
||||
|
||||
1. Browser sends `bank::deposit::request` with an `amount`.
|
||||
2. Client bridge calls the server bank request event.
|
||||
3. Server bank addon validates the request and calls bank hot-state logic.
|
||||
4. Server response is caught by the client post-init event handlers.
|
||||
5. Client bridge sends `bank::sync` or `bank::notice` back to the browser.
|
||||
|
||||
## Authoritative State
|
||||
|
||||
Balances, PIN authorization, transfers, checkout charges, credit lines, and
|
||||
persistence are server-owned. The client should only display account data and
|
||||
request mutations through server events.
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Bank Usage Guide](./BANK_USAGE_GUIDE.md)
|
||||
- [Client Common Usage Guide](./CLIENT_COMMON_USAGE_GUIDE.md)
|
||||
- [Client Store Usage Guide](./CLIENT_STORE_USAGE_GUIDE.md)
|
||||
100
docs/CLIENT_CAD_USAGE_GUIDE.md
Normal file
100
docs/CLIENT_CAD_USAGE_GUIDE.md
Normal file
@ -0,0 +1,100 @@
|
||||
# Client CAD Usage Guide
|
||||
|
||||
The client CAD addon provides the map and dispatch UI for groups, active
|
||||
tasks, task assignment, dispatch orders, support requests, and task
|
||||
acknowledge/decline workflows.
|
||||
|
||||
## Open CAD UI
|
||||
|
||||
```sqf
|
||||
call forge_client_cad_fnc_openUI;
|
||||
```
|
||||
|
||||
The CAD UI opens `RscMapUI` and loads separate browser controls for:
|
||||
|
||||
- top bar
|
||||
- bottom bar
|
||||
- side panel
|
||||
- dispatcher board
|
||||
|
||||
The native Arma map remains part of the same display.
|
||||
|
||||
## Repository and Bridge
|
||||
|
||||
`forge_client_cad_fnc_initRepository` caches the hydrated CAD payload,
|
||||
selected mode, dispatch view, session data, groups, tasks, requests, and
|
||||
assignments.
|
||||
|
||||
`forge_client_cad_fnc_initUIBridge` owns:
|
||||
|
||||
- ready state for side panel, top bar, and dispatcher board
|
||||
- operations vs dispatch mode
|
||||
- board vs map dispatch view
|
||||
- hydrate requests
|
||||
- task assignment, acknowledge, and decline requests
|
||||
- dispatch order create/close requests
|
||||
- support request submit/close requests
|
||||
- group status, role, and profile requests
|
||||
- map focus actions
|
||||
|
||||
## Browser Events
|
||||
|
||||
| Event | Client behavior |
|
||||
| --- | --- |
|
||||
| `cad::topbar::ready` | Mark top bar ready and push top bar state. |
|
||||
| `cad::ready` | Mark side panel ready and request hydrate. |
|
||||
| `cad::dispatcher::ready` | Mark dispatcher board ready and push hydrate data. |
|
||||
| `cad::mode::set` | Switch between operations and dispatch mode. |
|
||||
| `cad::dispatchView::set` | Switch dispatch board/map view. |
|
||||
| `cad::refresh` | Request fresh CAD hydrate data. |
|
||||
| `cad::tasks::assign` | Assign a task to a group. |
|
||||
| `cad::tasks::acknowledge` | Acknowledge assigned task. |
|
||||
| `cad::tasks::decline` | Decline assigned task. |
|
||||
| `cad::dispatchOrder::create` | Create dispatch order. |
|
||||
| `cad::dispatchOrder::close` | Close dispatch order. |
|
||||
| `cad::supportRequest::submit` | Submit support request. |
|
||||
| `cad::supportRequest::close` | Close support request. |
|
||||
| `cad::groups::status` | Update group status. |
|
||||
| `cad::groups::role` | Update group role. |
|
||||
| `cad::groups::profile` | Update status and role together. |
|
||||
| `cad::groups::focus` | Center map on a group. |
|
||||
| `cad::tasks::focus` | Center map on a task. |
|
||||
| `cad::requests::focus` | Center map on a support request. |
|
||||
| `map::zoomIn` | Zoom native map in. |
|
||||
| `map::zoomOut` | Zoom native map out. |
|
||||
| `map::search` | Placeholder status update. |
|
||||
| `map::close` | Dispose bridge state and close the display. |
|
||||
|
||||
## Response Events
|
||||
|
||||
The bridge pushes:
|
||||
|
||||
| Event | Purpose |
|
||||
| --- | --- |
|
||||
| `cad::hydrate` | Full hydrated CAD payload to the side panel. |
|
||||
| `cad::assignment::response` | Task assignment/acknowledge/decline result. |
|
||||
| `cad::group::response` | Group status/role/profile result. |
|
||||
| `cad::request::response` | Support request result. |
|
||||
|
||||
Dispatcher board controls also receive direct `ExecJS` status and hydrate
|
||||
calls.
|
||||
|
||||
## Task Compatibility
|
||||
|
||||
CAD task visibility depends on server-side task catalog entries. Tasks created
|
||||
through Eden Forge task modules or `forge_server_task_fnc_startTask` are the
|
||||
normal CAD-compatible task sources because they register task catalog data.
|
||||
|
||||
Direct handler or task-function calls only work with CAD when the task catalog
|
||||
entry already exists.
|
||||
|
||||
## Authorization Notes
|
||||
|
||||
Only dispatcher sessions can enter dispatch mode. If the hydrated session is
|
||||
not a dispatcher, the bridge forces the UI back to operations mode.
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [CAD Usage Guide](./CAD_USAGE_GUIDE.md)
|
||||
- [Task Usage Guide](./TASK_USAGE_GUIDE.md)
|
||||
- [Client Common Usage Guide](./CLIENT_COMMON_USAGE_GUIDE.md)
|
||||
92
docs/CLIENT_COMMON_USAGE_GUIDE.md
Normal file
92
docs/CLIENT_COMMON_USAGE_GUIDE.md
Normal file
@ -0,0 +1,92 @@
|
||||
# Client Common Usage Guide
|
||||
|
||||
The client `common` addon contains shared browser UI bridge declarations and
|
||||
common client-side browser integration patterns.
|
||||
|
||||
## Purpose
|
||||
|
||||
Use `forge_client_common` when a browser-backed feature UI needs reusable
|
||||
screen lifecycle behavior:
|
||||
|
||||
- active browser control tracking
|
||||
- browser ready state
|
||||
- pending event queues
|
||||
- `ExecJS` payload delivery
|
||||
- shared bridge object inheritance through `createHashMapObject`
|
||||
|
||||
Feature addons still own their app-specific events and server RPC mapping.
|
||||
|
||||
## Shared Bridge
|
||||
|
||||
Initialize the bridge declarations with:
|
||||
|
||||
```sqf
|
||||
private _webUIDeclarations = call forge_client_common_fnc_initWebUIBridge;
|
||||
private _bridgeDeclaration = _webUIDeclarations get "bridgeDeclaration";
|
||||
```
|
||||
|
||||
Feature bridges can inherit from the shared declaration:
|
||||
|
||||
```sqf
|
||||
GVAR(MyUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
||||
["#base", _bridgeDeclaration],
|
||||
["#type", "MyUIBridgeBaseClass"],
|
||||
["handleReady", compileFinal {
|
||||
params [["_control", controlNull, [controlNull]]];
|
||||
|
||||
_self call ["setActiveBrowserControl", [_control]];
|
||||
_self call ["sendEvent", ["myAddon::hydrate", createHashMap, _control]];
|
||||
}]
|
||||
];
|
||||
```
|
||||
|
||||
## Event Delivery
|
||||
|
||||
`sendEvent` builds this payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "myAddon::event",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
If the browser control is missing or not ready, the payload is queued on the
|
||||
screen object. When the screen marks ready, `flushPendingEvents` delivers the
|
||||
queue.
|
||||
|
||||
## Screen Lifecycle
|
||||
|
||||
The shared screen object tracks:
|
||||
|
||||
| Field | Purpose |
|
||||
| --- | --- |
|
||||
| `control` | Active browser control. |
|
||||
| `readyState` | Whether the browser app has sent its ready event. |
|
||||
| `pendingEvents` | Outbound events waiting for a ready browser. |
|
||||
|
||||
Call `handleClose` or `dispose` when a display closes so stale controls and
|
||||
queued events are cleared.
|
||||
|
||||
## Current Consumers
|
||||
|
||||
The common bridge pattern is used by the newer bank, CAD, garage, and
|
||||
organization client bridges. Store currently keeps its own bridge object and
|
||||
browser bridge function names.
|
||||
|
||||
## Usage Rules
|
||||
|
||||
- Keep bridge inheritance in feature addons thin and explicit.
|
||||
- Keep shared code generic; do not add bank, CAD, org, or store-specific logic
|
||||
to `common`.
|
||||
- Prefer namespaced events such as `garage::sync`.
|
||||
- Send hash maps or arrays that can be safely serialized with `toJSON`.
|
||||
- Avoid direct extension calls from the client bridge; send CBA server events.
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Client Usage Guide](./CLIENT_USAGE_GUIDE.md)
|
||||
- [Client Bank Usage Guide](./CLIENT_BANK_USAGE_GUIDE.md)
|
||||
- [Client CAD Usage Guide](./CLIENT_CAD_USAGE_GUIDE.md)
|
||||
- [Client Garage Usage Guide](./CLIENT_GARAGE_USAGE_GUIDE.md)
|
||||
- [Client Organization Usage Guide](./CLIENT_ORG_USAGE_GUIDE.md)
|
||||
95
docs/CLIENT_GARAGE_USAGE_GUIDE.md
Normal file
95
docs/CLIENT_GARAGE_USAGE_GUIDE.md
Normal file
@ -0,0 +1,95 @@
|
||||
# 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 uses mission-configured `FORGE_CfgGarages` locations to set
|
||||
the spawn/preview position, opens the BIS garage interface, and restricts the
|
||||
available vehicle lists from the virtual garage repository.
|
||||
|
||||
## 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];
|
||||
```
|
||||
|
||||
Virtual garage access also requires configured garage locations in mission
|
||||
config so the preview/spawn position can be resolved.
|
||||
|
||||
## 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](./GARAGE_USAGE_GUIDE.md)
|
||||
- [Client Actor Usage Guide](./CLIENT_ACTOR_USAGE_GUIDE.md)
|
||||
- [Client Notifications Usage Guide](./CLIENT_NOTIFICATIONS_USAGE_GUIDE.md)
|
||||
87
docs/CLIENT_LOCKER_USAGE_GUIDE.md
Normal file
87
docs/CLIENT_LOCKER_USAGE_GUIDE.md
Normal file
@ -0,0 +1,87 @@
|
||||
# Client Locker Usage Guide
|
||||
|
||||
The client locker addon manages personal locker display state, local locker
|
||||
container behavior, and virtual arsenal unlock state.
|
||||
|
||||
## Repositories
|
||||
|
||||
`forge_client_locker_fnc_initRepository` creates `GVAR(LockerRepository)`.
|
||||
|
||||
`forge_client_locker_fnc_initVARepository` creates `GVAR(VARepository)`.
|
||||
|
||||
Initialize locker state:
|
||||
|
||||
```sqf
|
||||
GVAR(LockerRepository) call ["init", []];
|
||||
GVAR(VARepository) call ["init", []];
|
||||
```
|
||||
|
||||
## Locker Container Flow
|
||||
|
||||
The repository searches mission namespace variables whose names contain
|
||||
`locker` and refer to objects. For each server/mission locker object, it creates
|
||||
a local `Box_NATO_Equip_F` at the same position and attaches container event
|
||||
handlers.
|
||||
|
||||
On container open:
|
||||
|
||||
- the local container is cleared
|
||||
- cached locker items are inserted into the container
|
||||
- over-capacity warnings are emitted when the item count is above 25
|
||||
|
||||
On container close:
|
||||
|
||||
- cargo, nested container items, and weapon attachments are read back
|
||||
- the new locker map is sent to the server with the override request
|
||||
- the local repository cache is updated
|
||||
|
||||
## Virtual Arsenal Flow
|
||||
|
||||
The virtual arsenal repository creates a local `FORGE_Locker_Box` and requests
|
||||
virtual arsenal unlocks from the server.
|
||||
|
||||
As sync data arrives, it applies unlocks through ACE Arsenal:
|
||||
|
||||
| Data key | Client behavior |
|
||||
| --- | --- |
|
||||
| `items` | Add virtual items. |
|
||||
| `weapons` | Add virtual weapons. |
|
||||
| `magazines` | Add virtual magazines. |
|
||||
| `backpacks` | Add virtual backpacks. |
|
||||
|
||||
The actor menu opens the virtual locker with:
|
||||
|
||||
```sqf
|
||||
[FORGE_Locker_Box, player, false] spawn ace_arsenal_fnc_openBox;
|
||||
```
|
||||
|
||||
## Server Events
|
||||
|
||||
The client repository sends requests for:
|
||||
|
||||
- locker initialization
|
||||
- locker save
|
||||
- locker override after container close
|
||||
- virtual arsenal initialization
|
||||
- virtual arsenal save
|
||||
|
||||
The server locker addon and extension own the saved locker and virtual arsenal
|
||||
state.
|
||||
|
||||
## Mission Setup
|
||||
|
||||
Mission locker objects must be placed into `missionNamespace` with a variable
|
||||
name containing `locker`. The client creates local interactive containers from
|
||||
those authoritative mission objects.
|
||||
|
||||
Example:
|
||||
|
||||
```sqf
|
||||
missionNamespace setVariable ["forge_locker_alpha", _lockerObject, true];
|
||||
```
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Locker Usage Guide](./LOCKER_USAGE_GUIDE.md)
|
||||
- [Owned Storage Usage Guide](./OWNED_STORAGE_USAGE_GUIDE.md)
|
||||
- [Client Actor Usage Guide](./CLIENT_ACTOR_USAGE_GUIDE.md)
|
||||
48
docs/CLIENT_MAIN_USAGE_GUIDE.md
Normal file
48
docs/CLIENT_MAIN_USAGE_GUIDE.md
Normal file
@ -0,0 +1,48 @@
|
||||
# Client Main Usage Guide
|
||||
|
||||
The client `main` addon provides the shared mod identity, version metadata,
|
||||
CBA settings, and macro foundation used by the Forge client addons.
|
||||
|
||||
## Purpose
|
||||
|
||||
Use `forge_client_main` as the foundation dependency for client addons that
|
||||
need Forge macros, function naming, settings, or mod-level configuration.
|
||||
|
||||
Feature logic should stay in the owning addon. `main` should remain limited to
|
||||
shared client configuration and compile infrastructure.
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
| --- | --- |
|
||||
| `script_mod.hpp` | Client mod identity. |
|
||||
| `script_version.hpp` | Client mod version values. |
|
||||
| `script_macros.hpp` | Shared client macros. |
|
||||
| `CfgSettings.hpp` | Client CBA settings. |
|
||||
| `config.cpp` | Addon config and mod wiring. |
|
||||
|
||||
## Dependency Pattern
|
||||
|
||||
Feature addons normally depend on `forge_client_main` in their `config.cpp`.
|
||||
|
||||
```cpp
|
||||
class forge_client_example {
|
||||
requiredAddons[] = {
|
||||
"forge_client_main"
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## Usage Notes
|
||||
|
||||
- Put domain UI, repositories, and event handling in feature addons.
|
||||
- Put reusable browser bridge behavior in `forge_client_common`.
|
||||
- Put server-only behavior in `arma/server/addons`.
|
||||
- Keep settings in `CfgSettings.hpp` when they apply to the client mod as a
|
||||
whole or to a client feature toggle.
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Client Usage Guide](./CLIENT_USAGE_GUIDE.md)
|
||||
- [Client Common Usage Guide](./CLIENT_COMMON_USAGE_GUIDE.md)
|
||||
- [Development Guide](./DEVELOPMENT_GUIDE.md)
|
||||
74
docs/CLIENT_NOTIFICATIONS_USAGE_GUIDE.md
Normal file
74
docs/CLIENT_NOTIFICATIONS_USAGE_GUIDE.md
Normal file
@ -0,0 +1,74 @@
|
||||
# Client Notifications Usage Guide
|
||||
|
||||
The client notifications addon owns the notification HUD, notification sound,
|
||||
and local notification service used by Forge client and server modules.
|
||||
|
||||
## Runtime Behavior
|
||||
|
||||
The notification display is created during client initialization. The browser
|
||||
HUD sends:
|
||||
|
||||
```text
|
||||
notifications::ready
|
||||
```
|
||||
|
||||
When that event is received, `NotificationService` initializes and sends a
|
||||
startup notification.
|
||||
|
||||
## Create a Notification
|
||||
|
||||
Use the notification service when available:
|
||||
|
||||
```sqf
|
||||
GVAR(NotificationService) call ["create", [
|
||||
"success",
|
||||
"Title",
|
||||
"Notification text.",
|
||||
4000
|
||||
]];
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
| Argument | Purpose |
|
||||
| --- | --- |
|
||||
| `_type` | Notification type, such as `success`, `info`, `warning`, or `error`. |
|
||||
| `_title` | Notification title. |
|
||||
| `_content` | Notification body text. |
|
||||
| `_duration` | Display duration in milliseconds. |
|
||||
|
||||
The service dispatches a browser `forge:notify` custom event.
|
||||
|
||||
## CBA Event Surface
|
||||
|
||||
Other addons can use the client notification event:
|
||||
|
||||
```sqf
|
||||
["forge_client_notifications_recieveNotification", [
|
||||
"warning",
|
||||
"Garage",
|
||||
"Vehicle spawn position is blocked.",
|
||||
3000
|
||||
]] call CBA_fnc_localEvent;
|
||||
```
|
||||
|
||||
The event payload is:
|
||||
|
||||
```sqf
|
||||
[_type, _title, _content, _duration]
|
||||
```
|
||||
|
||||
## Usage Rules
|
||||
|
||||
- Use the shared notification service instead of opening separate transient
|
||||
browser UIs.
|
||||
- Keep server-driven player feedback short and actionable.
|
||||
- Treat notification state as transient client UI state.
|
||||
- Do not use notifications as the only record of durable domain changes.
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Client Usage Guide](./CLIENT_USAGE_GUIDE.md)
|
||||
- [Client Garage Usage Guide](./CLIENT_GARAGE_USAGE_GUIDE.md)
|
||||
- [Client Bank Usage Guide](./CLIENT_BANK_USAGE_GUIDE.md)
|
||||
- [Client Store Usage Guide](./CLIENT_STORE_USAGE_GUIDE.md)
|
||||
106
docs/CLIENT_ORG_USAGE_GUIDE.md
Normal file
106
docs/CLIENT_ORG_USAGE_GUIDE.md
Normal file
@ -0,0 +1,106 @@
|
||||
# Client Organization Usage Guide
|
||||
|
||||
The client organization addon provides the organization portal UI and browser
|
||||
bridge for login, registration, membership, invites, credit lines, leave and
|
||||
disband flows, assets, fleet, and treasury display.
|
||||
|
||||
## Open Organization UI
|
||||
|
||||
```sqf
|
||||
call forge_client_org_fnc_openUI;
|
||||
```
|
||||
|
||||
The UI opens `RscOrg`, loads `ui/_site/index.html`, and routes browser alerts
|
||||
through `forge_client_org_fnc_handleUIEvents`.
|
||||
|
||||
## Repository and Bridge
|
||||
|
||||
`forge_client_org_fnc_initRepository` caches organization portal state.
|
||||
|
||||
`forge_client_org_fnc_initUIBridge` owns:
|
||||
|
||||
- active browser control tracking
|
||||
- portal hydrate requests
|
||||
- create/login response routing
|
||||
- leave and disband requests
|
||||
- credit-line assignment requests
|
||||
- invite, accept invite, and decline invite requests
|
||||
- targeted browser response events
|
||||
|
||||
## Browser Events
|
||||
|
||||
| Event | Client behavior |
|
||||
| --- | --- |
|
||||
| `org::ready` | Mark browser ready and request `org::sync`. |
|
||||
| `org::login::request` | Request portal hydrate as `org::login::success`. |
|
||||
| `org::create::request` | Validate org name and request creation on server. |
|
||||
| `org::disband::request` | Request disband on server. |
|
||||
| `org::leave::request` | Request leave on server. |
|
||||
| `org::credit::request` | Request credit-line assignment. |
|
||||
| `org::invite::request` | Request member invite. |
|
||||
| `org::invite::accept` | Accept invite by org ID. |
|
||||
| `org::invite::decline` | Decline invite by org ID. |
|
||||
| `org::close` | Close the display. |
|
||||
|
||||
## Browser Response Events
|
||||
|
||||
| Event | Purpose |
|
||||
| --- | --- |
|
||||
| `org::sync` | Full portal sync payload. |
|
||||
| `org::login::success` | Login hydrate payload. |
|
||||
| `org::create::success` | Creation hydrate payload. |
|
||||
| `org::create::failure` | Creation validation or server failure. |
|
||||
| `org::disband::success` | Requester disband success. |
|
||||
| `org::disband::failure` | Disband failure. |
|
||||
| `org::portal::revoked` | Portal state revoked by someone else's disband action. |
|
||||
| `org::leave::success` | Leave success. |
|
||||
| `org::leave::failure` | Leave failure. |
|
||||
| `org::credit::success` | Credit-line request success. |
|
||||
| `org::credit::failure` | Credit-line request failure. |
|
||||
| `org::member::creditUpdated` | Targeted member credit-line patch. |
|
||||
| `org::invite::success` | Invite success. |
|
||||
| `org::invite::failure` | Invite failure. |
|
||||
| `org::invite::decision::success` | Invite accept/decline success. |
|
||||
| `org::invite::decision::failure` | Invite accept/decline failure. |
|
||||
|
||||
## Request Examples
|
||||
|
||||
Create organization request payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"orgName": "Example Logistics"
|
||||
}
|
||||
```
|
||||
|
||||
Credit-line request payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"memberUid": "76561198000000000",
|
||||
"memberName": "Player Name",
|
||||
"amount": 2500
|
||||
}
|
||||
```
|
||||
|
||||
Invite request payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"targetUid": "76561198000000000",
|
||||
"targetName": "Player Name"
|
||||
}
|
||||
```
|
||||
|
||||
## Authoritative State
|
||||
|
||||
Organization funds, reputation, membership, invites, credit lines, assets,
|
||||
fleet, and persistence are server-owned. The client portal only displays and
|
||||
requests changes.
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Organization Usage Guide](./ORG_USAGE_GUIDE.md)
|
||||
- [Client Common Usage Guide](./CLIENT_COMMON_USAGE_GUIDE.md)
|
||||
- [Client Bank Usage Guide](./CLIENT_BANK_USAGE_GUIDE.md)
|
||||
- [Client Store Usage Guide](./CLIENT_STORE_USAGE_GUIDE.md)
|
||||
107
docs/CLIENT_PHONE_USAGE_GUIDE.md
Normal file
107
docs/CLIENT_PHONE_USAGE_GUIDE.md
Normal file
@ -0,0 +1,107 @@
|
||||
# Client Phone Usage Guide
|
||||
|
||||
The client phone addon provides the in-game phone UI for contacts, SMS
|
||||
messages, email, and local utility apps such as notes, calendar events, world
|
||||
clocks, and alarms.
|
||||
|
||||
## Open Phone UI
|
||||
|
||||
```sqf
|
||||
call forge_client_phone_fnc_openUI;
|
||||
```
|
||||
|
||||
The phone UI creates `RscPhone`, loads `ui/_site/index.html`, and routes
|
||||
browser alerts through `forge_client_phone_fnc_handleUIEvents`.
|
||||
|
||||
## State Ownership
|
||||
|
||||
Contacts, messages, and emails are server-owned and requested through the
|
||||
server phone addon.
|
||||
|
||||
Local utility app state is stored in `profileNamespace`:
|
||||
|
||||
- notes
|
||||
- calendar events
|
||||
- world clocks
|
||||
- alarms
|
||||
- theme/preferences
|
||||
|
||||
## Phone Class
|
||||
|
||||
`forge_client_phone_fnc_initClass` creates `GVAR(PhoneClass)`.
|
||||
|
||||
The phone class currently owns local notes, events, and settings helpers.
|
||||
Contacts, messages, and emails continue to use server-backed request/response
|
||||
events.
|
||||
|
||||
## Browser Events
|
||||
|
||||
### Session and Preferences
|
||||
|
||||
| Event | Client behavior |
|
||||
| --- | --- |
|
||||
| `phone::get::player` | Send player UID to browser with `setPlayerUid`. |
|
||||
| `phone::get::theme` | Send saved light/dark theme to browser. |
|
||||
| `phone::set::theme` | Save theme preference to `profileNamespace`. |
|
||||
|
||||
### Contacts
|
||||
|
||||
| Event | Client behavior |
|
||||
| --- | --- |
|
||||
| `phone::get::contacts` | Load cached contacts and request server refresh. |
|
||||
| `phone::refresh::contacts` | Request contacts from server. |
|
||||
| `phone::add::contact` | Add contact by phone number. |
|
||||
| `phone::add::contact::by::phone` | Add contact by phone number. |
|
||||
| `phone::add::contact::by::email` | Add contact by email. |
|
||||
| `phone::remove::contact` | Remove contact by UID. |
|
||||
|
||||
### Messages
|
||||
|
||||
| Event | Client behavior |
|
||||
| --- | --- |
|
||||
| `phone::get::messages` | Request messages from server. |
|
||||
| `phone::get::message::thread` | Request thread with another UID. |
|
||||
| `phone::send::message` | Send SMS through server. |
|
||||
| `phone::mark::message::read` | Mark message read on server. |
|
||||
| `phone::delete::message` | Delete message on server. |
|
||||
|
||||
### Email
|
||||
|
||||
| Event | Client behavior |
|
||||
| --- | --- |
|
||||
| `phone::get::emails` | Request emails from server. |
|
||||
| `phone::send::email` | Send email through server. |
|
||||
| `phone::mark::email::read` | Mark email read on server. |
|
||||
| `phone::delete::email` | Delete email on server. |
|
||||
|
||||
### Local Utility Apps
|
||||
|
||||
| Event | Client behavior |
|
||||
| --- | --- |
|
||||
| `phone::get::notes` | Load local notes. |
|
||||
| `phone::save::note` | Save local note. |
|
||||
| `phone::delete::note` | Delete local note. |
|
||||
| `phone::get::events` | Load local calendar events. |
|
||||
| `phone::save::event` | Save local calendar event. |
|
||||
| `phone::delete::event` | Delete local calendar event. |
|
||||
| `phone::get::clocks` | Load local world clocks. |
|
||||
| `phone::save::clock` | Save local world clock. |
|
||||
| `phone::delete::clock` | Delete local world clock. |
|
||||
| `phone::get::alarms` | Load local alarms. |
|
||||
| `phone::save::alarm` | Save local alarm. |
|
||||
| `phone::delete::alarm` | Delete local alarm. |
|
||||
| `phone::toggle::alarm` | Toggle local alarm enabled state. |
|
||||
|
||||
## Usage Rules
|
||||
|
||||
- Send contact, message, and email mutations to the server phone addon.
|
||||
- Keep local-only utility apps in `profileNamespace` until they are migrated to
|
||||
server-backed storage.
|
||||
- Do not treat local phone utility state as shared multiplayer state.
|
||||
- Validate required UID, phone, email, subject, and message fields before
|
||||
sending server requests.
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Phone Usage Guide](./PHONE_USAGE_GUIDE.md)
|
||||
- [Client Notifications Usage Guide](./CLIENT_NOTIFICATIONS_USAGE_GUIDE.md)
|
||||
92
docs/CLIENT_STORE_USAGE_GUIDE.md
Normal file
92
docs/CLIENT_STORE_USAGE_GUIDE.md
Normal file
@ -0,0 +1,92 @@
|
||||
# Client Store Usage Guide
|
||||
|
||||
The client store addon provides the storefront browser UI for catalog browsing,
|
||||
category hydration, payment source display, cart handling, and checkout
|
||||
requests.
|
||||
|
||||
## Open Store UI
|
||||
|
||||
```sqf
|
||||
call forge_client_store_fnc_openUI;
|
||||
```
|
||||
|
||||
The UI opens `RscStore`, loads `ui/_site/index.html`, and routes browser alerts
|
||||
through `forge_client_store_fnc_handleUIEvents`.
|
||||
|
||||
## Bridge
|
||||
|
||||
`forge_client_store_fnc_initUIBridge` owns:
|
||||
|
||||
- browser control lookup
|
||||
- store hydrate requests
|
||||
- category requests
|
||||
- checkout requests
|
||||
- category hydrate/failure responses
|
||||
- checkout success/failure responses
|
||||
- store config refresh after successful checkout
|
||||
|
||||
Store currently uses its own `StoreUIBridge.receive(...)` browser bridge rather
|
||||
than the shared `ForgeBridge.receive(...)` delivery used by newer bridges.
|
||||
|
||||
## Browser Events
|
||||
|
||||
| Event | Client behavior |
|
||||
| --- | --- |
|
||||
| `store::ready` | Request store hydrate from the server. |
|
||||
| `store::category::request` | Request catalog items for a category. |
|
||||
| `store::checkout::request` | Forward checkout JSON to the server. |
|
||||
| `store::close` | Close the display. |
|
||||
|
||||
## Browser Response Events
|
||||
|
||||
| Event | Purpose |
|
||||
| --- | --- |
|
||||
| `store::hydrate` | Initial storefront/session/config payload. |
|
||||
| `store::config::hydrate` | Refreshed payment/source config. |
|
||||
| `store::category::hydrate` | Category catalog payload. |
|
||||
| `store::category::failure` | Category request failure. |
|
||||
| `store::checkout::success` | Checkout success payload. |
|
||||
| `store::checkout::failure` | Checkout failure payload. |
|
||||
|
||||
## Category Requests
|
||||
|
||||
Category requests require a non-empty category value.
|
||||
|
||||
```json
|
||||
{
|
||||
"category": "weapons"
|
||||
}
|
||||
```
|
||||
|
||||
The client lowercases the category before forwarding it to the server store
|
||||
addon.
|
||||
|
||||
## Checkout Requests
|
||||
|
||||
Checkout requests send a serialized checkout payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"checkoutJson": "{\"items\":[],\"paymentSource\":\"cash\"}"
|
||||
}
|
||||
```
|
||||
|
||||
The client only forwards the checkout data. The server store addon and
|
||||
extension validate prices, inventory grants, payment source authorization, and
|
||||
integration with bank, organization, locker, and garage state.
|
||||
|
||||
After a successful checkout, the client asks the server for a fresh store config
|
||||
payload so payment-source balances and permissions stay current.
|
||||
|
||||
## Authoritative State
|
||||
|
||||
Catalog data, prices, checkout validation, money movement, organization funds,
|
||||
credit lines, locker grants, garage grants, and persistence are server-owned.
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Store Usage Guide](./STORE_USAGE_GUIDE.md)
|
||||
- [Client Bank Usage Guide](./CLIENT_BANK_USAGE_GUIDE.md)
|
||||
- [Client Organization Usage Guide](./CLIENT_ORG_USAGE_GUIDE.md)
|
||||
- [Client Locker Usage Guide](./CLIENT_LOCKER_USAGE_GUIDE.md)
|
||||
- [Client Garage Usage Guide](./CLIENT_GARAGE_USAGE_GUIDE.md)
|
||||
125
docs/CLIENT_USAGE_GUIDE.md
Normal file
125
docs/CLIENT_USAGE_GUIDE.md
Normal file
@ -0,0 +1,125 @@
|
||||
# Client Usage Guide
|
||||
|
||||
Forge Client contains the Arma client-side addons that open player interfaces,
|
||||
handle browser events, cache client-visible state, and forward authoritative
|
||||
requests to the server addons.
|
||||
|
||||
Use this guide as the entry point for client-side integration. Domain data,
|
||||
validation, persistence, rewards, ownership, and checkout behavior remain
|
||||
server-side responsibilities.
|
||||
|
||||
## Client Responsibilities
|
||||
|
||||
- Open Arma displays and `CT_WEBBROWSER` controls.
|
||||
- Load browser UI assets from each addon's `ui/_site` folder.
|
||||
- Receive browser alerts through `JSDialog` handlers.
|
||||
- Translate browser events into local actions or CBA server events.
|
||||
- Cache display state in client repositories.
|
||||
- Push server responses back into browser UIs with `ExecJS`.
|
||||
- Provide local-only utility state where the feature is intentionally local.
|
||||
|
||||
## Authoritative Boundaries
|
||||
|
||||
Client repositories are view state. They are useful for rendering, local UI
|
||||
decisions, and short-lived session behavior, but they should not be treated as
|
||||
durable state.
|
||||
|
||||
Authoritative state lives in:
|
||||
|
||||
- server SQF addons for mission and player workflow ownership
|
||||
- the `forge_server` extension for durable and hot-state domain logic
|
||||
- SurrealDB where the extension persists durable domain records
|
||||
|
||||
## Common Runtime Flow
|
||||
|
||||
Most browser-backed client addons follow this shape:
|
||||
|
||||
1. The addon creates a display, finds a browser control, and registers a
|
||||
`JSDialog` event handler.
|
||||
2. The browser loads an HTML entrypoint from `ui/_site`.
|
||||
3. The browser sends JSON alerts with an `event` name and `data` payload.
|
||||
4. `fnc_handleUIEvents.sqf` parses the alert and routes the event.
|
||||
5. A bridge object or repository sends a CBA server event when server data is
|
||||
needed.
|
||||
6. Server responses are caught in `XEH_postInitClient.sqf`.
|
||||
7. The bridge sends browser update events back through `ExecJS`.
|
||||
|
||||
Browser alert payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "module::action",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
## Open UI Entry Points
|
||||
|
||||
| UI | Entry point |
|
||||
| --- | --- |
|
||||
| Actor menu | `call forge_client_actor_fnc_openUI;` |
|
||||
| Bank | `call forge_client_bank_fnc_openUI;` |
|
||||
| ATM | `[true] call forge_client_bank_fnc_openUI;` |
|
||||
| CAD | `call forge_client_cad_fnc_openUI;` |
|
||||
| Garage | `call forge_client_garage_fnc_openUI;` |
|
||||
| Virtual garage | `call forge_client_garage_fnc_openVG;` |
|
||||
| Organization portal | `call forge_client_org_fnc_openUI;` |
|
||||
| Phone | `call forge_client_phone_fnc_openUI;` |
|
||||
| Store | `call forge_client_store_fnc_openUI;` |
|
||||
|
||||
Notifications are normally opened during client initialization and then updated
|
||||
through the notification event/service.
|
||||
|
||||
## Addon Guides
|
||||
|
||||
- [Client Main Usage Guide](./CLIENT_MAIN_USAGE_GUIDE.md)
|
||||
- [Client Common Usage Guide](./CLIENT_COMMON_USAGE_GUIDE.md)
|
||||
- [Client Actor Usage Guide](./CLIENT_ACTOR_USAGE_GUIDE.md)
|
||||
- [Client Bank Usage Guide](./CLIENT_BANK_USAGE_GUIDE.md)
|
||||
- [Client CAD Usage Guide](./CLIENT_CAD_USAGE_GUIDE.md)
|
||||
- [Client Garage Usage Guide](./CLIENT_GARAGE_USAGE_GUIDE.md)
|
||||
- [Client Locker Usage Guide](./CLIENT_LOCKER_USAGE_GUIDE.md)
|
||||
- [Client Notifications Usage Guide](./CLIENT_NOTIFICATIONS_USAGE_GUIDE.md)
|
||||
- [Client Organization Usage Guide](./CLIENT_ORG_USAGE_GUIDE.md)
|
||||
- [Client Phone Usage Guide](./CLIENT_PHONE_USAGE_GUIDE.md)
|
||||
- [Client Store Usage Guide](./CLIENT_STORE_USAGE_GUIDE.md)
|
||||
|
||||
## 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.
|
||||
77
docs/ECONOMY_USAGE_GUIDE.md
Normal file
77
docs/ECONOMY_USAGE_GUIDE.md
Normal file
@ -0,0 +1,77 @@
|
||||
# Economy Usage Guide
|
||||
|
||||
The economy server addon owns Arma-world service behavior for fuel, medical,
|
||||
and repair interactions. It does not own money state. Money mutations go
|
||||
through extension-backed bank and organization hot state before the world
|
||||
effect is applied.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `forge_server_common` for logging, formatting, and player lookup.
|
||||
- `forge_server_bank` for personal medical billing.
|
||||
- `forge_server_org` for organization-funded services and medical fallback
|
||||
debt.
|
||||
- `forge_client_actor` and `forge_client_notifications` for targeted client
|
||||
responses.
|
||||
|
||||
## Fuel
|
||||
|
||||
Fuel is organization-funded.
|
||||
|
||||
When refueling stops, `fnc_initFEconomyStore.sqf` calculates the fuel delta and
|
||||
cost, charges the player's organization through `OrgStore chargeCheckout`, and
|
||||
syncs the organization patch to online members. If organization funds cannot
|
||||
cover the refuel, the vehicle is rolled back to the fuel level it had when the
|
||||
session started.
|
||||
|
||||
Garage UI refuel requests use the server `RefuelService` event. The fuel store
|
||||
calculates missing fuel from the vehicle config `fuelCapacity`, charges the
|
||||
player's organization, and fills the vehicle only after the organization charge
|
||||
succeeds.
|
||||
|
||||
## Repair
|
||||
|
||||
Repair is organization-funded.
|
||||
|
||||
Use the repair service event:
|
||||
|
||||
```sqf
|
||||
[QEGVAR(economy,RepairService), [_target, _unit, _cost]] call CBA_fnc_serverEvent;
|
||||
```
|
||||
|
||||
`_cost` is optional. Passing `-1` uses the configured service repair cost.
|
||||
The target is only repaired after the organization charge succeeds.
|
||||
|
||||
The client garage UI forwards selected nearby vehicle repair requests through
|
||||
the same event.
|
||||
|
||||
## Medical
|
||||
|
||||
Medical is player-funded first.
|
||||
|
||||
When a heal is requested, `fnc_initMEconomyStore.sqf` uses this billing order:
|
||||
|
||||
1. Charge the player's bank balance when it can cover the medical fee.
|
||||
2. Otherwise charge the player's cash when it can cover the fee.
|
||||
3. If neither personal balance can cover the fee, charge organization funds.
|
||||
4. When organization funds cover the fallback charge, record the same amount as
|
||||
debt on the player's organization credit line.
|
||||
|
||||
The heal only completes after one of those charges succeeds. If personal
|
||||
billing is unavailable, the heal does not fall back to organization funds
|
||||
because the server cannot verify that the player is unable to cover the fee.
|
||||
|
||||
## Medical Debt Repayment
|
||||
|
||||
Medical fallback debt uses the existing organization credit-line repayment
|
||||
flow. The organization treasury is reduced when the service is rendered, and
|
||||
the player's credit-line `amount_due` increases by the medical fee. When the
|
||||
player repays through the bank credit-line repayment action, player bank funds
|
||||
are moved back into the organization treasury.
|
||||
|
||||
## Hot-Cache Boundary
|
||||
|
||||
The economy addon should stay server-authoritative for world effects such as
|
||||
vehicle fuel, vehicle repair, healing, respawn placement, and death inventory
|
||||
movement. Bank and organization balances should continue to mutate through the
|
||||
extension-backed hot-cache services.
|
||||
@ -33,10 +33,11 @@ docs/ Framework-level documentation
|
||||
| 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:*` |
|
||||
|
||||
Guides:
|
||||
Server and extension guides:
|
||||
[Actor](./ACTOR_USAGE_GUIDE.md),
|
||||
[Bank](./BANK_USAGE_GUIDE.md),
|
||||
[CAD](./CAD_USAGE_GUIDE.md),
|
||||
[Economy](./ECONOMY_USAGE_GUIDE.md),
|
||||
[Garage](./GARAGE_USAGE_GUIDE.md),
|
||||
[Locker](./LOCKER_USAGE_GUIDE.md),
|
||||
[Organization](./ORG_USAGE_GUIDE.md),
|
||||
@ -45,6 +46,20 @@ Guides:
|
||||
[Store](./STORE_USAGE_GUIDE.md),
|
||||
[Task](./TASK_USAGE_GUIDE.md).
|
||||
|
||||
Client guides:
|
||||
[Client Overview](./CLIENT_USAGE_GUIDE.md),
|
||||
[Main](./CLIENT_MAIN_USAGE_GUIDE.md),
|
||||
[Common](./CLIENT_COMMON_USAGE_GUIDE.md),
|
||||
[Actor](./CLIENT_ACTOR_USAGE_GUIDE.md),
|
||||
[Bank](./CLIENT_BANK_USAGE_GUIDE.md),
|
||||
[CAD](./CLIENT_CAD_USAGE_GUIDE.md),
|
||||
[Garage](./CLIENT_GARAGE_USAGE_GUIDE.md),
|
||||
[Locker](./CLIENT_LOCKER_USAGE_GUIDE.md),
|
||||
[Notifications](./CLIENT_NOTIFICATIONS_USAGE_GUIDE.md),
|
||||
[Organization](./CLIENT_ORG_USAGE_GUIDE.md),
|
||||
[Phone](./CLIENT_PHONE_USAGE_GUIDE.md),
|
||||
[Store](./CLIENT_STORE_USAGE_GUIDE.md).
|
||||
|
||||
## Infrastructure Modules
|
||||
|
||||
| Module | Purpose | Location |
|
||||
@ -52,7 +67,7 @@ Guides:
|
||||
| `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 economy store initialization and economy-specific state helpers. | `arma/server/addons/economy` |
|
||||
| `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` |
|
||||
@ -89,6 +104,8 @@ Nested groups use additional `:` separators, for example
|
||||
| `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](./ACTOR_USAGE_GUIDE.md) for examples.
|
||||
|
||||
### Bank
|
||||
|
||||
| Command | Purpose |
|
||||
@ -99,6 +116,8 @@ Nested groups use additional `:` separators, for example
|
||||
| `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](./BANK_USAGE_GUIDE.md) for examples.
|
||||
|
||||
### Garage
|
||||
|
||||
| Command | Purpose |
|
||||
@ -127,6 +146,8 @@ See [Locker Usage Guide](./LOCKER_USAGE_GUIDE.md) for examples.
|
||||
| `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](./ORG_USAGE_GUIDE.md) for examples.
|
||||
|
||||
### Phone
|
||||
|
||||
| Command | Purpose |
|
||||
@ -137,6 +158,8 @@ See [Locker Usage Guide](./LOCKER_USAGE_GUIDE.md) for examples.
|
||||
| `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](./PHONE_USAGE_GUIDE.md) for examples.
|
||||
|
||||
### CAD
|
||||
|
||||
| Command Group | Purpose |
|
||||
@ -149,6 +172,8 @@ See [Locker Usage Guide](./LOCKER_USAGE_GUIDE.md) for examples.
|
||||
| `cad:groups:build` | Build grouped CAD state. |
|
||||
| `cad:view:hydrate` | Build the dispatcher view model. |
|
||||
|
||||
See [CAD Usage Guide](./CAD_USAGE_GUIDE.md) for examples.
|
||||
|
||||
### Task
|
||||
|
||||
| Command Group | Purpose |
|
||||
@ -160,6 +185,8 @@ See [Locker Usage Guide](./LOCKER_USAGE_GUIDE.md) for examples.
|
||||
| `task:defuse:increment`, `task:defuse:get` | Manage defuse counters. |
|
||||
| `task:clear` | Clear task state. |
|
||||
|
||||
See [Task Usage Guide](./TASK_USAGE_GUIDE.md) for examples.
|
||||
|
||||
### Owned Storage
|
||||
|
||||
| Command Group | Purpose |
|
||||
@ -169,6 +196,8 @@ See [Locker Usage Guide](./LOCKER_USAGE_GUIDE.md) for examples.
|
||||
| `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](./OWNED_STORAGE_USAGE_GUIDE.md) for examples.
|
||||
|
||||
### Other Extension Groups
|
||||
|
||||
| Command Group | Purpose |
|
||||
|
||||
@ -14,11 +14,12 @@ collects framework-level documentation for those pieces.
|
||||
- [Development Guide](./DEVELOPMENT_GUIDE.md): how to add or change a module
|
||||
without breaking the framework boundaries.
|
||||
|
||||
## Existing Usage Guides
|
||||
## Server and Extension Usage Guides
|
||||
|
||||
- [Actor Usage Guide](./ACTOR_USAGE_GUIDE.md)
|
||||
- [Bank Usage Guide](./BANK_USAGE_GUIDE.md)
|
||||
- [CAD Usage Guide](./CAD_USAGE_GUIDE.md)
|
||||
- [Economy Usage Guide](./ECONOMY_USAGE_GUIDE.md)
|
||||
- [Garage Usage Guide](./GARAGE_USAGE_GUIDE.md)
|
||||
- [Locker Usage Guide](./LOCKER_USAGE_GUIDE.md)
|
||||
- [Organization Usage Guide](./ORG_USAGE_GUIDE.md)
|
||||
@ -27,6 +28,21 @@ collects framework-level documentation for those pieces.
|
||||
- [Store Usage Guide](./STORE_USAGE_GUIDE.md)
|
||||
- [Task Usage Guide](./TASK_USAGE_GUIDE.md)
|
||||
|
||||
## Client Usage Guides
|
||||
|
||||
- [Client Usage Guide](./CLIENT_USAGE_GUIDE.md)
|
||||
- [Client Main Usage Guide](./CLIENT_MAIN_USAGE_GUIDE.md)
|
||||
- [Client Common Usage Guide](./CLIENT_COMMON_USAGE_GUIDE.md)
|
||||
- [Client Actor Usage Guide](./CLIENT_ACTOR_USAGE_GUIDE.md)
|
||||
- [Client Bank Usage Guide](./CLIENT_BANK_USAGE_GUIDE.md)
|
||||
- [Client CAD Usage Guide](./CLIENT_CAD_USAGE_GUIDE.md)
|
||||
- [Client Garage Usage Guide](./CLIENT_GARAGE_USAGE_GUIDE.md)
|
||||
- [Client Locker Usage Guide](./CLIENT_LOCKER_USAGE_GUIDE.md)
|
||||
- [Client Notifications Usage Guide](./CLIENT_NOTIFICATIONS_USAGE_GUIDE.md)
|
||||
- [Client Organization Usage Guide](./CLIENT_ORG_USAGE_GUIDE.md)
|
||||
- [Client Phone Usage Guide](./CLIENT_PHONE_USAGE_GUIDE.md)
|
||||
- [Client Store Usage Guide](./CLIENT_STORE_USAGE_GUIDE.md)
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Server Extension Docs](../arma/server/docs/README.md)
|
||||
|
||||
@ -128,6 +128,83 @@ The task addon provides these server-owned task flows:
|
||||
- `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
|
||||
@ -155,8 +232,12 @@ through `forge_server_task_fnc_handler`.
|
||||
] 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:
|
||||
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
|
||||
[
|
||||
@ -167,9 +248,12 @@ registered and you want reputation gating plus ownership binding:
|
||||
] 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.
|
||||
`default` org. Create the BIS task separately if this task should appear in the
|
||||
map task tab.
|
||||
|
||||
## Timer Semantics
|
||||
|
||||
|
||||
@ -180,6 +180,10 @@ pub struct OrgCheckoutContext {
|
||||
pub requester_uid: String,
|
||||
pub org_id: String,
|
||||
pub requester_is_default_org_ceo: bool,
|
||||
#[serde(default)]
|
||||
pub allow_member_charge: bool,
|
||||
#[serde(default)]
|
||||
pub record_member_debt: bool,
|
||||
pub source: String,
|
||||
pub amount: f64,
|
||||
pub commit: bool,
|
||||
|
||||
@ -793,24 +793,69 @@ impl<R: OrgRepository, H: OrgHotRepository> OrgHotStateService<R, H> {
|
||||
|
||||
match context.source.trim().to_ascii_lowercase().as_str() {
|
||||
"org_funds" => {
|
||||
if !can_manage_treasury(
|
||||
let charged_amount = round_currency(context.amount);
|
||||
let can_charge_org_funds = can_manage_treasury(
|
||||
&org,
|
||||
&context.requester_uid,
|
||||
context.requester_is_default_org_ceo,
|
||||
) {
|
||||
) || (context.allow_member_charge
|
||||
&& org.members.contains_key(&context.requester_uid));
|
||||
|
||||
if !can_charge_org_funds {
|
||||
return Err(
|
||||
"Only the organization leader or CEO can charge org funds.".to_string()
|
||||
);
|
||||
}
|
||||
if org.funds < context.amount {
|
||||
if org.funds < charged_amount {
|
||||
return Err("Organization funds cannot cover this checkout.".to_string());
|
||||
}
|
||||
|
||||
org.funds -= context.amount;
|
||||
org.funds = round_currency(org.funds - charged_amount);
|
||||
if context.record_member_debt {
|
||||
let member_name = org
|
||||
.members
|
||||
.get(&context.requester_uid)
|
||||
.map(|member| member.name.clone())
|
||||
.filter(|name| !name.trim().is_empty())
|
||||
.unwrap_or_else(|| "Unknown".to_string());
|
||||
let mut credit_line = org
|
||||
.credit_lines
|
||||
.get(&context.requester_uid)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| CreditLineSummary {
|
||||
uid: context.requester_uid.clone(),
|
||||
name: member_name.clone(),
|
||||
approved_amount: 0.0,
|
||||
available_amount: 0.0,
|
||||
outstanding_principal: 0.0,
|
||||
interest_rate: DEFAULT_CREDIT_LINE_INTEREST_RATE,
|
||||
amount_due: 0.0,
|
||||
amount: 0.0,
|
||||
});
|
||||
credit_line.normalize();
|
||||
credit_line.uid = context.requester_uid.clone();
|
||||
credit_line.name = member_name;
|
||||
if credit_line.interest_rate <= 0.0 {
|
||||
credit_line.interest_rate = DEFAULT_CREDIT_LINE_INTEREST_RATE;
|
||||
}
|
||||
credit_line.outstanding_principal =
|
||||
round_currency(credit_line.outstanding_principal + charged_amount);
|
||||
credit_line.amount_due =
|
||||
round_currency(credit_line.amount_due + charged_amount);
|
||||
credit_line.amount = credit_line.available_amount;
|
||||
org.credit_lines
|
||||
.insert(context.requester_uid.clone(), credit_line);
|
||||
}
|
||||
self.repository.save(&org)?;
|
||||
|
||||
let patch_fields = if context.record_member_debt {
|
||||
vec!["funds", "credit_lines"]
|
||||
} else {
|
||||
vec!["funds"]
|
||||
};
|
||||
|
||||
Ok(OrgMutationResult {
|
||||
patch: build_org_patch(&org, &["funds"])?,
|
||||
patch: build_org_patch(&org, &patch_fields)?,
|
||||
member_uids,
|
||||
message: String::new(),
|
||||
org,
|
||||
@ -1220,3 +1265,158 @@ fn format_currency(amount: f64) -> String {
|
||||
fn round_currency(amount: f64) -> f64 {
|
||||
(amount.max(0.0) * 100.0).round() / 100.0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use forge_repositories::InMemoryOrgHotRepository;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct TestOrgRepository;
|
||||
|
||||
impl OrgRepository for TestOrgRepository {
|
||||
fn create(&self, _org: &Org) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_by_id(&self, _id: &str) -> Result<Option<Org>, String> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn update(&self, _org: &Org) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete(&self, _id: &str) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exists(&self, _id: &str) -> Result<bool, String> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn add_member(&self, _org_id: &str, _member_uid: &str) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_members(&self, _org_id: &str) -> Result<Vec<MemberSummary>, String> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
fn remove_member(&self, _org_id: &str, _member_uid: &str) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_assets(
|
||||
&self,
|
||||
_org_id: &str,
|
||||
) -> Result<HashMap<String, HashMap<String, OrgAssetEntry>>, String> {
|
||||
Ok(HashMap::new())
|
||||
}
|
||||
|
||||
fn update_assets(
|
||||
&self,
|
||||
_org_id: &str,
|
||||
_assets: &HashMap<String, HashMap<String, OrgAssetEntry>>,
|
||||
) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_fleet(&self, _org_id: &str) -> Result<HashMap<String, OrgFleetEntry>, String> {
|
||||
Ok(HashMap::new())
|
||||
}
|
||||
|
||||
fn update_fleet(
|
||||
&self,
|
||||
_org_id: &str,
|
||||
_fleet: &HashMap<String, OrgFleetEntry>,
|
||||
) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn test_hot_org() -> HotOrgRecord {
|
||||
let mut members = HashMap::new();
|
||||
members.insert(
|
||||
"member".to_string(),
|
||||
MemberSummary {
|
||||
uid: "member".to_string(),
|
||||
name: "Medic Patient".to_string(),
|
||||
},
|
||||
);
|
||||
|
||||
HotOrgRecord {
|
||||
id: "org".to_string(),
|
||||
owner: "owner".to_string(),
|
||||
name: "Test Org".to_string(),
|
||||
funds: 500.0,
|
||||
reputation: 0,
|
||||
credit_lines: HashMap::new(),
|
||||
assets: HashMap::new(),
|
||||
fleet: HashMap::new(),
|
||||
members,
|
||||
pending_invites: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn test_service(
|
||||
hot_repository: InMemoryOrgHotRepository,
|
||||
) -> OrgHotStateService<TestOrgRepository, InMemoryOrgHotRepository> {
|
||||
OrgHotStateService::new(TestOrgRepository, hot_repository)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn org_funds_checkout_without_member_debt_only_reduces_funds() {
|
||||
let hot_repository = InMemoryOrgHotRepository::new();
|
||||
hot_repository.save(&test_hot_org()).unwrap();
|
||||
let service = test_service(hot_repository);
|
||||
|
||||
let result = service
|
||||
.charge_checkout(OrgCheckoutContext {
|
||||
requester_uid: "member".to_string(),
|
||||
org_id: "org".to_string(),
|
||||
requester_is_default_org_ceo: false,
|
||||
allow_member_charge: true,
|
||||
record_member_debt: false,
|
||||
source: "org_funds".to_string(),
|
||||
amount: 125.0,
|
||||
commit: true,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.org.funds, 375.0);
|
||||
assert!(result.org.credit_lines.is_empty());
|
||||
assert!(result.patch.contains_key("funds"));
|
||||
assert!(!result.patch.contains_key("credit_lines"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn org_funds_checkout_can_record_member_debt() {
|
||||
let hot_repository = InMemoryOrgHotRepository::new();
|
||||
hot_repository.save(&test_hot_org()).unwrap();
|
||||
let service = test_service(hot_repository);
|
||||
|
||||
let result = service
|
||||
.charge_checkout(OrgCheckoutContext {
|
||||
requester_uid: "member".to_string(),
|
||||
org_id: "org".to_string(),
|
||||
requester_is_default_org_ceo: false,
|
||||
allow_member_charge: true,
|
||||
record_member_debt: true,
|
||||
source: "org_funds".to_string(),
|
||||
amount: 100.0,
|
||||
commit: true,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let credit_line = result.org.credit_lines.get("member").unwrap();
|
||||
assert_eq!(result.org.funds, 400.0);
|
||||
assert_eq!(credit_line.uid, "member");
|
||||
assert_eq!(credit_line.name, "Medic Patient");
|
||||
assert_eq!(credit_line.outstanding_principal, 100.0);
|
||||
assert_eq!(credit_line.amount_due, 100.0);
|
||||
assert_eq!(credit_line.available_amount, 0.0);
|
||||
assert!(result.patch.contains_key("funds"));
|
||||
assert!(result.patch.contains_key("credit_lines"));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user