Add framework transport service
@ -58,6 +58,37 @@ switch (_event) do {
|
||||
case "actor::open::phone": { [] spawn EFUNC(phone,openUI); };
|
||||
case "actor::open::iplayer": { hint "Player interaction is not yet implemented." };
|
||||
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]; };
|
||||
};
|
||||
|
||||
|
||||
@ -121,6 +121,12 @@ GVAR(ActorRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||
];
|
||||
private _deviceType = _x getVariable ["deviceType", ""];
|
||||
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 (_isAtm) then { _nearbyActions pushBack ["atm", true]; };
|
||||
@ -129,6 +135,55 @@ GVAR(ActorRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||
if (_isGarage) then { _nearbyActions pushBack ["garage", _garageContext]; };
|
||||
if (_isGarage && GVAR(enableVG)) then { _nearbyActions pushBack ["vg", _garageContext]; };
|
||||
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]; };
|
||||
} forEach (player nearObjects 5);
|
||||
|
||||
|
||||
@ -193,12 +193,19 @@ const actionDefinitions = {
|
||||
description: "Access your virtual garage",
|
||||
action: "actor::open::vgarage",
|
||||
},
|
||||
transport: {
|
||||
id: "transport",
|
||||
title: "Transport",
|
||||
description: "Show available travel destinations",
|
||||
action: "actor::show::transport",
|
||||
},
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
availableActions: [],
|
||||
menuItems: [...baseMenuItems],
|
||||
baseMenuItems: [...baseMenuItems],
|
||||
defaultMenuItems: [...baseMenuItems],
|
||||
actionDefinitions: { ...actionDefinitions },
|
||||
};
|
||||
|
||||
@ -244,6 +251,7 @@ function actorReducer(state = initialState, action) {
|
||||
...state,
|
||||
availableActions: action.payload,
|
||||
menuItems: newMenuItems,
|
||||
defaultMenuItems: newMenuItems,
|
||||
};
|
||||
|
||||
case ActionTypes.SET_MENU_ITEMS:
|
||||
@ -426,6 +434,43 @@ function RadialMenu() {
|
||||
|
||||
const handleItemClick = (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 = {
|
||||
event: item.action,
|
||||
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: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
|
||||
|
||||
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. |
|
||||
| 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. |
|
||||
| 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:
|
||||
|
||||
@ -38,6 +39,8 @@ atm
|
||||
bank
|
||||
store
|
||||
locker
|
||||
transport
|
||||
transport_1
|
||||
garage_hq
|
||||
garage_hq_2
|
||||
```
|
||||
@ -60,6 +63,7 @@ _atmTerminal setVariable ["isAtm", true, true];
|
||||
_storeCounter setVariable ["isStore", true, true];
|
||||
_garageTerminal setVariable ["isGarage", true, true];
|
||||
_garageTerminal setVariable ["garageType", "cars", true];
|
||||
_transportNode setVariable ["isTransport", true, true];
|
||||
```
|
||||
|
||||
Supported garage types are:
|
||||
@ -142,6 +146,67 @@ Minimum Eden setup:
|
||||
2. Set its Eden variable name to something containing `store`.
|
||||
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 objects intentionally expose different workflows.
|
||||
@ -179,7 +244,7 @@ Minimum Eden setup:
|
||||
Locker objects are slightly different from other interaction objects. The
|
||||
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
|
||||
position.
|
||||
position using the placed object's classname and orientation.
|
||||
|
||||

|
||||
|
||||
@ -192,6 +257,11 @@ Minimum Eden setup:
|
||||
3. Do not use `forge_locker_box`.
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
The locker is personal item storage.
|
||||
@ -225,6 +251,10 @@ Locker rules:
|
||||
- Up to 25 items can be stored.
|
||||
- The locker saves when the locker container is closed.
|
||||
- Over-capacity storage can warn or fail depending on server handling.
|
||||
- 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
|
||||
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."
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
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. |
|
||||
| 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. |
|
||||
| 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:
|
||||
|
||||
@ -38,6 +39,8 @@ atm
|
||||
bank
|
||||
store
|
||||
locker
|
||||
transport
|
||||
transport_1
|
||||
garage_hq
|
||||
garage_hq_2
|
||||
```
|
||||
@ -60,6 +63,7 @@ _atmTerminal setVariable ["isAtm", true, true];
|
||||
_storeCounter setVariable ["isStore", true, true];
|
||||
_garageTerminal setVariable ["isGarage", true, true];
|
||||
_garageTerminal setVariable ["garageType", "cars", true];
|
||||
_transportNode setVariable ["isTransport", true, true];
|
||||
```
|
||||
|
||||
Supported garage types are:
|
||||
@ -142,6 +146,67 @@ Minimum Eden setup:
|
||||
2. Set its Eden variable name to something containing `store`.
|
||||
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 objects intentionally expose different workflows.
|
||||
@ -179,7 +244,7 @@ Minimum Eden setup:
|
||||
Locker objects are slightly different from other interaction objects. The
|
||||
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
|
||||
position.
|
||||
position using the placed object's classname and orientation.
|
||||
|
||||

|
||||
|
||||
@ -192,6 +257,11 @@ Minimum Eden setup:
|
||||
3. Do not use `forge_locker_box`.
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
The locker is personal item storage.
|
||||
@ -224,6 +250,10 @@ Locker rules:
|
||||
- Up to 25 items can be stored.
|
||||
- The locker saves when the locker container is closed.
|
||||
- Over-capacity storage can warn or fail depending on server handling.
|
||||
- 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
|
||||
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.
|
||||
:::
|
||||
|
||||
:::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: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
|
||||
|
||||
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',
|
||||
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',
|
||||
target: '1.getting-started/4.mission-designer.md'
|
||||
target: '1.getting-started/5.mission-designer.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',
|
||||
target: '1.getting-started/6.surrealdb-setup.md'
|
||||
target: '1.getting-started/7.surrealdb-setup.md'
|
||||
},
|
||||
{
|
||||
source: 'arma/server/docs/README.md',
|
||||
@ -95,6 +99,10 @@ const generatedPages = [
|
||||
source: 'docs/TASK_USAGE_GUIDE.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',
|
||||
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.
|
||||
:::
|
||||
|
||||
:::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.
|
||||
:::
|
||||
::
|
||||
`
|
||||
},
|
||||
|
||||