feat(garage): enhance virtual garage functionality with spawn lane resolution and category handling
This commit is contained in:
parent
6be064fb15
commit
f3247b8e54
@ -37,6 +37,11 @@ the client.
|
||||
The client builds vehicle context and sends requests. The server garage addon
|
||||
and extension own stored vehicle state.
|
||||
|
||||
Virtual garage spawning resolves the active garage context and category lane,
|
||||
then finalizes only the vehicle selected in that BIS garage session. Nearby
|
||||
world vehicles are ignored as spawn candidates and are only used for the spawn
|
||||
blocking check at the resolved lane.
|
||||
|
||||
Refuel and repair buttons are available from the selected vehicle detail panel
|
||||
for nearby world vehicles. Stored records must be retrieved before they can be
|
||||
serviced because fuel and repair operate on live vehicle objects. Service
|
||||
|
||||
@ -88,9 +88,21 @@ GVAR(GarageActionServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||
GVAR(GarageUIBridge) call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [["message", "Stored vehicle record could not be found."]]]];
|
||||
};
|
||||
|
||||
private _className = _vehicleData getOrDefault ["classname", ""];
|
||||
if (_className isEqualTo "") exitWith {
|
||||
GVAR(GarageUIBridge) call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [["message", "Stored vehicle record is missing a classname."]]]];
|
||||
};
|
||||
|
||||
private _context = GVAR(GarageContextService) call ["getContext", []];
|
||||
private _spawnPosition = _context getOrDefault ["spawnPosition", getPosATL player];
|
||||
private _spawnHeading = _context getOrDefault ["spawnHeading", getDir player];
|
||||
private _vehicleCategory = GVAR(GarageHelperService) call ["resolveVGCategory", [_className]];
|
||||
private _spawnLane = GVAR(GarageContextService) call ["getExactSpawnLane", [_vehicleCategory, _context]];
|
||||
if (_spawnLane isEqualTo createHashMap) exitWith {
|
||||
private _categoryLabel = GVAR(GarageHelperService) call ["resolveGarageCategoryLabel", [_vehicleCategory]];
|
||||
GVAR(GarageUIBridge) call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [["message", format ["This garage does not support spawning %1.", _categoryLabel]]]]];
|
||||
};
|
||||
|
||||
private _spawnPosition = _spawnLane getOrDefault ["spawnPosition", _context getOrDefault ["spawnPosition", getPosATL player]];
|
||||
private _spawnHeading = _spawnLane getOrDefault ["spawnHeading", _context getOrDefault ["spawnHeading", getDir player]];
|
||||
private _spawnRadius = _context getOrDefault ["spawnRadius", 6];
|
||||
private _blockingVehicles = [];
|
||||
{ _blockingVehicles pushBackUnique _x; } forEach (_spawnPosition nearEntities [["Car", "Tank", "Air", "Ship"], _spawnRadius]);
|
||||
@ -99,11 +111,6 @@ GVAR(GarageActionServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||
GVAR(GarageUIBridge) call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [["message", "The garage spawn area is blocked."]]]];
|
||||
};
|
||||
|
||||
private _className = _vehicleData getOrDefault ["classname", ""];
|
||||
if (_className isEqualTo "") exitWith {
|
||||
GVAR(GarageUIBridge) call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [["message", "Stored vehicle record is missing a classname."]]]];
|
||||
};
|
||||
|
||||
private _vehicle = createVehicle [_className, _spawnPosition, [], 0, "CAN_COLLIDE"];
|
||||
_vehicle setDir _spawnHeading;
|
||||
_vehicle setFuel (_vehicleData getOrDefault ["fuel", 0]);
|
||||
|
||||
@ -29,81 +29,189 @@ GVAR(GarageContextServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||
["name", "Vehicle Garage"],
|
||||
["anchorPosition", getPosATL player],
|
||||
["sourceObject", objNull],
|
||||
["garageType", ""],
|
||||
["spawnHeading", getDir player],
|
||||
["spawnPosition", player getPos [8, getDir player]],
|
||||
["spawnLanes", createHashMap],
|
||||
["spawnRadius", 6],
|
||||
["nearbyRadius", 30]
|
||||
["nearbyRadius", 30],
|
||||
["laneRadius", 150]
|
||||
]
|
||||
}],
|
||||
["scanEntryValues", compileFinal {
|
||||
params [["_values", [], [[]]], ["_state", createHashMap, [createHashMap]]];
|
||||
["findNearbyGarageObject", compileFinal {
|
||||
private _nearestGarage = objNull;
|
||||
private _nearestDistance = 1e10;
|
||||
|
||||
{
|
||||
if (_x isEqualType "" && { (_state getOrDefault ["name", "Vehicle Garage"]) isEqualTo "Vehicle Garage" }) then { _state set ["name", _x]; };
|
||||
if (_x isEqualType "") then {
|
||||
private _resolvedObject = _state getOrDefault ["sourceObject", objNull];
|
||||
if (isNull _resolvedObject) then {
|
||||
private _namedObject = missionNamespace getVariable [_x, objNull];
|
||||
if (!isNull _namedObject) then { _state set ["sourceObject", _namedObject]; };
|
||||
};
|
||||
if ((_state getOrDefault ["anchorPosition", []]) isEqualTo [] && { _x in allMapMarkers }) then { _state set ["anchorPosition", markerPos _x]; };
|
||||
continue;
|
||||
if (isNull _x || { !(_x getVariable ["isGarage", false]) }) then { continue; };
|
||||
private _distance = player distance2D _x;
|
||||
if (_distance < _nearestDistance) then {
|
||||
_nearestDistance = _distance;
|
||||
_nearestGarage = _x;
|
||||
};
|
||||
if (_x isEqualType objNull && { isNull (_state getOrDefault ["sourceObject", objNull]) }) then {
|
||||
_state set ["sourceObject", _x];
|
||||
if ((_state getOrDefault ["anchorPosition", []]) isEqualTo []) then { _state set ["anchorPosition", getPosATL _x]; };
|
||||
continue;
|
||||
};
|
||||
if (_x isEqualType 0 && { (_state getOrDefault ["spawnHeading", -1]) < 0 }) then { _state set ["spawnHeading", _x]; continue; };
|
||||
if (_x isEqualType [] && { count _x > 0 }) then {
|
||||
if ({ _x isEqualType 0 } count _x >= 2 && { ((_state getOrDefault ["offset", []]) isEqualTo []) || ((_state getOrDefault ["anchorPosition", []]) isEqualTo []) }) then {
|
||||
if ((_state getOrDefault ["anchorPosition", []]) isEqualTo []) then { _state set ["anchorPosition", _x]; } else { _state set ["offset", _x]; };
|
||||
continue;
|
||||
};
|
||||
_self call ["scanEntryValues", [_x, _state]];
|
||||
};
|
||||
} forEach _values;
|
||||
_state
|
||||
} forEach (player nearObjects 12);
|
||||
|
||||
_nearestGarage
|
||||
}],
|
||||
["resolveEntry", compileFinal {
|
||||
params [["_entry", [], [[]]]];
|
||||
private _state = createHashMapFromArray [["name", "Vehicle Garage"], ["anchorPosition", []], ["sourceObject", objNull], ["offset", []], ["spawnHeading", -1]];
|
||||
_self call ["scanEntryValues", [_entry, _state]];
|
||||
private _anchorPosition = _state getOrDefault ["anchorPosition", []];
|
||||
private _offset = _state getOrDefault ["offset", []];
|
||||
private _spawnPosition = if (_anchorPosition isEqualTo []) then { [] } else { if (_offset isEqualTo []) then { _anchorPosition } else { _anchorPosition vectorAdd _offset } };
|
||||
createHashMapFromArray [["name", _state getOrDefault ["name", "Vehicle Garage"]], ["anchorPosition", _anchorPosition], ["sourceObject", _state getOrDefault ["sourceObject", objNull]], ["spawnHeading", _state getOrDefault ["spawnHeading", -1]], ["spawnPosition", _spawnPosition]]
|
||||
["resolveGarageName", compileFinal {
|
||||
params [["_garageObject", objNull, [objNull]]];
|
||||
|
||||
if (isNull _garageObject) exitWith { "Vehicle Garage" };
|
||||
|
||||
private _displayName = _garageObject getVariable ["garageName", ""];
|
||||
if (_displayName isNotEqualTo "") exitWith { _displayName };
|
||||
|
||||
private _varName = vehicleVarName _garageObject;
|
||||
if (_varName isEqualTo "") exitWith { "Vehicle Garage" };
|
||||
|
||||
_varName
|
||||
}],
|
||||
["buildMarkerLane", compileFinal {
|
||||
params [["_markerName", "", [""]], ["_garageObject", objNull, [objNull]]];
|
||||
|
||||
if (_markerName isEqualTo "" || { markerShape _markerName isEqualTo "" }) exitWith { createHashMap };
|
||||
|
||||
private _spawnCategory = GVAR(GarageHelperService) call ["inferGarageCategory", [_markerName]];
|
||||
if (_spawnCategory isEqualTo "") exitWith { createHashMap };
|
||||
|
||||
private _spawnPosition = markerPos _markerName;
|
||||
private _interactionPosition = if (isNull _garageObject) then { _spawnPosition } else { getPosATL _garageObject };
|
||||
private _markerDistance = if (isNull _garageObject) then { player distance2D _spawnPosition } else { _garageObject distance2D _spawnPosition };
|
||||
private _garageVarName = if (isNull _garageObject) then { "" } else { toLowerANSI (vehicleVarName _garageObject) };
|
||||
private _markerKey = toLowerANSI _markerName;
|
||||
private _nameScore = 0;
|
||||
|
||||
if (_garageVarName isNotEqualTo "" && { (_markerKey find _garageVarName) >= 0 }) then {
|
||||
_nameScore = -50;
|
||||
};
|
||||
|
||||
createHashMapFromArray [
|
||||
["name", _markerName],
|
||||
["interactionPosition", _interactionPosition],
|
||||
["sourceObject", _garageObject],
|
||||
["spawnCategory", _spawnCategory],
|
||||
["spawnHeading", markerDir _markerName],
|
||||
["spawnPosition", _spawnPosition],
|
||||
["score", _markerDistance + _nameScore]
|
||||
]
|
||||
}],
|
||||
["discoverSpawnLanes", compileFinal {
|
||||
params [["_garageObject", objNull, [objNull]]];
|
||||
|
||||
private _laneRadius = (_self call ["createDefaultContext", []]) getOrDefault ["laneRadius", 150];
|
||||
private _lanes = createHashMap;
|
||||
|
||||
{
|
||||
private _markerName = _x;
|
||||
if ((toLowerANSI _markerName find "garage") < 0) then { continue; };
|
||||
|
||||
private _entry = _self call ["buildMarkerLane", [_markerName, _garageObject]];
|
||||
if (_entry isEqualTo createHashMap) then { continue; };
|
||||
|
||||
private _spawnPosition = _entry getOrDefault ["spawnPosition", []];
|
||||
if (_spawnPosition isEqualTo []) then { continue; };
|
||||
|
||||
private _distance = if (isNull _garageObject) then { player distance2D _spawnPosition } else { _garageObject distance2D _spawnPosition };
|
||||
if (_distance > _laneRadius) then { continue; };
|
||||
|
||||
private _spawnCategory = _entry getOrDefault ["spawnCategory", ""];
|
||||
private _currentEntry = _lanes getOrDefault [_spawnCategory, createHashMap];
|
||||
|
||||
if (_currentEntry isEqualTo createHashMap || { (_entry getOrDefault ["score", 1e10]) < (_currentEntry getOrDefault ["score", 1e10]) }) then {
|
||||
_lanes set [_spawnCategory, _entry];
|
||||
};
|
||||
} forEach allMapMarkers;
|
||||
|
||||
_lanes
|
||||
}],
|
||||
["selectSpawnLane", compileFinal {
|
||||
params [
|
||||
["_lanes", createHashMap, [createHashMap]],
|
||||
["_preferredCategory", "", [""]],
|
||||
["_defaultPosition", [], [[]]],
|
||||
["_defaultHeading", 0, [0]]
|
||||
];
|
||||
|
||||
private _normalizedCategory = GVAR(GarageHelperService) call ["normalizeGarageCategory", [_preferredCategory]];
|
||||
private _lane = createHashMap;
|
||||
|
||||
if (_normalizedCategory isNotEqualTo "") then {
|
||||
_lane = _lanes getOrDefault [_normalizedCategory, createHashMap];
|
||||
};
|
||||
|
||||
if (_lane isEqualTo createHashMap) then {
|
||||
{
|
||||
private _candidate = _lanes getOrDefault [_x, createHashMap];
|
||||
if (_candidate isNotEqualTo createHashMap) exitWith { _lane = _candidate; };
|
||||
} forEach ["cars", "armor", "helis", "planes", "naval", "other"];
|
||||
};
|
||||
|
||||
if (_lane isEqualTo createHashMap) then {
|
||||
_lane = createHashMapFromArray [
|
||||
["spawnCategory", _normalizedCategory],
|
||||
["spawnHeading", _defaultHeading],
|
||||
["spawnPosition", _defaultPosition]
|
||||
];
|
||||
};
|
||||
|
||||
_lane
|
||||
}],
|
||||
["getSpawnLane", compileFinal {
|
||||
params [["_category", "", [""]], ["_context", createHashMap, [createHashMap]]];
|
||||
|
||||
private _resolvedContext = _context;
|
||||
if (_resolvedContext isEqualTo createHashMap) then {
|
||||
_resolvedContext = _self call ["getContext", []];
|
||||
};
|
||||
|
||||
private _spawnLanes = _resolvedContext getOrDefault ["spawnLanes", createHashMap];
|
||||
private _defaultPosition = _resolvedContext getOrDefault ["spawnPosition", getPosATL player];
|
||||
private _defaultHeading = _resolvedContext getOrDefault ["spawnHeading", getDir player];
|
||||
_self call ["selectSpawnLane", [_spawnLanes, _category, _defaultPosition, _defaultHeading]]
|
||||
}],
|
||||
["getExactSpawnLane", compileFinal {
|
||||
params [["_category", "", [""]], ["_context", createHashMap, [createHashMap]]];
|
||||
|
||||
private _resolvedContext = _context;
|
||||
if (_resolvedContext isEqualTo createHashMap) then {
|
||||
_resolvedContext = _self call ["getContext", []];
|
||||
};
|
||||
|
||||
private _normalizedCategory = GVAR(GarageHelperService) call ["normalizeGarageCategory", [_category]];
|
||||
if (_normalizedCategory isEqualTo "") exitWith { createHashMap };
|
||||
|
||||
private _spawnLanes = _resolvedContext getOrDefault ["spawnLanes", createHashMap];
|
||||
_spawnLanes getOrDefault [_normalizedCategory, createHashMap]
|
||||
}],
|
||||
["resolveContext", compileFinal {
|
||||
private _context = _self call ["createDefaultContext", []];
|
||||
private _locations = (missionConfigFile >> "FORGE_CfgGarages" >> "locations") call BFUNC(getCfgData);
|
||||
if !(_locations isEqualType []) exitWith { _self set ["lastContext", _context]; _context };
|
||||
private _garageObject = _self call ["findNearbyGarageObject", []];
|
||||
private _garageName = _self call ["resolveGarageName", [_garageObject]];
|
||||
private _garageType = "";
|
||||
private _anchorPosition = getPosATL player;
|
||||
private _spawnHeading = getDir player;
|
||||
private _spawnPosition = player getPos [8, _spawnHeading];
|
||||
private _spawnLanes = createHashMap;
|
||||
|
||||
private _nearestEntry = [];
|
||||
private _nearestDistance = 1e10;
|
||||
{
|
||||
private _entry = _self call ["resolveEntry", [_x]];
|
||||
private _anchorPosition = _entry getOrDefault ["anchorPosition", []];
|
||||
if (_anchorPosition isEqualTo []) then { continue; };
|
||||
private _distance = player distance2D _anchorPosition;
|
||||
if (_distance < _nearestDistance) then { _nearestDistance = _distance; _nearestEntry = _entry; };
|
||||
} forEach _locations;
|
||||
if (!isNull _garageObject) then {
|
||||
_garageType = GVAR(GarageHelperService) call ["normalizeGarageCategory", [_garageObject getVariable ["garageType", ""]]];
|
||||
_anchorPosition = getPosATL _garageObject;
|
||||
_spawnHeading = getDir _garageObject;
|
||||
_spawnPosition = _garageObject getPos [8, _spawnHeading];
|
||||
_spawnLanes = _self call ["discoverSpawnLanes", [_garageObject]];
|
||||
};
|
||||
|
||||
if (_nearestEntry isEqualTo []) exitWith { _self set ["lastContext", _context]; _context };
|
||||
|
||||
private _anchorPosition = _nearestEntry getOrDefault ["anchorPosition", []];
|
||||
private _garageObject = _nearestEntry getOrDefault ["sourceObject", objNull];
|
||||
private _garageName = _nearestEntry getOrDefault ["name", "Vehicle Garage"];
|
||||
private _spawnHeading = _nearestEntry getOrDefault ["spawnHeading", getDir player];
|
||||
if (_spawnHeading < 0) then { _spawnHeading = if (!isNull _garageObject) then { getDir _garageObject } else { getDir player }; };
|
||||
|
||||
private _spawnPosition = _nearestEntry getOrDefault ["spawnPosition", []];
|
||||
if (_spawnPosition isEqualTo []) then { _spawnPosition = if (_anchorPosition isEqualTo []) then { player getPos [8, _spawnHeading] } else { _anchorPosition }; };
|
||||
private _selectedLane = _self call ["selectSpawnLane", [_spawnLanes, _garageType, _spawnPosition, _spawnHeading]];
|
||||
_spawnHeading = _selectedLane getOrDefault ["spawnHeading", _spawnHeading];
|
||||
_spawnPosition = _selectedLane getOrDefault ["spawnPosition", _spawnPosition];
|
||||
|
||||
_context set ["name", _garageName];
|
||||
_context set ["anchorPosition", _anchorPosition];
|
||||
_context set ["sourceObject", _garageObject];
|
||||
_context set ["garageType", _garageType];
|
||||
_context set ["spawnHeading", _spawnHeading];
|
||||
_context set ["spawnPosition", _spawnPosition];
|
||||
_context set ["spawnLanes", _spawnLanes];
|
||||
_self set ["lastContext", _context];
|
||||
_context
|
||||
}],
|
||||
|
||||
@ -22,6 +22,33 @@
|
||||
#pragma hemtt ignore_variables ["_self"]
|
||||
GVAR(GarageHelperServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||
["#type", "GarageHelperServiceBaseClass"],
|
||||
["normalizeGarageCategory", compileFinal {
|
||||
params [["_value", "", [""]]];
|
||||
|
||||
private _normalized = toLowerANSI (trim _value);
|
||||
if (_normalized isEqualTo "") exitWith { "" };
|
||||
if (_normalized in ["cars", "armor", "helis", "planes", "naval", "other"]) exitWith { _normalized };
|
||||
""
|
||||
}],
|
||||
["inferGarageCategory", compileFinal {
|
||||
params [["_value", "", [""]]];
|
||||
|
||||
private _normalized = toLowerANSI (trim _value);
|
||||
if (_normalized isEqualTo "") exitWith { "" };
|
||||
|
||||
private _resolvedCategory = _self call ["normalizeGarageCategory", [_normalized]];
|
||||
if (_resolvedCategory isNotEqualTo "") exitWith { _resolvedCategory };
|
||||
|
||||
switch (true) do {
|
||||
case ((_normalized find "cars") >= 0): { "cars" };
|
||||
case ((_normalized find "armor") >= 0): { "armor" };
|
||||
case ((_normalized find "helis") >= 0): { "helis" };
|
||||
case ((_normalized find "planes") >= 0): { "planes" };
|
||||
case ((_normalized find "naval") >= 0): { "naval" };
|
||||
case ((_normalized find "other") >= 0): { "other" };
|
||||
default { "" };
|
||||
}
|
||||
}],
|
||||
["resolveCategory", compileFinal {
|
||||
params [["_className", "", [""]]];
|
||||
|
||||
@ -36,6 +63,33 @@ GVAR(GarageHelperServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||
default { "other" };
|
||||
}
|
||||
}],
|
||||
["resolveVGCategory", compileFinal {
|
||||
params [["_className", "", [""]]];
|
||||
|
||||
if (_className isEqualTo "") exitWith { "other" };
|
||||
|
||||
switch (true) do {
|
||||
case (_className isKindOf ["Car", configFile >> "CfgVehicles"]): { "cars" };
|
||||
case (_className isKindOf ["Tank", configFile >> "CfgVehicles"]): { "armor" };
|
||||
case (_className isKindOf ["Helicopter", configFile >> "CfgVehicles"]): { "helis" };
|
||||
case (_className isKindOf ["Plane", configFile >> "CfgVehicles"]): { "planes" };
|
||||
case (_className isKindOf ["Ship", configFile >> "CfgVehicles"]): { "naval" };
|
||||
default { "other" };
|
||||
}
|
||||
}],
|
||||
["resolveGarageCategoryLabel", compileFinal {
|
||||
params [["_category", "", [""]]];
|
||||
|
||||
switch (_category) do {
|
||||
case "cars": { "cars" };
|
||||
case "armor": { "armored vehicles" };
|
||||
case "helis": { "helicopters" };
|
||||
case "planes": { "planes" };
|
||||
case "naval": { "naval vehicles" };
|
||||
case "other": { "other vehicles" };
|
||||
default { "this vehicle type" };
|
||||
}
|
||||
}],
|
||||
["resolveDisplayName", compileFinal {
|
||||
params [["_className", "", [""]]];
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* File: fnc_openVG.sqf
|
||||
* Author: IDSolutions
|
||||
* Date: 2025-12-16
|
||||
* Last Update: 2026-01-30
|
||||
* Last Update: 2026-04-22
|
||||
* Public: No
|
||||
*
|
||||
* Description:
|
||||
@ -20,11 +20,12 @@
|
||||
* call forge_client_garage_fnc_openVG
|
||||
*/
|
||||
|
||||
private _locations = (missionConfigFile >> "FORGE_CfgGarages" >> "locations") call BFUNC(getCfgData);
|
||||
{
|
||||
FORGE_VehSpawnPos = (_x select 1) getPos [5, (_x select 2)];
|
||||
true;
|
||||
} count _locations;
|
||||
private _context = GVAR(GarageContextService) call ["getContext", []];
|
||||
private _spawnLane = GVAR(GarageContextService) call ["getSpawnLane", [_context getOrDefault ["garageType", ""], _context]];
|
||||
|
||||
FORGE_VehSpawnPos = _spawnLane getOrDefault ["spawnPosition", player getPos [8, getDir player]];
|
||||
missionNamespace setVariable [QGVAR(activeVGContext), _context];
|
||||
missionNamespace setVariable [QGVAR(activeVGNearbyVehicles), + (FORGE_VehSpawnPos nearEntities [["Car", "Tank", "Air", "Ship"], 15])];
|
||||
|
||||
BIS_fnc_garage_center = createVehicle ["Land_HelipadEmpty_F", FORGE_VehSpawnPos, [], 0, "NONE"];
|
||||
BIS_fnc_garage_centerType = getText (configFile >> "CfgVehicles" >> "B_Quadbike_01_F" >> "model");
|
||||
@ -53,16 +54,41 @@ if !(GVAR(isPreLoaded)) then {
|
||||
}] call BFUNC(addScriptedEventHandler);
|
||||
|
||||
[missionNamespace, "garageClosed", {
|
||||
private _nearestObjects = BIS_fnc_garage_center nearEntities [["Car","Tank","Air","Ship"], 15];
|
||||
private _nearbyVehicles = BIS_fnc_garage_center nearEntities [["Car", "Tank", "Air", "Ship"], 15];
|
||||
private _preExistingVehicles = missionNamespace getVariable [QGVAR(activeVGNearbyVehicles), []];
|
||||
private _spawnedVehicles = _nearbyVehicles select { !(_x in _preExistingVehicles) };
|
||||
|
||||
if (_spawnedVehicles isNotEqualTo []) then {
|
||||
private _spawnedVehiclePairs = _spawnedVehicles apply { [_x distance2D BIS_fnc_garage_center, _x] };
|
||||
_spawnedVehiclePairs sort true;
|
||||
|
||||
private _obj = (_spawnedVehiclePairs select 0) param [1, objNull];
|
||||
if (isNull _obj) exitWith {
|
||||
missionNamespace setVariable [QGVAR(activeVGNearbyVehicles), nil];
|
||||
missionNamespace setVariable [QGVAR(activeVGContext), nil];
|
||||
};
|
||||
|
||||
if (!isNil "_nearestObjects") then {
|
||||
private _obj = _nearestObjects select 0;
|
||||
private _veh = typeOf _obj;
|
||||
private _textures = getObjectTextures _obj;
|
||||
private _animationNames = animationNames _obj;
|
||||
private _context = missionNamespace getVariable [QGVAR(activeVGContext), createHashMap];
|
||||
private _spawnCategory = GVAR(GarageHelperService) call ["resolveVGCategory", [_veh]];
|
||||
private _spawnLane = GVAR(GarageContextService) call ["getExactSpawnLane", [_spawnCategory, _context]];
|
||||
private _spawnLabel = GVAR(GarageHelperService) call ["resolveGarageCategoryLabel", [_spawnCategory]];
|
||||
|
||||
{ deleteVehicle _x } forEach _nearestObjects;
|
||||
private _createVehicle = createVehicle [_veh, FORGE_VehSpawnPos, [], 0, "CAN_COLLIDE"];
|
||||
{ deleteVehicle _x } forEach _spawnedVehicles;
|
||||
|
||||
if (_spawnLane isEqualTo createHashMap) exitWith {
|
||||
missionNamespace setVariable [QGVAR(activeVGNearbyVehicles), nil];
|
||||
missionNamespace setVariable [QGVAR(activeVGContext), nil];
|
||||
private _params = ["warning", "Virtual Garage", format ["This garage does not support spawning %1.", _spawnLabel], 4000];
|
||||
EGVAR(notifications,NotificationService) call ["create", _params];
|
||||
};
|
||||
|
||||
private _spawnPosition = _spawnLane getOrDefault ["spawnPosition", FORGE_VehSpawnPos];
|
||||
private _spawnHeading = _spawnLane getOrDefault ["spawnHeading", getDir _obj];
|
||||
private _createVehicle = createVehicle [_veh, _spawnPosition, [], 0, "CAN_COLLIDE"];
|
||||
_createVehicle setDir _spawnHeading;
|
||||
|
||||
if (_textures isNotEqualTo []) then {
|
||||
private _count = 0;
|
||||
@ -81,6 +107,9 @@ if !(GVAR(isPreLoaded)) then {
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
missionNamespace setVariable [QGVAR(activeVGNearbyVehicles), nil];
|
||||
missionNamespace setVariable [QGVAR(activeVGContext), nil];
|
||||
}] call BFUNC(addScriptedEventHandler);
|
||||
|
||||
GVAR(isPreLoaded) = true;
|
||||
|
||||
@ -20,14 +20,37 @@
|
||||
* call forge_server_garage_fnc_initGarage
|
||||
*/
|
||||
|
||||
private _resolveGarageType = {
|
||||
params [["_value", "", [""]]];
|
||||
|
||||
private _normalized = toLowerANSI (trim _value);
|
||||
|
||||
switch (true) do {
|
||||
case ((_normalized find "cars") >= 0): { "cars" };
|
||||
case ((_normalized find "armor") >= 0): { "armor" };
|
||||
case ((_normalized find "helis") >= 0): { "helis" };
|
||||
case ((_normalized find "planes") >= 0): { "planes" };
|
||||
case ((_normalized find "naval") >= 0): { "naval" };
|
||||
case ((_normalized find "other") >= 0): { "other" };
|
||||
default { "" };
|
||||
}
|
||||
};
|
||||
|
||||
private _garages = (allVariables missionNamespace) select {
|
||||
private _var = missionNamespace getVariable _x;
|
||||
("garage" in _x) && { _var isEqualType objNull } && { !isNull _var }
|
||||
((toLowerANSI _x) find "garage") >= 0 && { _var isEqualType objNull } && { !isNull _var }
|
||||
};
|
||||
|
||||
if (_garages isEqualTo []) exitWith { ["INFO", "No editor-placed garages found."] call EFUNC(common,log) };
|
||||
|
||||
{
|
||||
private _garage = missionNamespace getVariable _x;
|
||||
private _garageName = _x;
|
||||
private _garage = missionNamespace getVariable _garageName;
|
||||
SETPVAR(_garage,isGarage,true);
|
||||
if ((_garage getVariable ["garageType", ""]) isEqualTo "") then {
|
||||
private _garageType = _garageName call _resolveGarageType;
|
||||
if (_garageType isNotEqualTo "") then {
|
||||
SETPVAR(_garage,garageType,_garageType);
|
||||
};
|
||||
};
|
||||
} forEach _garages;
|
||||
|
||||
@ -19,9 +19,12 @@ browser events through `forge_client_garage_fnc_handleUIEvents`.
|
||||
call forge_client_garage_fnc_openVG;
|
||||
```
|
||||
|
||||
The virtual garage uses mission-configured `FORGE_CfgGarages` locations to set
|
||||
the spawn/preview position, opens the BIS garage interface, and restricts the
|
||||
available vehicle lists from the virtual garage repository.
|
||||
The virtual garage resolves the active interaction object near the player,
|
||||
discovers nearby `garage*` markers placed in Eden, chooses the matching spawn
|
||||
lane for the selected vehicle type, opens the BIS garage interface, and
|
||||
restricts the available vehicle lists from the virtual garage repository. When
|
||||
the BIS garage closes, only the vehicle selected in that virtual garage session
|
||||
is finalized and spawned onto the resolved lane.
|
||||
|
||||
## Client Services
|
||||
|
||||
@ -79,8 +82,24 @@ _object setVariable ["isGarage", true, true];
|
||||
_object setVariable ["garageType", "cars", true];
|
||||
```
|
||||
|
||||
Virtual garage access also requires configured garage locations in mission
|
||||
config so the preview/spawn position can be resolved.
|
||||
When using the server garage auto-init flow, editor-placed objects whose
|
||||
variable names contain `garage` are marked as garage interaction points and
|
||||
their `garageType` can be inferred from the name.
|
||||
|
||||
Virtual garage spawn lanes are resolved from empty markers placed in Eden. The
|
||||
marker name should contain `garage` and one of the six supported category names:
|
||||
`cars`, `armor`, `helis`, `planes`, `naval`, or `other`. Markers are matched to
|
||||
the nearby interaction object by proximity, and names that include the garage
|
||||
object's variable name are preferred when multiple garages exist.
|
||||
|
||||
Vehicle spawning is strict by category. If the active garage site does not have
|
||||
a matching local marker for the vehicle category being retrieved or spawned from
|
||||
the virtual garage, the request is blocked and the player is shown a message.
|
||||
|
||||
Nearby world vehicles are not used as virtual garage spawn candidates. They are
|
||||
only checked to determine whether the resolved spawn position is blocked. If
|
||||
any vehicle is within 5 meters of the spawn marker when the virtual garage is
|
||||
opened, the session is blocked and the player is shown a warning.
|
||||
|
||||
## Authoritative State
|
||||
|
||||
|
||||
@ -19,9 +19,12 @@ browser events through `forge_client_garage_fnc_handleUIEvents`.
|
||||
call forge_client_garage_fnc_openVG;
|
||||
```
|
||||
|
||||
The virtual garage uses mission-configured `FORGE_CfgGarages` locations to set
|
||||
the spawn/preview position, opens the BIS garage interface, and restricts the
|
||||
available vehicle lists from the virtual garage repository.
|
||||
The virtual garage resolves the active interaction object near the player,
|
||||
discovers nearby `garage*` markers placed in Eden, chooses the matching spawn
|
||||
lane for the selected vehicle type, opens the BIS garage interface, and
|
||||
restricts the available vehicle lists from the virtual garage repository. When
|
||||
the BIS garage closes, only the vehicle selected in that virtual garage session
|
||||
is finalized and spawned onto the resolved lane.
|
||||
|
||||
## Client Services
|
||||
|
||||
@ -79,8 +82,24 @@ _object setVariable ["isGarage", true, true];
|
||||
_object setVariable ["garageType", "cars", true];
|
||||
```
|
||||
|
||||
Virtual garage access also requires configured garage locations in mission
|
||||
config so the preview/spawn position can be resolved.
|
||||
When using the server garage auto-init flow, editor-placed objects whose
|
||||
variable names contain `garage` are marked as garage interaction points and
|
||||
their `garageType` can be inferred from the name.
|
||||
|
||||
Virtual garage spawn lanes are resolved from empty markers placed in Eden. The
|
||||
marker name should contain `garage` and one of the six supported category names:
|
||||
`cars`, `armor`, `helis`, `planes`, `naval`, or `other`. Markers are matched to
|
||||
the nearby interaction object by proximity, and names that include the garage
|
||||
object's variable name are preferred when multiple garages exist.
|
||||
|
||||
Vehicle spawning is strict by category. If the active garage site does not have
|
||||
a matching local marker for the vehicle category being retrieved or spawned from
|
||||
the virtual garage, the request is blocked and the player is shown a message.
|
||||
|
||||
Nearby world vehicles are not used as virtual garage spawn candidates. They are
|
||||
only checked to determine whether the resolved spawn position is blocked. If
|
||||
any vehicle is within 5 meters of the spawn marker when the virtual garage is
|
||||
opened, the session is blocked and the player is shown a warning.
|
||||
|
||||
## Authoritative State
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user