Compare commits
3 Commits
f2ac9fcbe7
...
582dd39640
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
582dd39640 | ||
|
|
89e3f794dc | ||
|
|
c0dd782103 |
@ -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,408 @@
|
||||
#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
|
||||
};
|
||||
|
||||
private _personalSourceAttempted = false;
|
||||
private _allowOrgFallback = false;
|
||||
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 _bankBalance = _account getOrDefault ["bank", 0];
|
||||
private _cashBalance = _account getOrDefault ["cash", 0];
|
||||
private _source = ["", "bank"] select (_bankBalance >= _amount);
|
||||
if (_source isEqualTo "" && { _cashBalance >= _amount }) then {
|
||||
_source = "cash";
|
||||
};
|
||||
|
||||
if (_source isNotEqualTo "") then {
|
||||
_personalSourceAttempted = true;
|
||||
private _charge = EGVAR(bank,BankStore) call ["chargeCheckout", [_uid, _source, _amount, true]];
|
||||
if (_charge getOrDefault ["success", false]) then {
|
||||
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]];
|
||||
} else {
|
||||
_result set ["message", _charge getOrDefault ["message", format ["Unable to charge %1 from your %2.", _label, _source]]];
|
||||
};
|
||||
} else {
|
||||
_allowOrgFallback = true;
|
||||
};
|
||||
} else {
|
||||
_result set ["message", "Bank account could not be loaded for transport billing."];
|
||||
};
|
||||
} else {
|
||||
_result set ["message", "Bank service is unavailable for transport billing."];
|
||||
};
|
||||
|
||||
if (!(_result getOrDefault ["success", false]) && { !_personalSourceAttempted } && { _allowOrgFallback } && { !(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"
|
||||
@ -6,7 +6,7 @@ handlers, and optional browser UI.
|
||||
|
||||
## Runtime Flow
|
||||
|
||||

|
||||

|
||||
|
||||
```text
|
||||
Arma client UI or SQF action
|
||||
|
||||
@ -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,75 @@ 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 +252,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 +265,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
|
||||
|
||||
143
docs/TRANSPORT_SERVICE_GUIDE.md
Normal file
@ -0,0 +1,143 @@
|
||||
# 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.
|
||||
|
||||
## Reference Images
|
||||
|
||||
These screenshots show the default transport setup and player workflow:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
1
docs/images/architecture-flow.svg
Normal file
|
After Width: | Height: | Size: 21 KiB |
1
docs/images/eden/.gitkeep
Normal file
@ -0,0 +1 @@
|
||||
|
||||
BIN
docs/images/eden/atm_obj.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
docs/images/eden/atm_obj_var.jpg
Normal file
|
After Width: | Height: | Size: 618 KiB |
BIN
docs/images/eden/attack_task_mod.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
docs/images/eden/attack_task_mod_params.jpg
Normal file
|
After Width: | Height: | Size: 565 KiB |
BIN
docs/images/eden/attack_task_tgts.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
docs/images/eden/bank_obj.jpg
Normal file
|
After Width: | Height: | Size: 443 KiB |
BIN
docs/images/eden/bank_obj_var.jpg
Normal file
|
After Width: | Height: | Size: 307 KiB |
BIN
docs/images/eden/blacklist_mrkr.jpg
Normal file
|
After Width: | Height: | Size: 680 KiB |
BIN
docs/images/eden/blacklist_mrkr_var.jpg
Normal file
|
After Width: | Height: | Size: 477 KiB |
BIN
docs/images/eden/cad-visible-task.jpg
Normal file
|
After Width: | Height: | Size: 827 KiB |
BIN
docs/images/eden/ceo_unit.jpg
Normal file
|
After Width: | Height: | Size: 560 KiB |
BIN
docs/images/eden/ceo_unit_var.jpg
Normal file
|
After Width: | Height: | Size: 352 KiB |
BIN
docs/images/eden/create_task_mod.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
docs/images/eden/create_task_mod_params.jpg
Normal file
|
After Width: | Height: | Size: 564 KiB |
BIN
docs/images/eden/defend_task_mod.jpg
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
docs/images/eden/defend_task_mod_params.jpg
Normal file
|
After Width: | Height: | Size: 695 KiB |
BIN
docs/images/eden/defend_zone_mrkr.jpg
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
docs/images/eden/defend_zone_mrkr_var.jpg
Normal file
|
After Width: | Height: | Size: 662 KiB |
BIN
docs/images/eden/defuse_explosives_mod.jpg
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
docs/images/eden/defuse_protected_mod.jpg
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
docs/images/eden/defuse_task_mod.jpg
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
docs/images/eden/defuse_task_mod_params.jpg
Normal file
|
After Width: | Height: | Size: 714 KiB |
BIN
docs/images/eden/delivery_cargo_mod.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
docs/images/eden/delivery_task_mod.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
docs/images/eden/delivery_task_mod_params.jpg
Normal file
|
After Width: | Height: | Size: 644 KiB |
BIN
docs/images/eden/delivery_zone_mrkr.jpg
Normal file
|
After Width: | Height: | Size: 956 KiB |
BIN
docs/images/eden/delivery_zone_mrkr_var.jpg
Normal file
|
After Width: | Height: | Size: 603 KiB |
BIN
docs/images/eden/destroy_task_mod.jpg
Normal file
|
After Width: | Height: | Size: 1008 KiB |
BIN
docs/images/eden/destroy_task_mod_params.jpg
Normal file
|
After Width: | Height: | Size: 646 KiB |
BIN
docs/images/eden/destroy_task_tgts.jpg
Normal file
|
After Width: | Height: | Size: 1018 KiB |
BIN
docs/images/eden/dispatch_unit.jpg
Normal file
|
After Width: | Height: | Size: 453 KiB |
BIN
docs/images/eden/dispatch_unit_var.jpg
Normal file
|
After Width: | Height: | Size: 294 KiB |
BIN
docs/images/eden/garage_obj.jpg
Normal file
|
After Width: | Height: | Size: 824 KiB |
BIN
docs/images/eden/garage_obj_1.jpg
Normal file
|
After Width: | Height: | Size: 840 KiB |
BIN
docs/images/eden/garage_obj_1_var.jpg
Normal file
|
After Width: | Height: | Size: 461 KiB |
BIN
docs/images/eden/garage_obj_2.jpg
Normal file
|
After Width: | Height: | Size: 738 KiB |
BIN
docs/images/eden/garage_obj_2_var.jpg
Normal file
|
After Width: | Height: | Size: 430 KiB |
BIN
docs/images/eden/garage_obj_var.jpg
Normal file
|
After Width: | Height: | Size: 443 KiB |
BIN
docs/images/eden/garage_spawn_1_mrkr.jpg
Normal file
|
After Width: | Height: | Size: 853 KiB |
BIN
docs/images/eden/garage_spawn_1_mrkr_var.jpg
Normal file
|
After Width: | Height: | Size: 463 KiB |
BIN
docs/images/eden/garage_spawn_2_mrkrs.jpg
Normal file
|
After Width: | Height: | Size: 738 KiB |
BIN
docs/images/eden/garage_spawn_mrkrs.jpg
Normal file
|
After Width: | Height: | Size: 827 KiB |
BIN
docs/images/eden/hostage_entities_mod.jpg
Normal file
|
After Width: | Height: | Size: 1013 KiB |
BIN
docs/images/eden/hostage_ext_zone_mrkr.jpg
Normal file
|
After Width: | Height: | Size: 851 KiB |
BIN
docs/images/eden/hostage_ext_zone_mrkr_var.jpg
Normal file
|
After Width: | Height: | Size: 558 KiB |
BIN
docs/images/eden/hostage_shooters_mod.jpg
Normal file
|
After Width: | Height: | Size: 1012 KiB |
BIN
docs/images/eden/hostage_task_mod.jpg
Normal file
|
After Width: | Height: | Size: 1012 KiB |
BIN
docs/images/eden/hostage_task_mod_params.jpg
Normal file
|
After Width: | Height: | Size: 640 KiB |
BIN
docs/images/eden/hvt_capture_task_mod.jpg
Normal file
|
After Width: | Height: | Size: 691 KiB |
BIN
docs/images/eden/hvt_capture_task_mod_params.jpg
Normal file
|
After Width: | Height: | Size: 517 KiB |
BIN
docs/images/eden/hvt_ext_zone_mrkr.jpg
Normal file
|
After Width: | Height: | Size: 851 KiB |
BIN
docs/images/eden/hvt_ext_zone_mrkr_var.jpg
Normal file
|
After Width: | Height: | Size: 558 KiB |
BIN
docs/images/eden/hvt_task_mod.jpg
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
docs/images/eden/hvt_task_mod_params.jpg
Normal file
|
After Width: | Height: | Size: 688 KiB |
BIN
docs/images/eden/locker_obj.jpg
Normal file
|
After Width: | Height: | Size: 563 KiB |
BIN
docs/images/eden/locker_obj_var.jpg
Normal file
|
After Width: | Height: | Size: 366 KiB |
BIN
docs/images/eden/med_spawn_obj.jpg
Normal file
|
After Width: | Height: | Size: 695 KiB |
BIN
docs/images/eden/med_spawn_obj_var.jpg
Normal file
|
After Width: | Height: | Size: 412 KiB |
BIN
docs/images/eden/store_obj.jpg
Normal file
|
After Width: | Height: | Size: 588 KiB |
BIN
docs/images/eden/store_obj_var.jpg
Normal file
|
After Width: | Height: | Size: 393 KiB |
BIN
docs/images/eden/transport_arrival_mrkr.jpg
Normal file
|
After Width: | Height: | Size: 797 KiB |
BIN
docs/images/eden/transport_arrival_mrkr_var.jpg
Normal file
|
After Width: | Height: | Size: 498 KiB |
BIN
docs/images/eden/transport_loc_1.jpg
Normal file
|
After Width: | Height: | Size: 791 KiB |
BIN
docs/images/eden/transport_loc_2.jpg
Normal file
|
After Width: | Height: | Size: 762 KiB |
BIN
docs/images/eden/transport_obj_1.jpg
Normal file
|
After Width: | Height: | Size: 791 KiB |
BIN
docs/images/eden/transport_obj_1_var.jpg
Normal file
|
After Width: | Height: | Size: 495 KiB |
BIN
docs/images/eden/transport_veh_obj.jpg
Normal file
|
After Width: | Height: | Size: 793 KiB |
BIN
docs/images/eden/transport_veh_obj_var.jpg
Normal file
|
After Width: | Height: | Size: 512 KiB |
BIN
docs/images/player/atm_app_home.jpg
Normal file
|
After Width: | Height: | Size: 974 KiB |
BIN
docs/images/player/atm_app_pin.jpg
Normal file
|
After Width: | Height: | Size: 964 KiB |
BIN
docs/images/player/bank_app.jpg
Normal file
|
After Width: | Height: | Size: 183 KiB |
BIN
docs/images/player/cad_dispatch_board.jpg
Normal file
|
After Width: | Height: | Size: 450 KiB |
BIN
docs/images/player/cad_ops_board.jpg
Normal file
|
After Width: | Height: | Size: 569 KiB |
BIN
docs/images/player/garage.jpg
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
docs/images/player/interaction_menu.jpg
Normal file
|
After Width: | Height: | Size: 417 KiB |
BIN
docs/images/player/locker.jpg
Normal file
|
After Width: | Height: | Size: 383 KiB |
BIN
docs/images/player/medical_respawn.jpg
Normal file
|
After Width: | Height: | Size: 759 KiB |
BIN
docs/images/player/org_dashboard.jpg
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
docs/images/player/org_home.jpg
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
docs/images/player/org_registration.jpg
Normal file
|
After Width: | Height: | Size: 164 KiB |