Add framework transport service
@ -58,6 +58,37 @@ switch (_event) do {
|
|||||||
case "actor::open::phone": { [] spawn EFUNC(phone,openUI); };
|
case "actor::open::phone": { [] spawn EFUNC(phone,openUI); };
|
||||||
case "actor::open::iplayer": { hint "Player interaction is not yet implemented." };
|
case "actor::open::iplayer": { hint "Player interaction is not yet implemented." };
|
||||||
case "actor::open::store": { [] spawn EFUNC(store,openUI); };
|
case "actor::open::store": { [] spawn EFUNC(store,openUI); };
|
||||||
|
case "actor::request::transport": {
|
||||||
|
if !(_data isEqualType createHashMap) exitWith {
|
||||||
|
hint "Invalid transport request.";
|
||||||
|
};
|
||||||
|
|
||||||
|
private _destination = _data getOrDefault ["destination", createHashMap];
|
||||||
|
if !(_destination isEqualType createHashMap) exitWith {
|
||||||
|
hint "Invalid transport destination.";
|
||||||
|
};
|
||||||
|
|
||||||
|
private _fromNode = objectFromNetId (_data getOrDefault ["netId", ""]);
|
||||||
|
private _toNode = objectFromNetId (_destination getOrDefault ["netId", ""]);
|
||||||
|
|
||||||
|
if (isNull _fromNode || { isNull _toNode }) exitWith {
|
||||||
|
hint "Transport destination is no longer available.";
|
||||||
|
};
|
||||||
|
|
||||||
|
private _options = createHashMapFromArray [
|
||||||
|
["label", _data getOrDefault ["label", "Transport"]],
|
||||||
|
["nodePrefix", _data getOrDefault ["nodePrefix", "transport"]],
|
||||||
|
["vehiclePrefix", _data getOrDefault ["vehiclePrefix", "transport_vehicle"]],
|
||||||
|
["arrivalPrefix", _data getOrDefault ["arrivalPrefix", "transport_arrival"]],
|
||||||
|
["maxIndexedNodes", _data getOrDefault ["maxIndexedNodes", 10]],
|
||||||
|
["baseFare", _data getOrDefault ["baseFare", 100]],
|
||||||
|
["pricePerKm", _data getOrDefault ["pricePerKm", 50]],
|
||||||
|
["cargoRadius", _data getOrDefault ["cargoRadius", 25]],
|
||||||
|
["includeCargo", _data getOrDefault ["includeCargo", true]]
|
||||||
|
];
|
||||||
|
|
||||||
|
[SRPC(transport,requestTransport), [player, _fromNode, _toNode, _options]] call CFUNC(serverEvent);
|
||||||
|
};
|
||||||
default { hint format ["Unhandled UI event: %1", _event]; };
|
default { hint format ["Unhandled UI event: %1", _event]; };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -121,6 +121,12 @@ GVAR(ActorRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
];
|
];
|
||||||
private _deviceType = _x getVariable ["deviceType", ""];
|
private _deviceType = _x getVariable ["deviceType", ""];
|
||||||
private _isPlayer = _x isKindOf "Man" && isPlayer _x;
|
private _isPlayer = _x isKindOf "Man" && isPlayer _x;
|
||||||
|
private _objectName = vehicleVarName _x;
|
||||||
|
private _transportPrefix = _x getVariable ["transportNodePrefix", "transport"];
|
||||||
|
private _isTransport = _x getVariable ["isTransport", false];
|
||||||
|
if (!_isTransport && { _objectName isNotEqualTo "" }) then {
|
||||||
|
_isTransport = _objectName isEqualTo _transportPrefix || { (_objectName find format ["%1_", _transportPrefix]) == 0 };
|
||||||
|
};
|
||||||
|
|
||||||
if (_isStore) then { _nearbyActions pushBack ["store", true]; };
|
if (_isStore) then { _nearbyActions pushBack ["store", true]; };
|
||||||
if (_isAtm) then { _nearbyActions pushBack ["atm", true]; };
|
if (_isAtm) then { _nearbyActions pushBack ["atm", true]; };
|
||||||
@ -129,6 +135,55 @@ GVAR(ActorRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
if (_isGarage) then { _nearbyActions pushBack ["garage", _garageContext]; };
|
if (_isGarage) then { _nearbyActions pushBack ["garage", _garageContext]; };
|
||||||
if (_isGarage && GVAR(enableVG)) then { _nearbyActions pushBack ["vg", _garageContext]; };
|
if (_isGarage && GVAR(enableVG)) then { _nearbyActions pushBack ["vg", _garageContext]; };
|
||||||
if (_deviceType isNotEqualTo "") then { _nearbyActions pushBack ["device", _deviceType]; };
|
if (_deviceType isNotEqualTo "") then { _nearbyActions pushBack ["device", _deviceType]; };
|
||||||
|
if (_isTransport) then {
|
||||||
|
private _fromTransportNode = _x;
|
||||||
|
private _maxIndexedNodes = _x getVariable ["transportMaxIndexedNodes", 10];
|
||||||
|
private _baseFare = _x getVariable ["transportBaseFare", 100];
|
||||||
|
private _pricePerKm = _x getVariable ["transportPricePerKm", 50];
|
||||||
|
private _vehiclePrefix = _x getVariable ["transportVehiclePrefix", format ["%1_vehicle", _transportPrefix]];
|
||||||
|
private _arrivalPrefix = _x getVariable ["transportArrivalPrefix", format ["%1_arrival", _transportPrefix]];
|
||||||
|
private _nodeNames = [_transportPrefix];
|
||||||
|
|
||||||
|
for "_i" from 1 to _maxIndexedNodes do {
|
||||||
|
_nodeNames pushBack format ["%1_%2", _transportPrefix, _i];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _destinations = [];
|
||||||
|
{
|
||||||
|
private _node = missionNamespace getVariable [_x, objNull];
|
||||||
|
if (!isNull _node && { _node isNotEqualTo _fromTransportNode }) then {
|
||||||
|
private _nodeLabel = _node getVariable ["transportLabel", vehicleVarName _node];
|
||||||
|
if (_nodeLabel isEqualTo "") then { _nodeLabel = "Transport Point"; };
|
||||||
|
|
||||||
|
private _distanceMeters = _fromTransportNode distance2D _node;
|
||||||
|
private _cost = round (_baseFare + ((_distanceMeters / 1000) * _pricePerKm));
|
||||||
|
_destinations pushBack createHashMapFromArray [
|
||||||
|
["netId", netId _node],
|
||||||
|
["name", vehicleVarName _node],
|
||||||
|
["label", _nodeLabel],
|
||||||
|
["cost", _cost]
|
||||||
|
];
|
||||||
|
};
|
||||||
|
} forEach _nodeNames;
|
||||||
|
|
||||||
|
if (_destinations isNotEqualTo []) then {
|
||||||
|
private _transportContext = createHashMapFromArray [
|
||||||
|
["netId", netId _x],
|
||||||
|
["name", _objectName],
|
||||||
|
["label", _x getVariable ["transportLabel", "Transport"]],
|
||||||
|
["nodePrefix", _transportPrefix],
|
||||||
|
["vehiclePrefix", _vehiclePrefix],
|
||||||
|
["arrivalPrefix", _arrivalPrefix],
|
||||||
|
["maxIndexedNodes", _maxIndexedNodes],
|
||||||
|
["baseFare", _baseFare],
|
||||||
|
["pricePerKm", _pricePerKm],
|
||||||
|
["cargoRadius", _x getVariable ["transportCargoRadius", 25]],
|
||||||
|
["includeCargo", _x getVariable ["transportIncludeCargo", true]],
|
||||||
|
["destinations", _destinations]
|
||||||
|
];
|
||||||
|
_nearbyActions pushBack ["transport", _transportContext];
|
||||||
|
};
|
||||||
|
};
|
||||||
if (_isPlayer && { _x isNotEqualTo player }) then { _nearbyActions pushBack ["player", name _x]; };
|
if (_isPlayer && { _x isNotEqualTo player }) then { _nearbyActions pushBack ["player", name _x]; };
|
||||||
} forEach (player nearObjects 5);
|
} forEach (player nearObjects 5);
|
||||||
|
|
||||||
|
|||||||
@ -193,12 +193,19 @@ const actionDefinitions = {
|
|||||||
description: "Access your virtual garage",
|
description: "Access your virtual garage",
|
||||||
action: "actor::open::vgarage",
|
action: "actor::open::vgarage",
|
||||||
},
|
},
|
||||||
|
transport: {
|
||||||
|
id: "transport",
|
||||||
|
title: "Transport",
|
||||||
|
description: "Show available travel destinations",
|
||||||
|
action: "actor::show::transport",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
availableActions: [],
|
availableActions: [],
|
||||||
menuItems: [...baseMenuItems],
|
menuItems: [...baseMenuItems],
|
||||||
baseMenuItems: [...baseMenuItems],
|
baseMenuItems: [...baseMenuItems],
|
||||||
|
defaultMenuItems: [...baseMenuItems],
|
||||||
actionDefinitions: { ...actionDefinitions },
|
actionDefinitions: { ...actionDefinitions },
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -244,6 +251,7 @@ function actorReducer(state = initialState, action) {
|
|||||||
...state,
|
...state,
|
||||||
availableActions: action.payload,
|
availableActions: action.payload,
|
||||||
menuItems: newMenuItems,
|
menuItems: newMenuItems,
|
||||||
|
defaultMenuItems: newMenuItems,
|
||||||
};
|
};
|
||||||
|
|
||||||
case ActionTypes.SET_MENU_ITEMS:
|
case ActionTypes.SET_MENU_ITEMS:
|
||||||
@ -426,6 +434,43 @@ function RadialMenu() {
|
|||||||
|
|
||||||
const handleItemClick = (item) => {
|
const handleItemClick = (item) => {
|
||||||
console.log("Menu item clicked:", item);
|
console.log("Menu item clicked:", item);
|
||||||
|
if (item.action === "actor::show::default") {
|
||||||
|
store.dispatch(
|
||||||
|
actions.setMenuItems(state.defaultMenuItems || state.baseMenuItems),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.action === "actor::show::transport") {
|
||||||
|
const context = item.context || {};
|
||||||
|
const destinations = Array.isArray(context.destinations)
|
||||||
|
? context.destinations
|
||||||
|
: [];
|
||||||
|
const transportItems = [
|
||||||
|
{
|
||||||
|
id: "transport-back",
|
||||||
|
title: "Back",
|
||||||
|
description: "Return to the default interaction menu",
|
||||||
|
action: "actor::show::default",
|
||||||
|
},
|
||||||
|
...destinations.map((destination, index) => ({
|
||||||
|
id: `transport-destination-${index}`,
|
||||||
|
title: destination.cost
|
||||||
|
? `${destination.label || destination.name || "Destination"} - $${destination.cost}`
|
||||||
|
: destination.label || destination.name || "Destination",
|
||||||
|
description: "Request transport to this destination",
|
||||||
|
action: "actor::request::transport",
|
||||||
|
context: {
|
||||||
|
...context,
|
||||||
|
destination,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
|
||||||
|
store.dispatch(actions.setMenuItems(transportItems));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const alert = {
|
const alert = {
|
||||||
event: item.action,
|
event: item.action,
|
||||||
data: item.context || {},
|
data: item.context || {},
|
||||||
|
|||||||
1
arma/server/addons/transport/$PBOPREFIX$
Normal file
@ -0,0 +1 @@
|
|||||||
|
forge\forge_server\addons\transport
|
||||||
17
arma/server/addons/transport/CfgEventHandlers.hpp
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
class Extended_PreStart_EventHandlers {
|
||||||
|
class ADDON {
|
||||||
|
init = QUOTE(call COMPILE_SCRIPT(XEH_preStart));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class Extended_PreInit_EventHandlers {
|
||||||
|
class ADDON {
|
||||||
|
init = QUOTE(call COMPILE_SCRIPT(XEH_preInit));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class Extended_PostInit_EventHandlers {
|
||||||
|
class ADDON {
|
||||||
|
init = QUOTE(call COMPILE_SCRIPT(XEH_postInit));
|
||||||
|
};
|
||||||
|
};
|
||||||
2
arma/server/addons/transport/XEH_PREP.hpp
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
PREP(initTransportService);
|
||||||
|
PREP(requestTransport);
|
||||||
4
arma/server/addons/transport/XEH_postInit.sqf
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
|
||||||
|
if (isNil QEGVAR(common,EventBus)) then { call EFUNC(common,eventBus); };
|
||||||
|
if (isNil QGVAR(TransportService)) then { call FUNC(initTransportService); };
|
||||||
22
arma/server/addons/transport/XEH_preInit.sqf
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
|
||||||
|
PREP_RECOMPILE_START;
|
||||||
|
#include "XEH_PREP.hpp"
|
||||||
|
PREP_RECOMPILE_END;
|
||||||
|
|
||||||
|
[QGVAR(requestTransport), {
|
||||||
|
params [
|
||||||
|
["_unit", objNull, [objNull]],
|
||||||
|
["_fromNode", objNull, [objNull]],
|
||||||
|
["_toNode", objNull, [objNull]],
|
||||||
|
["_options", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isNull _unit || { isNull _fromNode || { isNull _toNode } }) exitWith {};
|
||||||
|
|
||||||
|
if (isNil QGVAR(TransportService)) then {
|
||||||
|
call FUNC(initTransportService);
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(TransportService) call ["requestTransport", [_unit, _fromNode, _toNode, _options]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
1
arma/server/addons/transport/XEH_preStart.sqf
Normal file
@ -0,0 +1 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
22
arma/server/addons/transport/config.cpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
|
||||||
|
class CfgPatches {
|
||||||
|
class ADDON {
|
||||||
|
author = AUTHOR;
|
||||||
|
authors[] = {"J.Schmidt"};
|
||||||
|
url = ECSTRING(main,url);
|
||||||
|
name = COMPONENT_NAME;
|
||||||
|
requiredVersion = REQUIRED_VERSION;
|
||||||
|
requiredAddons[] = {
|
||||||
|
"forge_server_main",
|
||||||
|
"forge_server_common",
|
||||||
|
"forge_server_bank",
|
||||||
|
"forge_server_economy"
|
||||||
|
};
|
||||||
|
units[] = {};
|
||||||
|
weapons[] = {};
|
||||||
|
VERSION_CONFIG;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "CfgEventHandlers.hpp"
|
||||||
@ -0,0 +1,399 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: fnc_initTransportService.sqf
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Date: 2026-05-25
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Initializes the server-side paid transport service for player and vehicle
|
||||||
|
* transfers between mission-placed transport nodes.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* Transport service object [HASHMAP OBJECT]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* call forge_server_transport_fnc_initTransportService
|
||||||
|
*/
|
||||||
|
|
||||||
|
if !(isServer) exitWith { objNull };
|
||||||
|
if !(isNil QGVAR(TransportService)) exitWith { GVAR(TransportService) };
|
||||||
|
if (isNil QEGVAR(common,EventBus)) then { call EFUNC(common,eventBus); };
|
||||||
|
|
||||||
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
|
GVAR(TransportServiceBase) = compileFinal createHashMapFromArray [
|
||||||
|
["#type", "TransportService"],
|
||||||
|
["#create", compileFinal {
|
||||||
|
_self set ["baseFare", 100];
|
||||||
|
_self set ["pricePerKm", 50];
|
||||||
|
_self set ["cargoRadius", 25];
|
||||||
|
_self set ["nodePrefix", "transport"];
|
||||||
|
_self set ["vehiclePrefix", "transport_vehicle"];
|
||||||
|
_self set ["arrivalPrefix", "transport_arrival"];
|
||||||
|
_self set ["maxIndexedNodes", 10];
|
||||||
|
_self set ["eventTokens", []];
|
||||||
|
["INFO", "Transport Service Initialized!"] call EFUNC(common,log);
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["notify", compileFinal {
|
||||||
|
params [["_unit", objNull, [objNull]], ["_type", "info", [""]], ["_title", "Transport", [""]], ["_message", "", [""]]];
|
||||||
|
|
||||||
|
if (isNull _unit || { _message isEqualTo "" }) exitWith { false };
|
||||||
|
|
||||||
|
private _uid = getPlayerUID _unit;
|
||||||
|
if (_uid isEqualTo "") exitWith {
|
||||||
|
[_message] remoteExecCall ["systemChat", _unit];
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isNil QEGVAR(common,EventBus)) exitWith {
|
||||||
|
[_message] remoteExecCall ["systemChat", _unit];
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
EGVAR(common,EventBus) call ["emit", [
|
||||||
|
"notification.requested",
|
||||||
|
createHashMapFromArray [
|
||||||
|
["uids", [_uid]],
|
||||||
|
["notificationType", _type],
|
||||||
|
["title", _title],
|
||||||
|
["message", _message]
|
||||||
|
],
|
||||||
|
createHashMapFromArray [["source", "transport"]]
|
||||||
|
]];
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["emit", compileFinal {
|
||||||
|
params [["_eventName", "", [""]], ["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
if (_eventName isEqualTo "" || { isNil QEGVAR(common,EventBus) }) exitWith { createHashMap };
|
||||||
|
|
||||||
|
EGVAR(common,EventBus) call ["emit", [
|
||||||
|
_eventName,
|
||||||
|
_payload,
|
||||||
|
createHashMapFromArray [["source", "transport"]]
|
||||||
|
]]
|
||||||
|
}],
|
||||||
|
["getIndexedNames", compileFinal {
|
||||||
|
params [["_prefix", "", [""]], ["_maxIndex", 10, [0]]];
|
||||||
|
|
||||||
|
private _names = [_prefix];
|
||||||
|
for "_i" from 1 to _maxIndex do {
|
||||||
|
_names pushBack format ["%1_%2", _prefix, _i];
|
||||||
|
};
|
||||||
|
_names
|
||||||
|
}],
|
||||||
|
["getNodes", compileFinal {
|
||||||
|
params [["_options", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _nodeNames = +(_options getOrDefault ["nodeNames", []]);
|
||||||
|
if (_nodeNames isEqualTo []) then {
|
||||||
|
private _prefix = _options getOrDefault ["nodePrefix", _self getOrDefault ["nodePrefix", "transport"]];
|
||||||
|
private _maxIndex = _options getOrDefault ["maxIndexedNodes", _self getOrDefault ["maxIndexedNodes", 10]];
|
||||||
|
_nodeNames = _self call ["getIndexedNames", [_prefix, _maxIndex]];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _nodes = _nodeNames apply { missionNamespace getVariable [_x, objNull] };
|
||||||
|
_nodes select { !isNull _x }
|
||||||
|
}],
|
||||||
|
["getExclusionObjects", compileFinal {
|
||||||
|
params [["_options", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _excluded = +(_options getOrDefault ["excludedObjects", []]);
|
||||||
|
_excluded append (_self call ["getNodes", [_options]]);
|
||||||
|
|
||||||
|
private _vehicleNames = +(_options getOrDefault ["vehicleNames", []]);
|
||||||
|
if (_vehicleNames isEqualTo []) then {
|
||||||
|
private _prefix = _options getOrDefault ["vehiclePrefix", _self getOrDefault ["vehiclePrefix", "transport_vehicle"]];
|
||||||
|
private _maxIndex = _options getOrDefault ["maxIndexedNodes", _self getOrDefault ["maxIndexedNodes", 10]];
|
||||||
|
_vehicleNames = _self call ["getIndexedNames", [_prefix, _maxIndex]];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _vehicles = _vehicleNames apply { missionNamespace getVariable [_x, objNull] };
|
||||||
|
_excluded append (_vehicles select { !isNull _x });
|
||||||
|
_excluded
|
||||||
|
}],
|
||||||
|
["getCost", compileFinal {
|
||||||
|
params [["_fromNode", objNull, [objNull]], ["_toNode", objNull, [objNull]], ["_options", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _baseFare = _options getOrDefault ["baseFare", _self getOrDefault ["baseFare", 100]];
|
||||||
|
private _pricePerKm = _options getOrDefault ["pricePerKm", _self getOrDefault ["pricePerKm", 50]];
|
||||||
|
private _distanceMeters = _fromNode distance2D _toNode;
|
||||||
|
|
||||||
|
round (_baseFare + ((_distanceMeters / 1000) * _pricePerKm))
|
||||||
|
}],
|
||||||
|
["getArrivalMarker", compileFinal {
|
||||||
|
params [["_toNode", objNull, [objNull]], ["_options", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _explicitMarker = _options getOrDefault ["arrivalMarker", ""];
|
||||||
|
if (_explicitMarker isNotEqualTo "") exitWith { _explicitMarker };
|
||||||
|
|
||||||
|
private _nodeName = vehicleVarName _toNode;
|
||||||
|
private _nodePrefix = _options getOrDefault ["nodePrefix", _self getOrDefault ["nodePrefix", "transport"]];
|
||||||
|
private _arrivalPrefix = _options getOrDefault ["arrivalPrefix", _self getOrDefault ["arrivalPrefix", "transport_arrival"]];
|
||||||
|
|
||||||
|
if (_nodeName isEqualTo _nodePrefix) exitWith { _arrivalPrefix };
|
||||||
|
|
||||||
|
private _prefixWithSeparator = format ["%1_", _nodePrefix];
|
||||||
|
if ((_nodeName find _prefixWithSeparator) != 0) exitWith { "" };
|
||||||
|
|
||||||
|
private _suffix = _nodeName select [count _prefixWithSeparator];
|
||||||
|
if (_suffix isEqualTo "") exitWith { "" };
|
||||||
|
|
||||||
|
format ["%1_%2", _arrivalPrefix, _suffix]
|
||||||
|
}],
|
||||||
|
["getArrivalPosition", compileFinal {
|
||||||
|
params [["_toNode", objNull, [objNull]], ["_index", -1, [0]], ["_options", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _marker = _self call ["getArrivalMarker", [_toNode, _options]];
|
||||||
|
private _basePos = if (_marker in allMapMarkers) then {
|
||||||
|
getMarkerPos _marker
|
||||||
|
} else {
|
||||||
|
ASLToATL (_toNode modelToWorldWorld [0, -8, 1.2])
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_index < 0) exitWith { _basePos };
|
||||||
|
|
||||||
|
private _spacingX = _options getOrDefault ["cargoSpacingX", 5];
|
||||||
|
private _spacingY = _options getOrDefault ["cargoSpacingY", 7];
|
||||||
|
private _columns = _options getOrDefault ["cargoColumns", 3];
|
||||||
|
private _xOffset = ((_index % _columns) - floor (_columns / 2)) * _spacingX;
|
||||||
|
private _yOffset = floor (_index / _columns) * _spacingY;
|
||||||
|
|
||||||
|
_basePos vectorAdd [_xOffset, _yOffset, 0]
|
||||||
|
}],
|
||||||
|
["chargePassenger", compileFinal {
|
||||||
|
params [["_unit", objNull, [objNull]], ["_amount", 0, [0]], ["_label", "Transport", [""]]];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", format ["Unable to charge %1 fare.", _label]],
|
||||||
|
["source", ""]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isNull _unit) exitWith { _result };
|
||||||
|
if (_amount <= 0) exitWith {
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["message", ""];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _uid = getPlayerUID _unit;
|
||||||
|
if (_uid isEqualTo "") exitWith {
|
||||||
|
_result set ["message", "A valid player UID is required for transport billing."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
if !(isNil QEGVAR(bank,BankStore)) then {
|
||||||
|
private _account = EGVAR(bank,BankStore) call ["get", [_uid, ""]];
|
||||||
|
if (_account isEqualTo createHashMap) then {
|
||||||
|
_account = EGVAR(bank,BankStore) call ["init", [_uid]];
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_account isNotEqualTo createHashMap) then {
|
||||||
|
private _source = "";
|
||||||
|
if ((_account getOrDefault ["bank", 0]) >= _amount) then {
|
||||||
|
_source = "bank";
|
||||||
|
} else {
|
||||||
|
if ((_account getOrDefault ["cash", 0]) >= _amount) then {
|
||||||
|
_source = "cash";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_source isNotEqualTo "") then {
|
||||||
|
private _charge = EGVAR(bank,BankStore) call ["chargeCheckout", [_uid, _source, _amount, true]];
|
||||||
|
if (_charge getOrDefault ["success", false]) exitWith {
|
||||||
|
EGVAR(bank,BankStore) call ["save", [_uid]];
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["source", _source];
|
||||||
|
_result set ["message", format ["%1 charged $%2 from your %3.", _label, [_amount] call EFUNC(common,formatNumber), _source]];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if !(isNil QEGVAR(economy,SEconomyStore)) then {
|
||||||
|
private _orgCharge = EGVAR(economy,SEconomyStore) call ["chargeOrg", [_unit, _amount, _label, true]];
|
||||||
|
if (_orgCharge getOrDefault ["success", false]) exitWith {
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["source", "org_credit"];
|
||||||
|
_result set ["message", format [
|
||||||
|
"Personal funds could not cover %1. Organization charged $%2 and added it to your credit line.",
|
||||||
|
_label,
|
||||||
|
[_amount] call EFUNC(common,formatNumber)
|
||||||
|
]];
|
||||||
|
};
|
||||||
|
|
||||||
|
_result set ["message", _orgCharge getOrDefault ["message", format ["You cannot afford %1.", _label]]];
|
||||||
|
};
|
||||||
|
|
||||||
|
_result
|
||||||
|
}],
|
||||||
|
["getNearbyCargo", compileFinal {
|
||||||
|
params [
|
||||||
|
["_fromNode", objNull, [objNull]],
|
||||||
|
["_unit", objNull, [objNull]],
|
||||||
|
["_options", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _radius = _options getOrDefault ["cargoRadius", _self getOrDefault ["cargoRadius", 25]];
|
||||||
|
if (_radius <= 0) exitWith { [] };
|
||||||
|
|
||||||
|
private _nearby = nearestObjects [
|
||||||
|
_fromNode,
|
||||||
|
["LandVehicle", "Air", "Ship", "CAManBase"],
|
||||||
|
_radius,
|
||||||
|
true
|
||||||
|
];
|
||||||
|
private _excluded = _self call ["getExclusionObjects", [_options]];
|
||||||
|
|
||||||
|
_nearby select {
|
||||||
|
!isNull _x
|
||||||
|
&& { _x isNotEqualTo _fromNode }
|
||||||
|
&& { !(_x in _excluded) }
|
||||||
|
&& { _x isNotEqualTo _unit }
|
||||||
|
&& { alive _x }
|
||||||
|
&& {
|
||||||
|
(_x isKindOf "LandVehicle")
|
||||||
|
|| { _x isKindOf "Air" }
|
||||||
|
|| { _x isKindOf "Ship" }
|
||||||
|
|| { _x isKindOf "CAManBase" && { isPlayer _x } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
["moveCargo", compileFinal {
|
||||||
|
params [["_cargo", [], [[]]], ["_toNode", objNull, [objNull]], ["_options", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _moved = [];
|
||||||
|
{
|
||||||
|
private _entity = _x;
|
||||||
|
if (isNull _entity) then { continue; };
|
||||||
|
|
||||||
|
private _pos = _self call ["getArrivalPosition", [_toNode, _forEachIndex, _options]];
|
||||||
|
if (_entity isKindOf "CAManBase") then {
|
||||||
|
[_entity, _pos] remoteExecCall ["setPosATL", _entity];
|
||||||
|
} else {
|
||||||
|
_entity setPosATL _pos;
|
||||||
|
_entity setDir (getDir _toNode);
|
||||||
|
};
|
||||||
|
|
||||||
|
_moved pushBack _entity;
|
||||||
|
} forEach _cargo;
|
||||||
|
|
||||||
|
_moved
|
||||||
|
}],
|
||||||
|
["requestTransport", compileFinal {
|
||||||
|
params [
|
||||||
|
["_unit", objNull, [objNull]],
|
||||||
|
["_fromNode", objNull, [objNull]],
|
||||||
|
["_toNode", objNull, [objNull]],
|
||||||
|
["_options", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", "Transport request failed."],
|
||||||
|
["cost", 0],
|
||||||
|
["movedCargo", []]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isNull _unit || { !isPlayer _unit }) exitWith { _result };
|
||||||
|
if (isNull _fromNode || { isNull _toNode }) exitWith { _result };
|
||||||
|
if (_fromNode isEqualTo _toNode) exitWith {
|
||||||
|
_result set ["message", "Origin and destination are the same."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _nodes = _self call ["getNodes", [_options]];
|
||||||
|
if !(_fromNode in _nodes && { _toNode in _nodes }) exitWith {
|
||||||
|
_result set ["message", "Transport route is unavailable."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _label = _options getOrDefault ["label", "Transport"];
|
||||||
|
private _cost = _self call ["getCost", [_fromNode, _toNode, _options]];
|
||||||
|
_result set ["cost", _cost];
|
||||||
|
|
||||||
|
_self call ["emit", [
|
||||||
|
"transport.requested",
|
||||||
|
createHashMapFromArray [
|
||||||
|
["unit", _unit],
|
||||||
|
["uid", getPlayerUID _unit],
|
||||||
|
["from", _fromNode],
|
||||||
|
["to", _toNode],
|
||||||
|
["cost", _cost],
|
||||||
|
["label", _label]
|
||||||
|
]
|
||||||
|
]];
|
||||||
|
|
||||||
|
private _charge = _self call ["chargePassenger", [_unit, _cost, _label]];
|
||||||
|
if !(_charge getOrDefault ["success", false]) exitWith {
|
||||||
|
private _message = _charge getOrDefault ["message", "Transport payment failed."];
|
||||||
|
_result set ["message", _message];
|
||||||
|
_self call ["notify", [_unit, "danger", _label, _message]];
|
||||||
|
_self call ["emit", ["transport.failed", +_result]];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _cargo = if (_options getOrDefault ["includeCargo", true]) then {
|
||||||
|
_self call ["getNearbyCargo", [_fromNode, _unit, _options]]
|
||||||
|
} else {
|
||||||
|
[]
|
||||||
|
};
|
||||||
|
private _destination = _self call ["getArrivalPosition", [_toNode, -1, _options]];
|
||||||
|
private _movedCargo = _self call ["moveCargo", [_cargo, _toNode, _options]];
|
||||||
|
|
||||||
|
[_unit, _destination] remoteExecCall ["setPosATL", _unit];
|
||||||
|
_self call ["notify", [_unit, "info", _label, _charge getOrDefault ["message", format ["%1 paid.", _label]]]];
|
||||||
|
|
||||||
|
if (_movedCargo isNotEqualTo []) then {
|
||||||
|
_self call ["notify", [_unit, "info", _label, format ["Moved %1 nearby passenger/vehicle item(s).", count _movedCargo]]];
|
||||||
|
};
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["message", "Transport completed."];
|
||||||
|
_result set ["movedCargo", _movedCargo];
|
||||||
|
_result set ["paymentSource", _charge getOrDefault ["source", ""]];
|
||||||
|
|
||||||
|
_self call ["emit", ["transport.completed", +_result]];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
|
["registerEventHandlers", compileFinal {
|
||||||
|
if (isNil QEGVAR(common,EventBus)) exitWith { false };
|
||||||
|
if ((_self getOrDefault ["eventTokens", []]) isNotEqualTo []) exitWith { true };
|
||||||
|
|
||||||
|
private _handleRequest = {
|
||||||
|
params ["_event"];
|
||||||
|
|
||||||
|
private _unit = _event getOrDefault ["unit", objNull];
|
||||||
|
private _from = _event getOrDefault ["from", objNull];
|
||||||
|
private _to = _event getOrDefault ["to", objNull];
|
||||||
|
private _options = _event getOrDefault ["options", createHashMap];
|
||||||
|
|
||||||
|
if (isNil QGVAR(TransportService)) exitWith {};
|
||||||
|
GVAR(TransportService) call ["requestTransport", [_unit, _from, _to, _options]];
|
||||||
|
};
|
||||||
|
|
||||||
|
_self set ["eventTokens", [
|
||||||
|
EGVAR(common,EventBus) call ["on", ["transport.request", _handleRequest, "transport.request"]]
|
||||||
|
]];
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["#delete", compileFinal {
|
||||||
|
if !(isNil QEGVAR(common,EventBus)) then {
|
||||||
|
{
|
||||||
|
EGVAR(common,EventBus) call ["off", [_x]];
|
||||||
|
} forEach (_self getOrDefault ["eventTokens", []]);
|
||||||
|
};
|
||||||
|
_self set ["eventTokens", []];
|
||||||
|
}]
|
||||||
|
];
|
||||||
|
|
||||||
|
GVAR(TransportService) = createHashMapObject [GVAR(TransportServiceBase), []];
|
||||||
|
GVAR(TransportService) call ["registerEventHandlers", []];
|
||||||
|
|
||||||
|
GVAR(TransportService)
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: fnc_requestTransport.sqf
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Date: 2026-05-25
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Requests a paid transport transfer for a player and nearby cargo.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* 0: Player unit <OBJECT>
|
||||||
|
* 1: Origin node <OBJECT>
|
||||||
|
* 2: Destination node <OBJECT>
|
||||||
|
* 3: Options <HASHMAP> (optional)
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* Result [HASHMAP]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* [player, transport, transport_1] call forge_server_transport_fnc_requestTransport
|
||||||
|
*/
|
||||||
|
|
||||||
|
params [
|
||||||
|
["_unit", objNull, [objNull]],
|
||||||
|
["_fromNode", objNull, [objNull]],
|
||||||
|
["_toNode", objNull, [objNull]],
|
||||||
|
["_options", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isNil QGVAR(TransportService)) then { call FUNC(initTransportService); };
|
||||||
|
GVAR(TransportService) call ["requestTransport", [_unit, _fromNode, _toNode, _options]]
|
||||||
9
arma/server/addons/transport/script_component.hpp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#define COMPONENT transport
|
||||||
|
#define COMPONENT_BEAUTIFIED Transport
|
||||||
|
#include "\forge\forge_server\addons\main\script_mod.hpp"
|
||||||
|
|
||||||
|
// #define DEBUG_MODE_FULL
|
||||||
|
// #define DISABLE_COMPILE_CACHE
|
||||||
|
// #define ENABLE_PERFORMANCE_COUNTERS
|
||||||
|
|
||||||
|
#include "\forge\forge_server\addons\main\script_macros.hpp"
|
||||||
@ -27,6 +27,42 @@ Rules validated by the Rust service:
|
|||||||
- `locker:get`, `locker:patch`, and `locker:remove` require an existing locker.
|
- `locker:get`, `locker:patch`, and `locker:remove` require an existing locker.
|
||||||
- `locker:remove` takes the classname directly, not a JSON object.
|
- `locker:remove` takes the classname directly, not a JSON object.
|
||||||
|
|
||||||
|
## Multiple Locker Objects
|
||||||
|
|
||||||
|
Editor-placed locker objects are templates and access points, not separate
|
||||||
|
durable inventories. During server post-init, any mission namespace object
|
||||||
|
whose variable name contains `locker` is hidden globally. During client setup,
|
||||||
|
each client reads the hidden object's classname, ASL position, vector direction,
|
||||||
|
and vector up, then creates a matching local locker object with
|
||||||
|
`createVehicleLocal`.
|
||||||
|
|
||||||
|
The local clone is the object the player actually opens. On `ContainerOpened`,
|
||||||
|
the client clears the clone and fills it from the player's UID-owned locker
|
||||||
|
state. On `ContainerClosed`, the client reads the clone's cargo and sends a
|
||||||
|
full locker override back to the server.
|
||||||
|
|
||||||
|
There is no explicit maximum number of editor-placed locker access points. The
|
||||||
|
practical limit is mission performance and how many local container objects are
|
||||||
|
reasonable for the scenario.
|
||||||
|
|
||||||
|
All locker access points load and save the same player locker, keyed by player
|
||||||
|
UID. Opening `locker`, `locker_hq`, or `locker_outpost_1` does not create
|
||||||
|
separate persistent inventories; those objects are separate local access clones
|
||||||
|
for the same underlying player locker.
|
||||||
|
|
||||||
|
## Store Grants and Duplicate Inventory
|
||||||
|
|
||||||
|
The store checkout path grants items to the UID-owned locker hot state, not to a
|
||||||
|
specific placed locker object. Item grants are merged by classname:
|
||||||
|
|
||||||
|
- buying a new classname adds one new locker entry
|
||||||
|
- buying an existing classname increases that entry's amount
|
||||||
|
- checkout fails if the result would exceed 25 unique classnames
|
||||||
|
|
||||||
|
Having more than one locker object on the map does not duplicate store grants.
|
||||||
|
Duplicate quantities can only come from repeated checkout requests or repeated
|
||||||
|
manual locker writes, not from the number of placed locker access points.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
All commands are called on the `locker` group.
|
All commands are called on the `locker` group.
|
||||||
|
|||||||
@ -30,6 +30,7 @@ case-sensitive in some initializers, so use lower-case names.
|
|||||||
| Store | name contains `store` | `isStore = true` | Store UI | Store catalog and checkout behavior are configured server-side. |
|
| Store | name contains `store` | `isStore = true` | Store UI | Store catalog and checkout behavior are configured server-side. |
|
||||||
| Garage | name contains `garage` | `isGarage = true` | Garage UI and virtual garage | Include a garage category in the name or set `garageType` manually. |
|
| Garage | name contains `garage` | `isGarage = true` | Garage UI and virtual garage | Include a garage category in the name or set `garageType` manually. |
|
||||||
| Locker | name contains `locker` | local `isLocker = true` | Virtual arsenal action | The server hides the editor object; each client creates a local locker at the same position. |
|
| Locker | name contains `locker` | local `isLocker = true` | Virtual arsenal action | The server hides the editor object; each client creates a local locker at the same position. |
|
||||||
|
| Transport | `transport`, `transport_1` through `transport_10` | discovered by variable name or `isTransport = true` | Transport destination menu | Paid player and cargo transfer between named transport nodes. |
|
||||||
|
|
||||||
Recommended object names:
|
Recommended object names:
|
||||||
|
|
||||||
@ -38,6 +39,8 @@ atm
|
|||||||
bank
|
bank
|
||||||
store
|
store
|
||||||
locker
|
locker
|
||||||
|
transport
|
||||||
|
transport_1
|
||||||
garage_hq
|
garage_hq
|
||||||
garage_hq_2
|
garage_hq_2
|
||||||
```
|
```
|
||||||
@ -60,6 +63,7 @@ _atmTerminal setVariable ["isAtm", true, true];
|
|||||||
_storeCounter setVariable ["isStore", true, true];
|
_storeCounter setVariable ["isStore", true, true];
|
||||||
_garageTerminal setVariable ["isGarage", true, true];
|
_garageTerminal setVariable ["isGarage", true, true];
|
||||||
_garageTerminal setVariable ["garageType", "cars", true];
|
_garageTerminal setVariable ["garageType", "cars", true];
|
||||||
|
_transportNode setVariable ["isTransport", true, true];
|
||||||
```
|
```
|
||||||
|
|
||||||
Supported garage types are:
|
Supported garage types are:
|
||||||
@ -142,6 +146,67 @@ Minimum Eden setup:
|
|||||||
2. Set its Eden variable name to something containing `store`.
|
2. Set its Eden variable name to something containing `store`.
|
||||||
3. Test that the actor menu shows the store action within 5 meters.
|
3. Test that the actor menu shows the store action within 5 meters.
|
||||||
|
|
||||||
|
## Transport Setup
|
||||||
|
|
||||||
|
Transport nodes are generic paid travel points. They can represent ferries,
|
||||||
|
airports, bus stops, teleport terminals, or any other mission transport system.
|
||||||
|
The framework owns the menu, billing, cargo scan, and movement logic. The
|
||||||
|
mission only needs placed objects and optional arrival markers.
|
||||||
|
|
||||||
|

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

|
||||||
|
|
||||||
|
Place transport node objects with these variable names:
|
||||||
|
|
||||||
|
```text
|
||||||
|
transport
|
||||||
|
transport_1
|
||||||
|
transport_2
|
||||||
|
...
|
||||||
|
transport_10
|
||||||
|
```
|
||||||
|
|
||||||
|
Place optional arrival markers with matching suffixes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
transport_arrival
|
||||||
|
transport_arrival_1
|
||||||
|
transport_arrival_2
|
||||||
|
...
|
||||||
|
transport_arrival_10
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Objects that should be excluded from the nearby cargo scan, such as the actual
|
||||||
|
boat or transport vehicle used as set dressing, should use:
|
||||||
|
|
||||||
|
```text
|
||||||
|
transport_vehicle
|
||||||
|
transport_vehicle_1
|
||||||
|
transport_vehicle_2
|
||||||
|
...
|
||||||
|
transport_vehicle_10
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Minimum Eden setup:
|
||||||
|
|
||||||
|
1. Place at least two transport node objects.
|
||||||
|
2. Name them `transport`, `transport_1`, and so on.
|
||||||
|
3. Place matching `transport_arrival*` markers where players and cargo should
|
||||||
|
appear.
|
||||||
|
4. Name any set-dressing transport vehicles `transport_vehicle*` so they are
|
||||||
|
not moved as cargo.
|
||||||
|
5. Test that the actor menu shows Transport within 5 meters of a node.
|
||||||
|
|
||||||
|
The default fare is `$100 + distance in kilometers * $50`. The server charges
|
||||||
|
player bank first, player cash second, then organization credit line fallback.
|
||||||
|
See [Transport Service Guide](./TRANSPORT_SERVICE_GUIDE.md) for override
|
||||||
|
variables and implementation details.
|
||||||
|
|
||||||
## Bank and ATM Setup
|
## Bank and ATM Setup
|
||||||
|
|
||||||
Bank and ATM objects intentionally expose different workflows.
|
Bank and ATM objects intentionally expose different workflows.
|
||||||
@ -179,7 +244,7 @@ Minimum Eden setup:
|
|||||||
Locker objects are slightly different from other interaction objects. The
|
Locker objects are slightly different from other interaction objects. The
|
||||||
server finds editor-placed objects whose variable names contain `locker`, hides
|
server finds editor-placed objects whose variable names contain `locker`, hides
|
||||||
those global objects, and each client creates a local locker object at the same
|
those global objects, and each client creates a local locker object at the same
|
||||||
position.
|
position using the placed object's classname and orientation.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -192,6 +257,11 @@ Minimum Eden setup:
|
|||||||
3. Do not use `forge_locker_box`.
|
3. Do not use `forge_locker_box`.
|
||||||
4. Test that the local locker appears and opens the virtual arsenal action.
|
4. Test that the local locker appears and opens the virtual arsenal action.
|
||||||
|
|
||||||
|
There is no editor-side maximum number of locker access points. Multiple locker
|
||||||
|
objects on a map create multiple local access clones, but all of those clones
|
||||||
|
load and save the same UID-owned player locker state. They do not create
|
||||||
|
separate persistent lockers or cause store grants to duplicate by themselves.
|
||||||
|
|
||||||
## Medical Spawn Setup
|
## Medical Spawn Setup
|
||||||
|
|
||||||
The medical economy store discovers up to eleven medical spawn objects by exact
|
The medical economy store discovers up to eleven medical spawn objects by exact
|
||||||
|
|||||||
@ -214,6 +214,32 @@ physical vehicle into the player's 5-slot garage. Use the virtual garage to
|
|||||||
spawn an unlocked vehicle, and use the garage to store or retrieve live world
|
spawn an unlocked vehicle, and use the garage to store or retrieve live world
|
||||||
vehicles.
|
vehicles.
|
||||||
|
|
||||||
|
## Transport
|
||||||
|
|
||||||
|
Transport points let players pay to travel between configured mission locations.
|
||||||
|
They may represent ferries, terminals, air shuttles, or other mission-specific
|
||||||
|
travel points.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Player workflow:
|
||||||
|
|
||||||
|
1. Stand near a transport point.
|
||||||
|
2. Open the actor interaction menu.
|
||||||
|
3. Select Transport.
|
||||||
|
4. Select a destination from the transport submenu, or select Close to return
|
||||||
|
to the default interaction menu.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The destination price is based on distance. The server charges player bank
|
||||||
|
first, player cash second, then organization credit line fallback when
|
||||||
|
available. If payment succeeds, the player is moved to the selected arrival
|
||||||
|
point. Nearby eligible vehicles or passengers may be moved with the player when
|
||||||
|
the mission has configured the transport point for cargo movement.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Locker and Virtual Arsenal
|
## Locker and Virtual Arsenal
|
||||||
|
|
||||||
The locker is personal item storage.
|
The locker is personal item storage.
|
||||||
@ -225,6 +251,10 @@ Locker rules:
|
|||||||
- Up to 25 items can be stored.
|
- Up to 25 items can be stored.
|
||||||
- The locker saves when the locker container is closed.
|
- The locker saves when the locker container is closed.
|
||||||
- Over-capacity storage can warn or fail depending on server handling.
|
- Over-capacity storage can warn or fail depending on server handling.
|
||||||
|
- Multiple locker access points on the map open local copies of the locker
|
||||||
|
object, but all of them use the same personal locker inventory.
|
||||||
|
- Store purchases merge granted items into the same personal locker by
|
||||||
|
classname; extra locker objects on the map do not duplicate store grants.
|
||||||
|
|
||||||
The virtual arsenal is locked down. Players only see gear they have been
|
The virtual arsenal is locked down. Players only see gear they have been
|
||||||
granted or have unlocked through systems such as the store. The virtual arsenal
|
granted or have unlocked through systems such as the store. The virtual arsenal
|
||||||
|
|||||||
135
docs/TRANSPORT_SERVICE_GUIDE.md
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
# Transport Service Guide
|
||||||
|
|
||||||
|
The transport service provides paid point-to-point travel for players and
|
||||||
|
nearby vehicles or passengers. It is framework-owned: missions only need placed
|
||||||
|
transport objects and optional arrival markers with the expected variable names.
|
||||||
|
|
||||||
|
## Mission Contract
|
||||||
|
|
||||||
|
By default the framework discovers transport nodes by exact mission namespace
|
||||||
|
variable name:
|
||||||
|
|
||||||
|
```text
|
||||||
|
transport
|
||||||
|
transport_1
|
||||||
|
transport_2
|
||||||
|
...
|
||||||
|
transport_10
|
||||||
|
```
|
||||||
|
|
||||||
|
Each node is an Eden-placed object players can stand near. When a player opens
|
||||||
|
the actor interaction menu within 5 meters of a node, the menu shows a
|
||||||
|
Transport action. Selecting Transport opens destination choices for the other
|
||||||
|
configured nodes.
|
||||||
|
|
||||||
|
Arrival markers use the same suffix:
|
||||||
|
|
||||||
|
```text
|
||||||
|
transport_arrival
|
||||||
|
transport_arrival_1
|
||||||
|
transport_arrival_2
|
||||||
|
...
|
||||||
|
transport_arrival_10
|
||||||
|
```
|
||||||
|
|
||||||
|
Object names used only to exclude parked ferry/transport vehicles from cargo
|
||||||
|
pickup scans use this convention:
|
||||||
|
|
||||||
|
```text
|
||||||
|
transport_vehicle
|
||||||
|
transport_vehicle_1
|
||||||
|
transport_vehicle_2
|
||||||
|
...
|
||||||
|
transport_vehicle_10
|
||||||
|
```
|
||||||
|
|
||||||
|
The suffix mapping is direct:
|
||||||
|
|
||||||
|
- `transport` arrives at `transport_arrival`
|
||||||
|
- `transport_1` arrives at `transport_arrival_1`
|
||||||
|
- `transport_10` arrives at `transport_arrival_10`
|
||||||
|
|
||||||
|
If an arrival marker is missing, the framework falls back to a position behind
|
||||||
|
the destination node object.
|
||||||
|
|
||||||
|
## Pricing and Payment
|
||||||
|
|
||||||
|
The default fare is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
base fare + distance in kilometers * price per kilometer
|
||||||
|
```
|
||||||
|
|
||||||
|
Current defaults:
|
||||||
|
|
||||||
|
- base fare: `$100`
|
||||||
|
- price per kilometer: `$50`
|
||||||
|
- cargo scan radius: `25` meters
|
||||||
|
- max indexed nodes: `10`
|
||||||
|
|
||||||
|
Payment is server-authoritative. The transport service attempts payment in this
|
||||||
|
order:
|
||||||
|
|
||||||
|
1. Player bank balance.
|
||||||
|
2. Player cash.
|
||||||
|
3. Organization credit line fallback.
|
||||||
|
|
||||||
|
The player and cargo are moved only after payment succeeds.
|
||||||
|
|
||||||
|
## Cargo and Vehicle Transfer
|
||||||
|
|
||||||
|
When a player requests transport, the server scans near the origin node for
|
||||||
|
nearby vehicles, ships, aircraft, and player units. The scan ignores:
|
||||||
|
|
||||||
|
- the origin and destination transport nodes
|
||||||
|
- objects named with the `transport_vehicle` prefix
|
||||||
|
- the requesting player
|
||||||
|
- dead entities
|
||||||
|
|
||||||
|
Use `transport_vehicle*` names for the actual boat, ferry, aircraft, or set
|
||||||
|
dressing object that should not be moved as cargo.
|
||||||
|
|
||||||
|
## Optional Per-Node Overrides
|
||||||
|
|
||||||
|
The default naming convention should cover normal missions. If a specific
|
||||||
|
mission needs another prefix or different pricing, set variables on the
|
||||||
|
transport node object:
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
this setVariable ["isTransport", true, true];
|
||||||
|
this setVariable ["transportLabel", "North Dock", true];
|
||||||
|
this setVariable ["transportNodePrefix", "dock", true];
|
||||||
|
this setVariable ["transportVehiclePrefix", "dock_vehicle", true];
|
||||||
|
this setVariable ["transportArrivalPrefix", "dock_arrival", true];
|
||||||
|
this setVariable ["transportMaxIndexedNodes", 4, true];
|
||||||
|
this setVariable ["transportBaseFare", 150, true];
|
||||||
|
this setVariable ["transportPricePerKm", 75, true];
|
||||||
|
this setVariable ["transportCargoRadius", 25, true];
|
||||||
|
this setVariable ["transportIncludeCargo", true, true];
|
||||||
|
```
|
||||||
|
|
||||||
|
Only use overrides when the default `transport*` convention is not appropriate.
|
||||||
|
|
||||||
|
## Image Checklist
|
||||||
|
|
||||||
|
Replace these placeholder image references after screenshots are captured:
|
||||||
|
|
||||||
|

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

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

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

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

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

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

|
||||||
|
|
||||||
|
## Mission-Side Code Requirement
|
||||||
|
|
||||||
|
No mission-side transport service, addAction script, or server event bridge is
|
||||||
|
required. The framework handles menu discovery, destination selection, pricing,
|
||||||
|
billing, cargo movement, and EventBus notifications.
|
||||||
9
docs/images/eden/transport_arrival_marker.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Placeholder: Eden transport arrival marker placement</title>
|
||||||
|
<desc id="desc">Capture an Eden Editor screenshot showing the arrival marker location.</desc>
|
||||||
|
<rect width="1280" height="720" fill="#202733"/>
|
||||||
|
<rect x="80" y="70" width="1120" height="580" rx="8" fill="#2f3948" stroke="#7d8aa0" stroke-width="4" stroke-dasharray="18 14"/>
|
||||||
|
<text x="640" y="280" fill="#f3f5f7" font-family="Arial, sans-serif" font-size="48" text-anchor="middle">PLACEHOLDER IMAGE</text>
|
||||||
|
<text x="640" y="352" fill="#d7dde7" font-family="Arial, sans-serif" font-size="30" text-anchor="middle">Eden: transport arrival marker</text>
|
||||||
|
<text x="640" y="410" fill="#aeb9c9" font-family="Arial, sans-serif" font-size="24" text-anchor="middle">Show where players and cargo should spawn.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 930 B |
9
docs/images/eden/transport_node_obj.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Placeholder: Eden transport node object placement</title>
|
||||||
|
<desc id="desc">Capture an Eden Editor screenshot showing the placed transport access object.</desc>
|
||||||
|
<rect width="1280" height="720" fill="#202733"/>
|
||||||
|
<rect x="80" y="70" width="1120" height="580" rx="8" fill="#2f3948" stroke="#7d8aa0" stroke-width="4" stroke-dasharray="18 14"/>
|
||||||
|
<text x="640" y="280" fill="#f3f5f7" font-family="Arial, sans-serif" font-size="48" text-anchor="middle">PLACEHOLDER IMAGE</text>
|
||||||
|
<text x="640" y="352" fill="#d7dde7" font-family="Arial, sans-serif" font-size="30" text-anchor="middle">Eden: transport node object placement</text>
|
||||||
|
<text x="640" y="410" fill="#aeb9c9" font-family="Arial, sans-serif" font-size="24" text-anchor="middle">Show the object players interact with for transport.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 951 B |
9
docs/images/eden/transport_node_var.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Placeholder: Eden transport node variable name</title>
|
||||||
|
<desc id="desc">Capture the Eden object attributes panel with the transport variable name.</desc>
|
||||||
|
<rect width="1280" height="720" fill="#202733"/>
|
||||||
|
<rect x="80" y="70" width="1120" height="580" rx="8" fill="#2f3948" stroke="#7d8aa0" stroke-width="4" stroke-dasharray="18 14"/>
|
||||||
|
<text x="640" y="280" fill="#f3f5f7" font-family="Arial, sans-serif" font-size="48" text-anchor="middle">PLACEHOLDER IMAGE</text>
|
||||||
|
<text x="640" y="352" fill="#d7dde7" font-family="Arial, sans-serif" font-size="30" text-anchor="middle">Eden: transport variable name</text>
|
||||||
|
<text x="640" y="410" fill="#aeb9c9" font-family="Arial, sans-serif" font-size="24" text-anchor="middle">Show transport, transport_1, through transport_10.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 935 B |
9
docs/images/eden/transport_vehicle_var.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Placeholder: Eden transport vehicle variable name</title>
|
||||||
|
<desc id="desc">Capture the variable name for the transport vehicle that should be excluded from cargo scans.</desc>
|
||||||
|
<rect width="1280" height="720" fill="#202733"/>
|
||||||
|
<rect x="80" y="70" width="1120" height="580" rx="8" fill="#2f3948" stroke="#7d8aa0" stroke-width="4" stroke-dasharray="18 14"/>
|
||||||
|
<text x="640" y="280" fill="#f3f5f7" font-family="Arial, sans-serif" font-size="48" text-anchor="middle">PLACEHOLDER IMAGE</text>
|
||||||
|
<text x="640" y="352" fill="#d7dde7" font-family="Arial, sans-serif" font-size="30" text-anchor="middle">Eden: transport_vehicle variable</text>
|
||||||
|
<text x="640" y="410" fill="#aeb9c9" font-family="Arial, sans-serif" font-size="24" text-anchor="middle">Show transport_vehicle, transport_vehicle_1, etc.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 959 B |
9
docs/images/player/transport_complete.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Placeholder: Player transport completion</title>
|
||||||
|
<desc id="desc">Capture the player and cargo after a successful transport request.</desc>
|
||||||
|
<rect width="1280" height="720" fill="#202733"/>
|
||||||
|
<rect x="80" y="70" width="1120" height="580" rx="8" fill="#2f3948" stroke="#7d8aa0" stroke-width="4" stroke-dasharray="18 14"/>
|
||||||
|
<text x="640" y="280" fill="#f3f5f7" font-family="Arial, sans-serif" font-size="48" text-anchor="middle">PLACEHOLDER IMAGE</text>
|
||||||
|
<text x="640" y="352" fill="#d7dde7" font-family="Arial, sans-serif" font-size="30" text-anchor="middle">Player: transport complete</text>
|
||||||
|
<text x="640" y="410" fill="#aeb9c9" font-family="Arial, sans-serif" font-size="24" text-anchor="middle">Show arrival at the destination with moved vehicles nearby.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 927 B |
9
docs/images/player/transport_destination_menu.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Placeholder: Player transport destination menu</title>
|
||||||
|
<desc id="desc">Capture the destination submenu after selecting Transport.</desc>
|
||||||
|
<rect width="1280" height="720" fill="#202733"/>
|
||||||
|
<rect x="80" y="70" width="1120" height="580" rx="8" fill="#2f3948" stroke="#7d8aa0" stroke-width="4" stroke-dasharray="18 14"/>
|
||||||
|
<text x="640" y="280" fill="#f3f5f7" font-family="Arial, sans-serif" font-size="48" text-anchor="middle">PLACEHOLDER IMAGE</text>
|
||||||
|
<text x="640" y="352" fill="#d7dde7" font-family="Arial, sans-serif" font-size="30" text-anchor="middle">Player: destination submenu</text>
|
||||||
|
<text x="640" y="410" fill="#aeb9c9" font-family="Arial, sans-serif" font-size="24" text-anchor="middle">Show destination choices and the Back action.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 912 B |
9
docs/images/player/transport_menu_action.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Placeholder: Player transport menu action</title>
|
||||||
|
<desc id="desc">Capture the actor interaction menu showing the Transport action.</desc>
|
||||||
|
<rect width="1280" height="720" fill="#202733"/>
|
||||||
|
<rect x="80" y="70" width="1120" height="580" rx="8" fill="#2f3948" stroke="#7d8aa0" stroke-width="4" stroke-dasharray="18 14"/>
|
||||||
|
<text x="640" y="280" fill="#f3f5f7" font-family="Arial, sans-serif" font-size="48" text-anchor="middle">PLACEHOLDER IMAGE</text>
|
||||||
|
<text x="640" y="352" fill="#d7dde7" font-family="Arial, sans-serif" font-size="30" text-anchor="middle">Player: Transport action</text>
|
||||||
|
<text x="640" y="410" fill="#aeb9c9" font-family="Arial, sans-serif" font-size="24" text-anchor="middle">Show the first interaction menu near a transport node.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 919 B |
@ -3,6 +3,52 @@ title: "Development Guide"
|
|||||||
description: "This guide covers the usual path for adding or changing a Forge module."
|
description: "This guide covers the usual path for adding or changing a Forge module."
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Repository Workflow
|
||||||
|
|
||||||
|
Use [Git Workflow](/getting-started/git-workflow) as the source of truth for branch roles,
|
||||||
|
release tags, and mission branch handling. The short version is:
|
||||||
|
|
||||||
|
- Use `pre-v0.2` for framework development after the `v0.1.0` baseline.
|
||||||
|
- Keep `master` as the clean release baseline branch.
|
||||||
|
- Keep mission folders off `master`; mission work belongs on
|
||||||
|
`missions/local-mission-copies`.
|
||||||
|
- Keep `archive/pre-v0.1-history` read-only unless recovering old work.
|
||||||
|
- Bring reusable mission logic back to framework branches by copying only the
|
||||||
|
needed framework files or code, not by merging the mission branch.
|
||||||
|
|
||||||
|
Use the workflow helper for the routine checks:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm run workflow -- status
|
||||||
|
npm run workflow -- doctor
|
||||||
|
npm run workflow -- switch dev
|
||||||
|
npm run workflow -- switch missions
|
||||||
|
```
|
||||||
|
|
||||||
|
Example framework workflow:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git switch pre-v0.2
|
||||||
|
git pull
|
||||||
|
git switch -c feature/cad-task-request
|
||||||
|
|
||||||
|
# make framework changes
|
||||||
|
git status --short --branch
|
||||||
|
git add arma/client/addons/cad arma/server/addons/cad
|
||||||
|
git commit -m "Add CAD task request workflow"
|
||||||
|
```
|
||||||
|
|
||||||
|
Example mission workflow:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git switch missions/local-mission-copies
|
||||||
|
|
||||||
|
# make mission changes
|
||||||
|
git status --short --branch
|
||||||
|
git add arma/forge_pmc_simulator.Tanoa
|
||||||
|
git commit -m "Update PMC simulator mission setup"
|
||||||
|
```
|
||||||
|
|
||||||
## Local Checks
|
## Local Checks
|
||||||
|
|
||||||
Before running storage-backed workflows locally, complete
|
Before running storage-backed workflows locally, complete
|
||||||
|
|||||||
158
docus/content/1.getting-started/4.git-workflow.md
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
---
|
||||||
|
title: "Git Workflow"
|
||||||
|
description: "This repository uses `master` as the clean framework branch. Mission folders are kept off `master` so the framework can be versioned without bundling local test missions or playable mission copies."
|
||||||
|
---
|
||||||
|
|
||||||
|
## Workflow Helper
|
||||||
|
|
||||||
|
The repository includes a small helper for the common branch checks and branch
|
||||||
|
switching commands:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm run workflow -- status
|
||||||
|
npm run workflow -- doctor
|
||||||
|
npm run workflow -- switch dev
|
||||||
|
npm run workflow -- switch missions
|
||||||
|
npm run workflow -- start-feature cad-task-request
|
||||||
|
npm run workflow -- release-check
|
||||||
|
```
|
||||||
|
|
||||||
|
The helper refuses branch switches and feature branch creation when the working
|
||||||
|
tree has uncommitted changes. Use the manual Git commands below when you need
|
||||||
|
more control.
|
||||||
|
|
||||||
|
## Branch Roles
|
||||||
|
|
||||||
|
- `master`: framework source, addon code, Rust extension code, docs, tooling,
|
||||||
|
and release tags.
|
||||||
|
- `missions/local-mission-copies`: local mission folders used for testing and
|
||||||
|
mission iteration. This branch is not pushed unless intentionally needed.
|
||||||
|
- `archive/pre-v0.1-history`: read-only archive of the previous full `master`
|
||||||
|
history before the `v0.1.0` baseline cleanup.
|
||||||
|
|
||||||
|
## Daily Framework Work
|
||||||
|
|
||||||
|
Start from the clean framework branch.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git switch master
|
||||||
|
git pull
|
||||||
|
git status --short --branch
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a short-lived feature branch for framework work.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git switch -c feature/garage-marker-selection
|
||||||
|
```
|
||||||
|
|
||||||
|
Make the change, validate it, then commit.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git status --short --branch
|
||||||
|
git add arma/client/addons/garage/functions/fnc_initContextService.sqf
|
||||||
|
git commit -m "Improve garage spawn marker selection"
|
||||||
|
```
|
||||||
|
|
||||||
|
Merge the work back into `master`. Squash merges keep future `master` history
|
||||||
|
compact.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git switch master
|
||||||
|
git merge --squash feature/garage-marker-selection
|
||||||
|
git commit -m "Improve garage spawn marker selection"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove the local feature branch when it is no longer needed.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git branch -D feature/garage-marker-selection
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mission Work
|
||||||
|
|
||||||
|
Switch to the local mission branch before editing mission folders.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git switch missions/local-mission-copies
|
||||||
|
git status --short --branch
|
||||||
|
```
|
||||||
|
|
||||||
|
Mission folders currently tracked on that branch:
|
||||||
|
|
||||||
|
```text
|
||||||
|
arma/forge_framework.Malden
|
||||||
|
arma/forge_pmc_simulator.Tanoa
|
||||||
|
arma/forge_pmc_simulator_v2.Tanoa
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit mission-only changes on the mission branch.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add arma/forge_pmc_simulator.Tanoa
|
||||||
|
git commit -m "Update PMC simulator mission setup"
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not merge the mission branch into `master`. If a mission change becomes
|
||||||
|
framework code, copy only the reusable files or logic onto a framework feature
|
||||||
|
branch created from `master`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git switch master
|
||||||
|
git switch -c feature/cad-on-demand-task-request
|
||||||
|
|
||||||
|
# Bring over only the framework files needed from the mission branch.
|
||||||
|
git checkout missions/local-mission-copies -- arma/client/addons/cad/functions/fnc_initUIBridge.sqf
|
||||||
|
git checkout missions/local-mission-copies -- arma/server/addons/cad/XEH_preInit.sqf
|
||||||
|
|
||||||
|
git add arma/client/addons/cad/functions/fnc_initUIBridge.sqf arma/server/addons/cad/XEH_preInit.sqf
|
||||||
|
git commit -m "Add CAD on-demand mission task request bridge"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Release Versioning
|
||||||
|
|
||||||
|
Use tags to mark framework releases.
|
||||||
|
|
||||||
|
Version guideline:
|
||||||
|
|
||||||
|
- Patch, such as `v0.1.1`: fixes and small compatible changes.
|
||||||
|
- Minor, such as `v0.2.0`: new modules or features.
|
||||||
|
- Major, such as `v1.0.0`: stable release line or breaking changes.
|
||||||
|
|
||||||
|
Create a release tag from `master`.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git switch master
|
||||||
|
git pull
|
||||||
|
git status --short --branch
|
||||||
|
git tag -a v0.1.1 -m "v0.1.1"
|
||||||
|
git push origin master
|
||||||
|
git push origin v0.1.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safety Checks
|
||||||
|
|
||||||
|
Before committing on `master`, check that no mission folders are staged.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git status --short --branch
|
||||||
|
```
|
||||||
|
|
||||||
|
On `master`, these paths should not appear:
|
||||||
|
|
||||||
|
```text
|
||||||
|
arma/forge_framework.Malden
|
||||||
|
arma/forge_pmc_simulator.Tanoa
|
||||||
|
arma/forge_pmc_simulator_v2.Tanoa
|
||||||
|
```
|
||||||
|
|
||||||
|
If mission files appear while on `master`, stop and switch to the mission
|
||||||
|
branch before continuing.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git switch missions/local-mission-copies
|
||||||
|
```
|
||||||
|
|
||||||
@ -30,6 +30,7 @@ case-sensitive in some initializers, so use lower-case names.
|
|||||||
| Store | name contains `store` | `isStore = true` | Store UI | Store catalog and checkout behavior are configured server-side. |
|
| Store | name contains `store` | `isStore = true` | Store UI | Store catalog and checkout behavior are configured server-side. |
|
||||||
| Garage | name contains `garage` | `isGarage = true` | Garage UI and virtual garage | Include a garage category in the name or set `garageType` manually. |
|
| Garage | name contains `garage` | `isGarage = true` | Garage UI and virtual garage | Include a garage category in the name or set `garageType` manually. |
|
||||||
| Locker | name contains `locker` | local `isLocker = true` | Virtual arsenal action | The server hides the editor object; each client creates a local locker at the same position. |
|
| Locker | name contains `locker` | local `isLocker = true` | Virtual arsenal action | The server hides the editor object; each client creates a local locker at the same position. |
|
||||||
|
| Transport | `transport`, `transport_1` through `transport_10` | discovered by variable name or `isTransport = true` | Transport destination menu | Paid player and cargo transfer between named transport nodes. |
|
||||||
|
|
||||||
Recommended object names:
|
Recommended object names:
|
||||||
|
|
||||||
@ -38,6 +39,8 @@ atm
|
|||||||
bank
|
bank
|
||||||
store
|
store
|
||||||
locker
|
locker
|
||||||
|
transport
|
||||||
|
transport_1
|
||||||
garage_hq
|
garage_hq
|
||||||
garage_hq_2
|
garage_hq_2
|
||||||
```
|
```
|
||||||
@ -60,6 +63,7 @@ _atmTerminal setVariable ["isAtm", true, true];
|
|||||||
_storeCounter setVariable ["isStore", true, true];
|
_storeCounter setVariable ["isStore", true, true];
|
||||||
_garageTerminal setVariable ["isGarage", true, true];
|
_garageTerminal setVariable ["isGarage", true, true];
|
||||||
_garageTerminal setVariable ["garageType", "cars", true];
|
_garageTerminal setVariable ["garageType", "cars", true];
|
||||||
|
_transportNode setVariable ["isTransport", true, true];
|
||||||
```
|
```
|
||||||
|
|
||||||
Supported garage types are:
|
Supported garage types are:
|
||||||
@ -142,6 +146,67 @@ Minimum Eden setup:
|
|||||||
2. Set its Eden variable name to something containing `store`.
|
2. Set its Eden variable name to something containing `store`.
|
||||||
3. Test that the actor menu shows the store action within 5 meters.
|
3. Test that the actor menu shows the store action within 5 meters.
|
||||||
|
|
||||||
|
## Transport Setup
|
||||||
|
|
||||||
|
Transport nodes are generic paid travel points. They can represent ferries,
|
||||||
|
airports, bus stops, teleport terminals, or any other mission transport system.
|
||||||
|
The framework owns the menu, billing, cargo scan, and movement logic. The
|
||||||
|
mission only needs placed objects and optional arrival markers.
|
||||||
|
|
||||||
|

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

|
||||||
|
|
||||||
|
Place transport node objects with these variable names:
|
||||||
|
|
||||||
|
```text
|
||||||
|
transport
|
||||||
|
transport_1
|
||||||
|
transport_2
|
||||||
|
...
|
||||||
|
transport_10
|
||||||
|
```
|
||||||
|
|
||||||
|
Place optional arrival markers with matching suffixes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
transport_arrival
|
||||||
|
transport_arrival_1
|
||||||
|
transport_arrival_2
|
||||||
|
...
|
||||||
|
transport_arrival_10
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Objects that should be excluded from the nearby cargo scan, such as the actual
|
||||||
|
boat or transport vehicle used as set dressing, should use:
|
||||||
|
|
||||||
|
```text
|
||||||
|
transport_vehicle
|
||||||
|
transport_vehicle_1
|
||||||
|
transport_vehicle_2
|
||||||
|
...
|
||||||
|
transport_vehicle_10
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Minimum Eden setup:
|
||||||
|
|
||||||
|
1. Place at least two transport node objects.
|
||||||
|
2. Name them `transport`, `transport_1`, and so on.
|
||||||
|
3. Place matching `transport_arrival*` markers where players and cargo should
|
||||||
|
appear.
|
||||||
|
4. Name any set-dressing transport vehicles `transport_vehicle*` so they are
|
||||||
|
not moved as cargo.
|
||||||
|
5. Test that the actor menu shows Transport within 5 meters of a node.
|
||||||
|
|
||||||
|
The default fare is `$100 + distance in kilometers * $50`. The server charges
|
||||||
|
player bank first, player cash second, then organization credit line fallback.
|
||||||
|
See [Transport Service Guide](/server-modules/transport-service) for override
|
||||||
|
variables and implementation details.
|
||||||
|
|
||||||
## Bank and ATM Setup
|
## Bank and ATM Setup
|
||||||
|
|
||||||
Bank and ATM objects intentionally expose different workflows.
|
Bank and ATM objects intentionally expose different workflows.
|
||||||
@ -179,7 +244,7 @@ Minimum Eden setup:
|
|||||||
Locker objects are slightly different from other interaction objects. The
|
Locker objects are slightly different from other interaction objects. The
|
||||||
server finds editor-placed objects whose variable names contain `locker`, hides
|
server finds editor-placed objects whose variable names contain `locker`, hides
|
||||||
those global objects, and each client creates a local locker object at the same
|
those global objects, and each client creates a local locker object at the same
|
||||||
position.
|
position using the placed object's classname and orientation.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -192,6 +257,11 @@ Minimum Eden setup:
|
|||||||
3. Do not use `forge_locker_box`.
|
3. Do not use `forge_locker_box`.
|
||||||
4. Test that the local locker appears and opens the virtual arsenal action.
|
4. Test that the local locker appears and opens the virtual arsenal action.
|
||||||
|
|
||||||
|
There is no editor-side maximum number of locker access points. Multiple locker
|
||||||
|
objects on a map create multiple local access clones, but all of those clones
|
||||||
|
load and save the same UID-owned player locker state. They do not create
|
||||||
|
separate persistent lockers or cause store grants to duplicate by themselves.
|
||||||
|
|
||||||
## Medical Spawn Setup
|
## Medical Spawn Setup
|
||||||
|
|
||||||
The medical economy store discovers up to eleven medical spawn objects by exact
|
The medical economy store discovers up to eleven medical spawn objects by exact
|
||||||
@ -213,6 +213,32 @@ physical vehicle into the player's 5-slot garage. Use the virtual garage to
|
|||||||
spawn an unlocked vehicle, and use the garage to store or retrieve live world
|
spawn an unlocked vehicle, and use the garage to store or retrieve live world
|
||||||
vehicles.
|
vehicles.
|
||||||
|
|
||||||
|
## Transport
|
||||||
|
|
||||||
|
Transport points let players pay to travel between configured mission locations.
|
||||||
|
They may represent ferries, terminals, air shuttles, or other mission-specific
|
||||||
|
travel points.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Player workflow:
|
||||||
|
|
||||||
|
1. Stand near a transport point.
|
||||||
|
2. Open the actor interaction menu.
|
||||||
|
3. Select Transport.
|
||||||
|
4. Select a destination from the transport submenu, or select Close to return
|
||||||
|
to the default interaction menu.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The destination price is based on distance. The server charges player bank
|
||||||
|
first, player cash second, then organization credit line fallback when
|
||||||
|
available. If payment succeeds, the player is moved to the selected arrival
|
||||||
|
point. Nearby eligible vehicles or passengers may be moved with the player when
|
||||||
|
the mission has configured the transport point for cargo movement.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Locker and Virtual Arsenal
|
## Locker and Virtual Arsenal
|
||||||
|
|
||||||
The locker is personal item storage.
|
The locker is personal item storage.
|
||||||
@ -224,6 +250,10 @@ Locker rules:
|
|||||||
- Up to 25 items can be stored.
|
- Up to 25 items can be stored.
|
||||||
- The locker saves when the locker container is closed.
|
- The locker saves when the locker container is closed.
|
||||||
- Over-capacity storage can warn or fail depending on server handling.
|
- Over-capacity storage can warn or fail depending on server handling.
|
||||||
|
- Multiple locker access points on the map open local copies of the locker
|
||||||
|
object, but all of them use the same personal locker inventory.
|
||||||
|
- Store purchases merge granted items into the same personal locker by
|
||||||
|
classname; extra locker objects on the map do not duplicate store grants.
|
||||||
|
|
||||||
The virtual arsenal is locked down. Players only see gear they have been
|
The virtual arsenal is locked down. Players only see gear they have been
|
||||||
granted or have unlocked through systems such as the store. The virtual arsenal
|
granted or have unlocked through systems such as the store. The virtual arsenal
|
||||||
@ -111,4 +111,14 @@ Most modules follow the same shape:
|
|||||||
---
|
---
|
||||||
Task catalog, ownership, status transitions, defuse counters, and rewards.
|
Task catalog, ownership, status transitions, defuse counters, and rewards.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::u-page-card
|
||||||
|
---
|
||||||
|
icon: i-lucide-route
|
||||||
|
title: Transport Service
|
||||||
|
to: /server-modules/transport-service
|
||||||
|
---
|
||||||
|
Paid point-to-point player and cargo transport configured through Eden
|
||||||
|
objects and arrival markers.
|
||||||
|
:::
|
||||||
::
|
::
|
||||||
|
|||||||
134
docus/content/3.server-modules/12.transport-service.md
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
---
|
||||||
|
title: "Transport Service Guide"
|
||||||
|
description: "The transport service provides paid point-to-point travel for players and nearby vehicles or passengers. It is framework-owned: missions only need placed transport objects and optional arrival markers with the expected variable names."
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mission Contract
|
||||||
|
|
||||||
|
By default the framework discovers transport nodes by exact mission namespace
|
||||||
|
variable name:
|
||||||
|
|
||||||
|
```text
|
||||||
|
transport
|
||||||
|
transport_1
|
||||||
|
transport_2
|
||||||
|
...
|
||||||
|
transport_10
|
||||||
|
```
|
||||||
|
|
||||||
|
Each node is an Eden-placed object players can stand near. When a player opens
|
||||||
|
the actor interaction menu within 5 meters of a node, the menu shows a
|
||||||
|
Transport action. Selecting Transport opens destination choices for the other
|
||||||
|
configured nodes.
|
||||||
|
|
||||||
|
Arrival markers use the same suffix:
|
||||||
|
|
||||||
|
```text
|
||||||
|
transport_arrival
|
||||||
|
transport_arrival_1
|
||||||
|
transport_arrival_2
|
||||||
|
...
|
||||||
|
transport_arrival_10
|
||||||
|
```
|
||||||
|
|
||||||
|
Object names used only to exclude parked ferry/transport vehicles from cargo
|
||||||
|
pickup scans use this convention:
|
||||||
|
|
||||||
|
```text
|
||||||
|
transport_vehicle
|
||||||
|
transport_vehicle_1
|
||||||
|
transport_vehicle_2
|
||||||
|
...
|
||||||
|
transport_vehicle_10
|
||||||
|
```
|
||||||
|
|
||||||
|
The suffix mapping is direct:
|
||||||
|
|
||||||
|
- `transport` arrives at `transport_arrival`
|
||||||
|
- `transport_1` arrives at `transport_arrival_1`
|
||||||
|
- `transport_10` arrives at `transport_arrival_10`
|
||||||
|
|
||||||
|
If an arrival marker is missing, the framework falls back to a position behind
|
||||||
|
the destination node object.
|
||||||
|
|
||||||
|
## Pricing and Payment
|
||||||
|
|
||||||
|
The default fare is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
base fare + distance in kilometers * price per kilometer
|
||||||
|
```
|
||||||
|
|
||||||
|
Current defaults:
|
||||||
|
|
||||||
|
- base fare: `$100`
|
||||||
|
- price per kilometer: `$50`
|
||||||
|
- cargo scan radius: `25` meters
|
||||||
|
- max indexed nodes: `10`
|
||||||
|
|
||||||
|
Payment is server-authoritative. The transport service attempts payment in this
|
||||||
|
order:
|
||||||
|
|
||||||
|
1. Player bank balance.
|
||||||
|
2. Player cash.
|
||||||
|
3. Organization credit line fallback.
|
||||||
|
|
||||||
|
The player and cargo are moved only after payment succeeds.
|
||||||
|
|
||||||
|
## Cargo and Vehicle Transfer
|
||||||
|
|
||||||
|
When a player requests transport, the server scans near the origin node for
|
||||||
|
nearby vehicles, ships, aircraft, and player units. The scan ignores:
|
||||||
|
|
||||||
|
- the origin and destination transport nodes
|
||||||
|
- objects named with the `transport_vehicle` prefix
|
||||||
|
- the requesting player
|
||||||
|
- dead entities
|
||||||
|
|
||||||
|
Use `transport_vehicle*` names for the actual boat, ferry, aircraft, or set
|
||||||
|
dressing object that should not be moved as cargo.
|
||||||
|
|
||||||
|
## Optional Per-Node Overrides
|
||||||
|
|
||||||
|
The default naming convention should cover normal missions. If a specific
|
||||||
|
mission needs another prefix or different pricing, set variables on the
|
||||||
|
transport node object:
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
this setVariable ["isTransport", true, true];
|
||||||
|
this setVariable ["transportLabel", "North Dock", true];
|
||||||
|
this setVariable ["transportNodePrefix", "dock", true];
|
||||||
|
this setVariable ["transportVehiclePrefix", "dock_vehicle", true];
|
||||||
|
this setVariable ["transportArrivalPrefix", "dock_arrival", true];
|
||||||
|
this setVariable ["transportMaxIndexedNodes", 4, true];
|
||||||
|
this setVariable ["transportBaseFare", 150, true];
|
||||||
|
this setVariable ["transportPricePerKm", 75, true];
|
||||||
|
this setVariable ["transportCargoRadius", 25, true];
|
||||||
|
this setVariable ["transportIncludeCargo", true, true];
|
||||||
|
```
|
||||||
|
|
||||||
|
Only use overrides when the default `transport*` convention is not appropriate.
|
||||||
|
|
||||||
|
## Image Checklist
|
||||||
|
|
||||||
|
Replace these placeholder image references after screenshots are captured:
|
||||||
|
|
||||||
|

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

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

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

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

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

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

|
||||||
|
|
||||||
|
## Mission-Side Code Requirement
|
||||||
|
|
||||||
|
No mission-side transport service, addAction script, or server event bridge is
|
||||||
|
required. The framework handles menu discovery, destination selection, pricing,
|
||||||
|
billing, cargo movement, and EventBus notifications.
|
||||||
@ -26,6 +26,42 @@ Rules validated by the Rust service:
|
|||||||
- `locker:get`, `locker:patch`, and `locker:remove` require an existing locker.
|
- `locker:get`, `locker:patch`, and `locker:remove` require an existing locker.
|
||||||
- `locker:remove` takes the classname directly, not a JSON object.
|
- `locker:remove` takes the classname directly, not a JSON object.
|
||||||
|
|
||||||
|
## Multiple Locker Objects
|
||||||
|
|
||||||
|
Editor-placed locker objects are templates and access points, not separate
|
||||||
|
durable inventories. During server post-init, any mission namespace object
|
||||||
|
whose variable name contains `locker` is hidden globally. During client setup,
|
||||||
|
each client reads the hidden object's classname, ASL position, vector direction,
|
||||||
|
and vector up, then creates a matching local locker object with
|
||||||
|
`createVehicleLocal`.
|
||||||
|
|
||||||
|
The local clone is the object the player actually opens. On `ContainerOpened`,
|
||||||
|
the client clears the clone and fills it from the player's UID-owned locker
|
||||||
|
state. On `ContainerClosed`, the client reads the clone's cargo and sends a
|
||||||
|
full locker override back to the server.
|
||||||
|
|
||||||
|
There is no explicit maximum number of editor-placed locker access points. The
|
||||||
|
practical limit is mission performance and how many local container objects are
|
||||||
|
reasonable for the scenario.
|
||||||
|
|
||||||
|
All locker access points load and save the same player locker, keyed by player
|
||||||
|
UID. Opening `locker`, `locker_hq`, or `locker_outpost_1` does not create
|
||||||
|
separate persistent inventories; those objects are separate local access clones
|
||||||
|
for the same underlying player locker.
|
||||||
|
|
||||||
|
## Store Grants and Duplicate Inventory
|
||||||
|
|
||||||
|
The store checkout path grants items to the UID-owned locker hot state, not to a
|
||||||
|
specific placed locker object. Item grants are merged by classname:
|
||||||
|
|
||||||
|
- buying a new classname adds one new locker entry
|
||||||
|
- buying an existing classname increases that entry's amount
|
||||||
|
- checkout fails if the result would exceed 25 unique classnames
|
||||||
|
|
||||||
|
Having more than one locker object on the map does not duplicate store grants.
|
||||||
|
Duplicate quantities can only come from repeated checkout requests or repeated
|
||||||
|
manual locker writes, not from the number of placed locker access points.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
All commands are called on the `locker` group.
|
All commands are called on the `locker` group.
|
||||||
|
|||||||
9
docus/public/images/eden/transport_arrival_marker.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Placeholder: Eden transport arrival marker placement</title>
|
||||||
|
<desc id="desc">Capture an Eden Editor screenshot showing the arrival marker location.</desc>
|
||||||
|
<rect width="1280" height="720" fill="#202733"/>
|
||||||
|
<rect x="80" y="70" width="1120" height="580" rx="8" fill="#2f3948" stroke="#7d8aa0" stroke-width="4" stroke-dasharray="18 14"/>
|
||||||
|
<text x="640" y="280" fill="#f3f5f7" font-family="Arial, sans-serif" font-size="48" text-anchor="middle">PLACEHOLDER IMAGE</text>
|
||||||
|
<text x="640" y="352" fill="#d7dde7" font-family="Arial, sans-serif" font-size="30" text-anchor="middle">Eden: transport arrival marker</text>
|
||||||
|
<text x="640" y="410" fill="#aeb9c9" font-family="Arial, sans-serif" font-size="24" text-anchor="middle">Show where players and cargo should spawn.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 930 B |
9
docus/public/images/eden/transport_node_obj.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Placeholder: Eden transport node object placement</title>
|
||||||
|
<desc id="desc">Capture an Eden Editor screenshot showing the placed transport access object.</desc>
|
||||||
|
<rect width="1280" height="720" fill="#202733"/>
|
||||||
|
<rect x="80" y="70" width="1120" height="580" rx="8" fill="#2f3948" stroke="#7d8aa0" stroke-width="4" stroke-dasharray="18 14"/>
|
||||||
|
<text x="640" y="280" fill="#f3f5f7" font-family="Arial, sans-serif" font-size="48" text-anchor="middle">PLACEHOLDER IMAGE</text>
|
||||||
|
<text x="640" y="352" fill="#d7dde7" font-family="Arial, sans-serif" font-size="30" text-anchor="middle">Eden: transport node object placement</text>
|
||||||
|
<text x="640" y="410" fill="#aeb9c9" font-family="Arial, sans-serif" font-size="24" text-anchor="middle">Show the object players interact with for transport.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 951 B |
9
docus/public/images/eden/transport_node_var.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Placeholder: Eden transport node variable name</title>
|
||||||
|
<desc id="desc">Capture the Eden object attributes panel with the transport variable name.</desc>
|
||||||
|
<rect width="1280" height="720" fill="#202733"/>
|
||||||
|
<rect x="80" y="70" width="1120" height="580" rx="8" fill="#2f3948" stroke="#7d8aa0" stroke-width="4" stroke-dasharray="18 14"/>
|
||||||
|
<text x="640" y="280" fill="#f3f5f7" font-family="Arial, sans-serif" font-size="48" text-anchor="middle">PLACEHOLDER IMAGE</text>
|
||||||
|
<text x="640" y="352" fill="#d7dde7" font-family="Arial, sans-serif" font-size="30" text-anchor="middle">Eden: transport variable name</text>
|
||||||
|
<text x="640" y="410" fill="#aeb9c9" font-family="Arial, sans-serif" font-size="24" text-anchor="middle">Show transport, transport_1, through transport_10.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 935 B |
9
docus/public/images/eden/transport_vehicle_var.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Placeholder: Eden transport vehicle variable name</title>
|
||||||
|
<desc id="desc">Capture the variable name for the transport vehicle that should be excluded from cargo scans.</desc>
|
||||||
|
<rect width="1280" height="720" fill="#202733"/>
|
||||||
|
<rect x="80" y="70" width="1120" height="580" rx="8" fill="#2f3948" stroke="#7d8aa0" stroke-width="4" stroke-dasharray="18 14"/>
|
||||||
|
<text x="640" y="280" fill="#f3f5f7" font-family="Arial, sans-serif" font-size="48" text-anchor="middle">PLACEHOLDER IMAGE</text>
|
||||||
|
<text x="640" y="352" fill="#d7dde7" font-family="Arial, sans-serif" font-size="30" text-anchor="middle">Eden: transport_vehicle variable</text>
|
||||||
|
<text x="640" y="410" fill="#aeb9c9" font-family="Arial, sans-serif" font-size="24" text-anchor="middle">Show transport_vehicle, transport_vehicle_1, etc.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 959 B |
9
docus/public/images/player/transport_complete.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Placeholder: Player transport completion</title>
|
||||||
|
<desc id="desc">Capture the player and cargo after a successful transport request.</desc>
|
||||||
|
<rect width="1280" height="720" fill="#202733"/>
|
||||||
|
<rect x="80" y="70" width="1120" height="580" rx="8" fill="#2f3948" stroke="#7d8aa0" stroke-width="4" stroke-dasharray="18 14"/>
|
||||||
|
<text x="640" y="280" fill="#f3f5f7" font-family="Arial, sans-serif" font-size="48" text-anchor="middle">PLACEHOLDER IMAGE</text>
|
||||||
|
<text x="640" y="352" fill="#d7dde7" font-family="Arial, sans-serif" font-size="30" text-anchor="middle">Player: transport complete</text>
|
||||||
|
<text x="640" y="410" fill="#aeb9c9" font-family="Arial, sans-serif" font-size="24" text-anchor="middle">Show arrival at the destination with moved vehicles nearby.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 927 B |
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Placeholder: Player transport destination menu</title>
|
||||||
|
<desc id="desc">Capture the destination submenu after selecting Transport.</desc>
|
||||||
|
<rect width="1280" height="720" fill="#202733"/>
|
||||||
|
<rect x="80" y="70" width="1120" height="580" rx="8" fill="#2f3948" stroke="#7d8aa0" stroke-width="4" stroke-dasharray="18 14"/>
|
||||||
|
<text x="640" y="280" fill="#f3f5f7" font-family="Arial, sans-serif" font-size="48" text-anchor="middle">PLACEHOLDER IMAGE</text>
|
||||||
|
<text x="640" y="352" fill="#d7dde7" font-family="Arial, sans-serif" font-size="30" text-anchor="middle">Player: destination submenu</text>
|
||||||
|
<text x="640" y="410" fill="#aeb9c9" font-family="Arial, sans-serif" font-size="24" text-anchor="middle">Show destination choices and the Back action.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 912 B |
9
docus/public/images/player/transport_menu_action.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Placeholder: Player transport menu action</title>
|
||||||
|
<desc id="desc">Capture the actor interaction menu showing the Transport action.</desc>
|
||||||
|
<rect width="1280" height="720" fill="#202733"/>
|
||||||
|
<rect x="80" y="70" width="1120" height="580" rx="8" fill="#2f3948" stroke="#7d8aa0" stroke-width="4" stroke-dasharray="18 14"/>
|
||||||
|
<text x="640" y="280" fill="#f3f5f7" font-family="Arial, sans-serif" font-size="48" text-anchor="middle">PLACEHOLDER IMAGE</text>
|
||||||
|
<text x="640" y="352" fill="#d7dde7" font-family="Arial, sans-serif" font-size="30" text-anchor="middle">Player: Transport action</text>
|
||||||
|
<text x="640" y="410" fill="#aeb9c9" font-family="Arial, sans-serif" font-size="24" text-anchor="middle">Show the first interaction menu near a transport node.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 919 B |
@ -19,17 +19,21 @@ const generatedPages = [
|
|||||||
source: 'docs/DEVELOPMENT_GUIDE.md',
|
source: 'docs/DEVELOPMENT_GUIDE.md',
|
||||||
target: '1.getting-started/3.development.md'
|
target: '1.getting-started/3.development.md'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: 'docs/GIT_WORKFLOW.md',
|
||||||
|
target: '1.getting-started/4.git-workflow.md'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
source: 'docs/MISSION_DESIGNER_GUIDE.md',
|
source: 'docs/MISSION_DESIGNER_GUIDE.md',
|
||||||
target: '1.getting-started/4.mission-designer.md'
|
target: '1.getting-started/5.mission-designer.md'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: 'docs/PLAYER_GUIDE.md',
|
source: 'docs/PLAYER_GUIDE.md',
|
||||||
target: '1.getting-started/5.player-guide.md'
|
target: '1.getting-started/6.player-guide.md'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: 'docs/surrealdb-setup.md',
|
source: 'docs/surrealdb-setup.md',
|
||||||
target: '1.getting-started/6.surrealdb-setup.md'
|
target: '1.getting-started/7.surrealdb-setup.md'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
source: 'arma/server/docs/README.md',
|
source: 'arma/server/docs/README.md',
|
||||||
@ -95,6 +99,10 @@ const generatedPages = [
|
|||||||
source: 'docs/TASK_USAGE_GUIDE.md',
|
source: 'docs/TASK_USAGE_GUIDE.md',
|
||||||
target: '3.server-modules/11.task.md'
|
target: '3.server-modules/11.task.md'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: 'docs/TRANSPORT_SERVICE_GUIDE.md',
|
||||||
|
target: '3.server-modules/12.transport-service.md'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
source: 'docs/CLIENT_USAGE_GUIDE.md',
|
source: 'docs/CLIENT_USAGE_GUIDE.md',
|
||||||
target: '4.client-addons/0.index.md'
|
target: '4.client-addons/0.index.md'
|
||||||
@ -616,6 +624,16 @@ Most modules follow the same shape:
|
|||||||
---
|
---
|
||||||
Task catalog, ownership, status transitions, defuse counters, and rewards.
|
Task catalog, ownership, status transitions, defuse counters, and rewards.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::u-page-card
|
||||||
|
---
|
||||||
|
icon: i-lucide-route
|
||||||
|
title: Transport Service
|
||||||
|
to: /server-modules/transport-service
|
||||||
|
---
|
||||||
|
Paid point-to-point player and cargo transport configured through Eden
|
||||||
|
objects and arrival markers.
|
||||||
|
:::
|
||||||
::
|
::
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
|
|||||||