Pass garage context through actor menu and update phone docs

- Thread garage object context into garage and vehicle garage launch paths
- Preserve nearby garage metadata in the UI and improve garage context resolution
- Add wallet and message/email examples to the player guide
This commit is contained in:
Jacob Schmidt 2026-05-21 18:07:07 -05:00
parent da5dccd2c0
commit 72a6dbb90a
15 changed files with 134 additions and 39 deletions

View File

@ -37,8 +37,22 @@ switch (_event) do {
case "actor::open::bank": { [] spawn EFUNC(bank,openUI); }; case "actor::open::bank": { [] spawn EFUNC(bank,openUI); };
case "actor::open::cad": { [] spawn EFUNC(cad,openUI); }; case "actor::open::cad": { [] spawn EFUNC(cad,openUI); };
case "actor::open::device": { hint "Device interaction is not yet implemented."; }; case "actor::open::device": { hint "Device interaction is not yet implemented."; };
case "actor::open::garage": { [] spawn EFUNC(garage,openUI); }; case "actor::open::garage": {
case "actor::open::vgarage": { [] spawn EFUNC(garage,openVG); }; private _garageObject = objNull;
if (_data isEqualType createHashMap) then {
private _netId = _data getOrDefault ["netId", ""];
if (_netId isNotEqualTo "") then { _garageObject = objectFromNetId _netId; };
};
[_garageObject] spawn EFUNC(garage,openUI);
};
case "actor::open::vgarage": {
private _garageObject = objNull;
if (_data isEqualType createHashMap) then {
private _netId = _data getOrDefault ["netId", ""];
if (_netId isNotEqualTo "") then { _garageObject = objectFromNetId _netId; };
};
[_garageObject] spawn EFUNC(garage,openVG);
};
case "actor::open::org": { [] spawn EFUNC(org,openUI); }; case "actor::open::org": { [] spawn EFUNC(org,openUI); };
case "actor::open::vlocker": { [FORGE_Locker_Box, player, false] spawn AFUNC(arsenal,openBox) }; case "actor::open::vlocker": { [FORGE_Locker_Box, player, false] spawn AFUNC(arsenal,openBox) };
case "actor::open::phone": { [] spawn EFUNC(phone,openUI); }; case "actor::open::phone": { [] spawn EFUNC(phone,openUI); };

View File

@ -114,6 +114,11 @@ GVAR(ActorRepositoryBaseClass) = compileFinal createHashMapFromArray [
private _isLocker = _x getVariable ["isLocker", false]; private _isLocker = _x getVariable ["isLocker", false];
private _isStore = _x getVariable ["isStore", false]; private _isStore = _x getVariable ["isStore", false];
private _garageType = _x getVariable ["garageType", ""]; private _garageType = _x getVariable ["garageType", ""];
private _garageContext = createHashMapFromArray [
["netId", netId _x],
["name", vehicleVarName _x],
["garageType", _garageType]
];
private _deviceType = _x getVariable ["deviceType", ""]; private _deviceType = _x getVariable ["deviceType", ""];
private _isPlayer = _x isKindOf "Man" && isPlayer _x; private _isPlayer = _x isKindOf "Man" && isPlayer _x;
@ -121,8 +126,8 @@ GVAR(ActorRepositoryBaseClass) = compileFinal createHashMapFromArray [
if (_isAtm) then { _nearbyActions pushBack ["atm", true]; }; if (_isAtm) then { _nearbyActions pushBack ["atm", true]; };
if (_isBank) then { _nearbyActions pushBack ["bank", true]; }; if (_isBank) then { _nearbyActions pushBack ["bank", true]; };
if (_isLocker && GVAR(enableVA)) then { _nearbyActions pushBack ["va", true]; }; if (_isLocker && GVAR(enableVA)) then { _nearbyActions pushBack ["va", true]; };
if (_isGarage) then { _nearbyActions pushBack ["garage", _garageType]; }; if (_isGarage) then { _nearbyActions pushBack ["garage", _garageContext]; };
if (_isGarage && GVAR(enableVG)) then { _nearbyActions pushBack ["vg", true]; }; if (_isGarage && GVAR(enableVG)) then { _nearbyActions pushBack ["vg", _garageContext]; };
if (_deviceType isNotEqualTo "") then { _nearbyActions pushBack ["device", _deviceType]; }; if (_deviceType isNotEqualTo "") then { _nearbyActions pushBack ["device", _deviceType]; };
if (_isPlayer && { _x isNotEqualTo player }) then { _nearbyActions pushBack ["player", name _x]; }; if (_isPlayer && { _x isNotEqualTo player }) then { _nearbyActions pushBack ["player", name _x]; };
} forEach (player nearObjects 5); } forEach (player nearObjects 5);

View File

@ -215,7 +215,21 @@ function actorReducer(state = initialState, action) {
const [type, value] = actionItem; const [type, value] = actionItem;
const definition = state.actionDefinitions[type]; const definition = state.actionDefinitions[type];
if (definition) { if (definition) {
newMenuItems.push(definition); const context =
value && typeof value === "object"
? value
: { value };
const garageLabel =
context.name || context.garageType || "";
const title =
["garage", "vg"].includes(type) && garageLabel
? `${definition.title}: ${garageLabel}`
: definition.title;
newMenuItems.push({
...definition,
title,
context,
});
} else { } else {
console.warn( console.warn(
`No definition found for: ${type} - ${value}`, `No definition found for: ${type} - ${value}`,
@ -414,7 +428,7 @@ function RadialMenu() {
console.log("Menu item clicked:", item); console.log("Menu item clicked:", item);
const alert = { const alert = {
event: item.action, event: item.action,
data: {}, data: item.context || {},
}; };
if (typeof A3API !== "undefined") { if (typeof A3API !== "undefined") {
A3API.SendAlert(JSON.stringify(alert)); A3API.SendAlert(JSON.stringify(alert));

View File

@ -22,8 +22,35 @@
#pragma hemtt ignore_variables ["_self"] #pragma hemtt ignore_variables ["_self"]
GVAR(GarageContextServiceBaseClass) = compileFinal createHashMapFromArray [ GVAR(GarageContextServiceBaseClass) = compileFinal createHashMapFromArray [
["#type", "GarageContextServiceBaseClass"], ["#type", "GarageContextServiceBaseClass"],
["#create", compileFinal { _self set ["lastContext", createHashMap]; }], ["#create", compileFinal {
["#delete", compileFinal { _self set ["lastContext", createHashMap]; }], _self set ["lastContext", createHashMap];
_self set ["activeGarageObject", objNull];
}],
["#delete", compileFinal {
_self set ["lastContext", createHashMap];
_self set ["activeGarageObject", objNull];
}],
["setActiveGarageObject", compileFinal {
params [["_garageObject", objNull, [objNull]]];
if (isNull _garageObject || { !(_garageObject getVariable ["isGarage", false]) }) exitWith {
_self set ["activeGarageObject", objNull];
false
};
_self set ["activeGarageObject", _garageObject];
true
}],
["getActiveGarageObject", compileFinal {
private _garageObject = _self getOrDefault ["activeGarageObject", objNull];
if (isNull _garageObject || { !(_garageObject getVariable ["isGarage", false]) }) exitWith { objNull };
if ((player distance2D _garageObject) > 12) exitWith {
_self set ["activeGarageObject", objNull];
objNull
};
_garageObject
}],
["createDefaultContext", compileFinal { ["createDefaultContext", compileFinal {
createHashMapFromArray [ createHashMapFromArray [
["name", "Vehicle Garage"], ["name", "Vehicle Garage"],
@ -184,8 +211,16 @@ GVAR(GarageContextServiceBaseClass) = compileFinal createHashMapFromArray [
_spawnLanes getOrDefault [_normalizedCategory, createHashMap] _spawnLanes getOrDefault [_normalizedCategory, createHashMap]
}], }],
["resolveContext", compileFinal { ["resolveContext", compileFinal {
params [["_preferredGarageObject", objNull, [objNull]]];
private _context = _self call ["createDefaultContext", []]; private _context = _self call ["createDefaultContext", []];
private _garageObject = _self call ["findNearbyGarageObject", []]; private _garageObject = _preferredGarageObject;
if (isNull _garageObject || { !(_garageObject getVariable ["isGarage", false]) }) then {
_garageObject = _self call ["getActiveGarageObject", []];
};
if (isNull _garageObject) then {
_garageObject = _self call ["findNearbyGarageObject", []];
};
private _garageName = _self call ["resolveGarageName", [_garageObject]]; private _garageName = _self call ["resolveGarageName", [_garageObject]];
private _garageType = ""; private _garageType = "";
private _anchorPosition = getPosATL player; private _anchorPosition = getPosATL player;
@ -215,7 +250,10 @@ GVAR(GarageContextServiceBaseClass) = compileFinal createHashMapFromArray [
_self set ["lastContext", _context]; _self set ["lastContext", _context];
_context _context
}], }],
["getContext", compileFinal { _self call ["resolveContext", []] }], ["getContext", compileFinal {
params [["_preferredGarageObject", objNull, [objNull]]];
_self call ["resolveContext", [_preferredGarageObject]]
}],
["buildNearbyState", compileFinal { ["buildNearbyState", compileFinal {
private _context = _self call ["getContext", []]; private _context = _self call ["getContext", []];
private _anchorPosition = _context getOrDefault ["anchorPosition", []]; private _anchorPosition = _context getOrDefault ["anchorPosition", []];

View File

@ -20,6 +20,12 @@
* call forge_client_garage_fnc_openUI; * call forge_client_garage_fnc_openUI;
*/ */
params [["_garageObject", objNull, [objNull]]];
if (!isNull _garageObject) then {
GVAR(GarageContextService) call ["setActiveGarageObject", [_garageObject]];
};
private _display = createDialog ["RscGarage", true]; private _display = createDialog ["RscGarage", true];
private _ctrl = _display displayCtrl 1006; private _ctrl = _display displayCtrl 1006;

View File

@ -20,7 +20,13 @@
* call forge_client_garage_fnc_openVG * call forge_client_garage_fnc_openVG
*/ */
private _context = GVAR(GarageContextService) call ["getContext", []]; params [["_garageObject", objNull, [objNull]]];
if (!isNull _garageObject) then {
GVAR(GarageContextService) call ["setActiveGarageObject", [_garageObject]];
};
private _context = GVAR(GarageContextService) call ["getContext", [_garageObject]];
private _spawnLane = GVAR(GarageContextService) call ["getSpawnLane", [_context getOrDefault ["garageType", ""], _context]]; private _spawnLane = GVAR(GarageContextService) call ["getSpawnLane", [_context getOrDefault ["garageType", ""], _context]];
FORGE_VehSpawnPos = _spawnLane getOrDefault ["spawnPosition", player getPos [8, getDir player]]; FORGE_VehSpawnPos = _spawnLane getOrDefault ["spawnPosition", player getPos [8, getDir player]];

View File

@ -76,7 +76,8 @@ Important task behavior:
## Phone ## Phone
The phone provides contacts, messages, email, and local utility apps. The phone provides contacts, messages, email, mobile bank access, and local
utility apps.
![Phone home screen](images/player/phone_home.jpg) ![Phone home screen](images/player/phone_home.jpg)
@ -90,29 +91,34 @@ entering recipient details every time.
### Messages ### Messages
Messages are short player-to-player conversations. Messages are short player-to-player conversations. Use Messages to start or
continue a conversation with a contact, read incoming messages, mark messages as
read, or delete messages you no longer need.
![Phone messages screen](images/player/phone_messages.jpg) ![Phone messages screen](images/player/phone_messages.jpg)
Use Messages to: ![Example phone message conversation](images/player/phone_message_example.jpg)
- start or continue a conversation with a contact
- read incoming messages
- mark messages as read
- delete messages you no longer need
### Email ### Email
Email is used for longer player-to-player communication. Email is used for longer player-to-player communication. Use Email to send a
subject and body to another player, read incoming mail, mark email as read, or
delete old email.
![Phone email screen](images/player/phone_email.jpg) ![Phone email screen](images/player/phone_email.jpg)
Use Email to: ![Example phone email](images/player/phone_email_example.jpg)
- send a subject and body to another player ### Wallet
- read incoming mail
- mark email as read Wallet is the phone version of the bank app. Use it to refresh your account
- delete old email view, check your available balance, review cash and pending earnings, deposit all
pending earnings, and pay your organization credit line when payment is due.
![Phone wallet app](images/player/phone_wallet.jpg)
Deposit Earnings deposits the full pending earnings amount. Players do not enter
a custom amount for that action.
### Local Phone Apps ### Local Phone Apps

View File

@ -75,7 +75,8 @@ Important task behavior:
## Phone ## Phone
The phone provides contacts, messages, email, and local utility apps. The phone provides contacts, messages, email, mobile bank access, and local
utility apps.
![Phone home screen](images/player/phone_home.jpg) ![Phone home screen](images/player/phone_home.jpg)
@ -89,29 +90,34 @@ entering recipient details every time.
### Messages ### Messages
Messages are short player-to-player conversations. Messages are short player-to-player conversations. Use Messages to start or
continue a conversation with a contact, read incoming messages, mark messages as
read, or delete messages you no longer need.
![Phone messages screen](images/player/phone_messages.jpg) ![Phone messages screen](images/player/phone_messages.jpg)
Use Messages to: ![Example phone message conversation](images/player/phone_message_example.jpg)
- start or continue a conversation with a contact
- read incoming messages
- mark messages as read
- delete messages you no longer need
### Email ### Email
Email is used for longer player-to-player communication. Email is used for longer player-to-player communication. Use Email to send a
subject and body to another player, read incoming mail, mark email as read, or
delete old email.
![Phone email screen](images/player/phone_email.jpg) ![Phone email screen](images/player/phone_email.jpg)
Use Email to: ![Example phone email](images/player/phone_email_example.jpg)
- send a subject and body to another player ### Wallet
- read incoming mail
- mark email as read Wallet is the phone version of the bank app. Use it to refresh your account
- delete old email view, check your available balance, review cash and pending earnings, deposit all
pending earnings, and pay your organization credit line when payment is due.
![Phone wallet app](images/player/phone_wallet.jpg)
Deposit Earnings deposits the full pending earnings amount. Players do not enter
a custom amount for that action.
### Local Phone Apps ### Local Phone Apps

Binary file not shown.

Before

Width:  |  Height:  |  Size: 954 KiB

After

Width:  |  Height:  |  Size: 732 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 949 KiB

After

Width:  |  Height:  |  Size: 730 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 966 KiB

After

Width:  |  Height:  |  Size: 743 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 951 KiB

After

Width:  |  Height:  |  Size: 733 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 KiB