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::cad": { [] spawn EFUNC(cad,openUI); };
case "actor::open::device": { hint "Device interaction is not yet implemented."; };
case "actor::open::garage": { [] spawn EFUNC(garage,openUI); };
case "actor::open::vgarage": { [] spawn EFUNC(garage,openVG); };
case "actor::open::garage": {
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::vlocker": { [FORGE_Locker_Box, player, false] spawn AFUNC(arsenal,openBox) };
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 _isStore = _x getVariable ["isStore", false];
private _garageType = _x getVariable ["garageType", ""];
private _garageContext = createHashMapFromArray [
["netId", netId _x],
["name", vehicleVarName _x],
["garageType", _garageType]
];
private _deviceType = _x getVariable ["deviceType", ""];
private _isPlayer = _x isKindOf "Man" && isPlayer _x;
@ -121,8 +126,8 @@ GVAR(ActorRepositoryBaseClass) = compileFinal createHashMapFromArray [
if (_isAtm) then { _nearbyActions pushBack ["atm", true]; };
if (_isBank) then { _nearbyActions pushBack ["bank", true]; };
if (_isLocker && GVAR(enableVA)) then { _nearbyActions pushBack ["va", true]; };
if (_isGarage) then { _nearbyActions pushBack ["garage", _garageType]; };
if (_isGarage && GVAR(enableVG)) then { _nearbyActions pushBack ["vg", true]; };
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 (_isPlayer && { _x isNotEqualTo player }) then { _nearbyActions pushBack ["player", name _x]; };
} forEach (player nearObjects 5);

View File

@ -215,7 +215,21 @@ function actorReducer(state = initialState, action) {
const [type, value] = actionItem;
const definition = state.actionDefinitions[type];
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 {
console.warn(
`No definition found for: ${type} - ${value}`,
@ -414,7 +428,7 @@ function RadialMenu() {
console.log("Menu item clicked:", item);
const alert = {
event: item.action,
data: {},
data: item.context || {},
};
if (typeof A3API !== "undefined") {
A3API.SendAlert(JSON.stringify(alert));

View File

@ -22,8 +22,35 @@
#pragma hemtt ignore_variables ["_self"]
GVAR(GarageContextServiceBaseClass) = compileFinal createHashMapFromArray [
["#type", "GarageContextServiceBaseClass"],
["#create", compileFinal { _self set ["lastContext", createHashMap]; }],
["#delete", compileFinal { _self set ["lastContext", createHashMap]; }],
["#create", compileFinal {
_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 {
createHashMapFromArray [
["name", "Vehicle Garage"],
@ -184,8 +211,16 @@ GVAR(GarageContextServiceBaseClass) = compileFinal createHashMapFromArray [
_spawnLanes getOrDefault [_normalizedCategory, createHashMap]
}],
["resolveContext", compileFinal {
params [["_preferredGarageObject", objNull, [objNull]]];
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 _garageType = "";
private _anchorPosition = getPosATL player;
@ -215,7 +250,10 @@ GVAR(GarageContextServiceBaseClass) = compileFinal createHashMapFromArray [
_self set ["lastContext", _context];
_context
}],
["getContext", compileFinal { _self call ["resolveContext", []] }],
["getContext", compileFinal {
params [["_preferredGarageObject", objNull, [objNull]]];
_self call ["resolveContext", [_preferredGarageObject]]
}],
["buildNearbyState", compileFinal {
private _context = _self call ["getContext", []];
private _anchorPosition = _context getOrDefault ["anchorPosition", []];

View File

@ -20,6 +20,12 @@
* 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 _ctrl = _display displayCtrl 1006;

View File

@ -20,7 +20,13 @@
* 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]];
FORGE_VehSpawnPos = _spawnLane getOrDefault ["spawnPosition", player getPos [8, getDir player]];

View File

@ -76,7 +76,8 @@ Important task behavior:
## 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)
@ -90,29 +91,34 @@ entering recipient details every time.
### 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)
Use Messages to:
- start or continue a conversation with a contact
- read incoming messages
- mark messages as read
- delete messages you no longer need
![Example phone message conversation](images/player/phone_message_example.jpg)
### 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)
Use Email to:
![Example phone email](images/player/phone_email_example.jpg)
- send a subject and body to another player
- read incoming mail
- mark email as read
- delete old email
### Wallet
Wallet is the phone version of the bank app. Use it to refresh your account
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

View File

@ -75,7 +75,8 @@ Important task behavior:
## 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)
@ -89,29 +90,34 @@ entering recipient details every time.
### 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)
Use Messages to:
- start or continue a conversation with a contact
- read incoming messages
- mark messages as read
- delete messages you no longer need
![Example phone message conversation](images/player/phone_message_example.jpg)
### 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)
Use Email to:
![Example phone email](images/player/phone_email_example.jpg)
- send a subject and body to another player
- read incoming mail
- mark email as read
- delete old email
### Wallet
Wallet is the phone version of the bank app. Use it to refresh your account
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

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