Skip to content

Commit 0abb0f5

Browse files
authored
Quick Mount - Add Get In actions to vehicle seats (#11380)
* Add Get In actions to vehicle seats * Add parameters to drawSeatProxies * Add API to override proxy with vehicle config or variable * Add macro for distance and height * Use _actionParams * Use _actionParams in config
1 parent c6a9a13 commit 0abb0f5

10 files changed

Lines changed: 355 additions & 21 deletions

addons/quickmount/CfgVehicles.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,17 @@ class CfgVehicles {
3535
};
3636
};
3737

38+
// because Get In action has its own statement
39+
// we have to cache subactions in args and reuse them in insertChildren code
3840
#define GETIN_ACTIONS \
3941
class ACE_Actions { \
4042
class ACE_MainActions { \
4143
class GVAR(GetIn) { \
4244
displayName = "$STR_rscMenu.hppRscGroupRootMenu_Items_GetIn1"; \
43-
condition = QUOTE(call DFUNC(canShowFreeSeats)); \
45+
condition = QUOTE(call DFUNC(canShowFreeSeats) && {_actionParams set [ARR_2(0,call DFUNC(addFreeSeatsActions))];_actionParams select 0 isNotEqualTo []}); \
4446
statement = QUOTE(call DFUNC(getInNearest)); \
4547
exceptions[] = {"isNotSwimming"}; \
46-
insertChildren = QUOTE((_this select 2) param [ARR_2(0,[])]); \
48+
insertChildren = QUOTE(_actionParams param [ARR_2(0,[])]); \
4749
}; \
4850
}; \
4951
}; \

addons/quickmount/XEH_PREP.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
PREP(addFreeSeatsActions);
2+
PREP(addGetInActions);
23
PREP(canShowFreeSeats);
34
PREP(getInNearest);
5+
PREP(getSeatProxies);
6+
PREP(getSeatUnit);
47
PREP(moduleInit);

addons/quickmount/XEH_postInitClient.sqf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ if (!hasInterface) exitWith {};
77
[] call FUNC(getInNearest);
88
};
99
}] call CBA_fnc_addKeybind;
10+
11+
GVAR(initializedVehicleClasses) = [];
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#include "..\script_component.hpp"
2+
3+
// call compileScript ["z\ace\addons\quickmount\dev\drawSeatProxies.sqf"]
4+
// [_showSingleProxies, _minProxyDistance, _showAnyProxy] call compileScript ["z\ace\addons\quickmount\dev\drawSeatProxies.sqf"]
5+
6+
params [["_showSingleProxies", false], ["_minProxyDistance", 0.1], ["_showAnyProxy", false]];
7+
8+
GVAR(showSingleProxies) = _showSingleProxies; // show seats that have only one proxy point
9+
GVAR(minProxyDistance) = _minProxyDistance; // minimum distance between nearby proxies to show both
10+
GVAR(showAnyProxy) = _showAnyProxy; // show any proxy not only seat
11+
12+
if (!isNil QGVAR(draw3DEH)) then {
13+
removeMissionEventHandler ["Draw3D", GVAR(draw3DEH)];
14+
};
15+
16+
GVAR(draw3DEH) = addMissionEventHandler ["Draw3D", {
17+
{
18+
private _vehicle = _x;
19+
if (!(_vehicle isKindOf "AllVehicles") || {_vehicle isKindOf "Man"}) then {continue};
20+
21+
// make unique var name to recalc proxies
22+
private _seatProxies = _vehicle getVariable format ["%1%2", QGVAR(seatProxies), GVAR(draw3DEH)];
23+
if (isNil "_seatProxies") then {
24+
// see fnc_getSeatProxies
25+
_seatProxies = createHashMap;
26+
private _proxyRolePrefixes = ["driver", "gunner", "commander", "cargo", "pilot"];
27+
private _proxyRoleMappings = ["driver", "gunner", "commander", "cargo", "driver", ""];
28+
private _allLODsNumbers = allLODs _vehicle apply {_x select 2};
29+
{
30+
private _lodNumber = _x;
31+
private _proxyPaths = _vehicle selectionNames _lodNumber select {
32+
_x select [0,6] == "proxy:"
33+
};
34+
private _color = [random 1, random 1, random 1, 1];
35+
{
36+
private _proxyPath = _x;
37+
private _substrings = _proxyPath splitString ":\.";
38+
if (count _substrings < 3) then {continue};
39+
private _proxyIndex = parseNumber (_substrings select -1);
40+
if (_proxyIndex < 1) then {continue};
41+
private _proxyRole = toLower (_substrings select -2);
42+
private _proxyGroup = _proxyRoleMappings select (_proxyRolePrefixes findIf {_proxyRole find _x == 0});
43+
if (_proxyGroup == "" && {!GVAR(showAnyProxy)}) then {continue};
44+
45+
private _proxyHashMap = _seatProxies getOrDefault [_proxyGroup, createHashMap, true];
46+
private _proxies = _proxyHashMap getOrDefault [_proxyIndex, [], true];
47+
private _pos = _vehicle selectionPosition [_proxyPath, _lodNumber, "AveragePoint"];
48+
// skip nearby proxies
49+
if (-1 < _proxies findIf {GVAR(minProxyDistance) > _vehicle selectionPosition (_x select 0) vectorDistance _pos}) then {continue};
50+
_proxies pushBack [[_proxyPath, _lodNumber, "AveragePoint"], format ["%1:%2.%3", _lodNumber, _proxyRole, _proxyIndex], _color];
51+
} forEach _proxyPaths;
52+
} forEach _allLODsNumbers;
53+
_vehicle setVariable [format ["%1%2", QGVAR(seatProxies), GVAR(draw3DEH)], _seatProxies];
54+
};
55+
56+
{
57+
{
58+
if (!GVAR(showSingleProxies) && {count _y < 2}) then {continue}; // skip seats with only one proxy
59+
{
60+
drawIcon3D [
61+
"\a3\ui_f\data\gui\cfg\hints\icon_text\group_1_ca.paa",
62+
_x select 2,
63+
_vehicle modelToWorldVisual (_vehicle selectionPosition (_x select 0)),
64+
0.5, 0.5, 0,
65+
_x select 1
66+
];
67+
} forEach _y;
68+
} forEach _y;
69+
} forEach _seatProxies;
70+
} forEach [cursorObject, objectParent ACE_player];
71+
}];
72+
73+
GVAR(draw3DEH)

addons/quickmount/functions/fnc_addFreeSeatsActions.sqf

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,6 @@
1818

1919
#define TAKEN_SEAT_TIMEOUT 0.5
2020

21-
#define ICON_DRIVER "A3\ui_f\data\IGUI\RscIngameUI\RscUnitInfo\role_driver_ca.paa"
22-
#define ICON_PILOT "A3\ui_f\data\IGUI\Cfg\Actions\getinpilot_ca.paa"
23-
#define ICON_CARGO "A3\ui_f\data\IGUI\RscIngameUI\RscUnitInfo\role_cargo_ca.paa"
24-
#define ICON_GUNNER "A3\ui_f\data\IGUI\Cfg\Actions\getingunner_ca.paa"
25-
#define ICON_COMMANDER "A3\ui_f\data\IGUI\RscIngameUI\RscUnitInfo\role_commander_ca.paa"
26-
#define ICON_TURRET "A3\ui_f\data\IGUI\RscIngameUI\RscUnitInfo\role_gunner_ca.paa"
27-
#define ICON_FFV "A3\ui_f\data\IGUI\Cfg\CrewAimIndicator\gunnerAuto_ca.paa"
28-
2921
#define TO_COMPARTMENT_STRING(var) if !(var isEqualType "") then {var = format [ARR_2("Compartment%1",var)]}
3022

3123
// if unit isn't moved to new seat in TAKEN_SEAT_TIMEOUT, we move him back to his seat
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#include "..\script_component.hpp"
2+
/*
3+
* Author: Dystopian
4+
* Adds Get In actions for vehicle seats and Passenger Actions for vehicle crew.
5+
*
6+
* Arguments:
7+
* 0: Vehicle <OBJECT>
8+
*
9+
* Return Value:
10+
* None
11+
*
12+
* Example:
13+
* cursorObject call ace_quickmount_fnc_addGetInActions
14+
*
15+
* Public: No
16+
*/
17+
18+
#define ACTION_DISTANCE 4
19+
#define CREW_HEIGHT_ABOVE_SEAT 0.3
20+
21+
params ["_vehicle"];
22+
23+
private _vehicleClass = typeOf _vehicle;
24+
private _vehicleConfig = configOf _vehicle;
25+
private _cargoProxyIndexes = getArray (_vehicleConfig >> "cargoProxyIndexes");
26+
private _seatProxies = _vehicle call FUNC(getSeatProxies);
27+
28+
private _seatPositions = GETVAR(_vehicle,GVAR(seatPositions),[]);
29+
if (_seatPositions isEqualType []) then {
30+
_seatPositions = createHashMapFromArray _seatPositions;
31+
};
32+
private _seatPositionsConfig = getArray (_vehicleConfig >> QGVAR(seatPositions));
33+
_seatPositionsConfig params [["_model", ""], ["_seatPositionsArray", []]];
34+
// handle inherited classes with different model
35+
if (_model == getText (_vehicleConfig >> "model")) then {
36+
_seatPositions merge createHashMapFromArray _seatPositionsArray;
37+
};
38+
39+
TRACE_4("addGetInActions",_vehicleClass,_cargoProxyIndexes,_seatPositions,_seatProxies);
40+
41+
private _conditionCrew = {
42+
call FUNC(canShowFreeSeats)
43+
&& {!isNull ([_target, _actionParams select 0] call FUNC(getSeatUnit))}
44+
};
45+
private _insertChildrenCrew = {
46+
private _unit = [_target, _actionParams select 0] call FUNC(getSeatUnit);
47+
[nil, nil, _unit] call EFUNC(interaction,addPassengerActions)
48+
};
49+
private _modifierFunctionCrew = {
50+
params ["_target", "", "_params", "_actionData"];
51+
_params params ["_seat"];
52+
private _unit = [_target, _seat] call FUNC(getSeatUnit);
53+
if (isNull _unit) exitWith {}; // skip empty seats
54+
_actionData set [1, [_unit, true] call EFUNC(common,getName)];
55+
if (["ace_medical_gui"] call EFUNC(common,isModLoaded)) then {
56+
_this set [0, _unit]; // == _target
57+
call EFUNC(medical_gui,modifyActionTriageLevel);
58+
};
59+
};
60+
61+
private _allSeats = fullCrew [_vehicle, "", true];
62+
private _cargoPositionNumber = -1;
63+
64+
{
65+
_x params ["", "_role", "_cargoIndex", "_turretPath", "_isPersonTurret", "", "_positionName"];
66+
private _name = if (isLocalized _positionName) then {localize _positionName} else {_positionName};
67+
68+
private ["_proxyGroup", "_proxyIndex", "_icon", "_condition", "_statement", "_params", "_position", "_positionCrew"];
69+
switch (_role) do {
70+
case "driver": {
71+
if (
72+
unitIsUAV _vehicle
73+
|| {getNumber (_vehicleConfig >> "hasDriver") == 0}
74+
) then {continue};
75+
_proxyGroup = "driver";
76+
_proxyIndex = 1;
77+
_params = [_turretPath];
78+
_condition = {
79+
call FUNC(canShowFreeSeats)
80+
&& {!lockedDriver _target}
81+
&& {!alive driver _target}
82+
};
83+
_statement = {_player action ["GetInDriver", _target]};
84+
_icon = [ICON_DRIVER, ICON_PILOT] select (_vehicle isKindOf "Air");
85+
};
86+
case "cargo": {
87+
INC(_cargoPositionNumber);
88+
_proxyGroup = "cargo";
89+
_proxyIndex = _cargoProxyIndexes param [_cargoPositionNumber, _cargoPositionNumber + 1];
90+
_params = [_cargoIndex, _cargoPositionNumber];
91+
_condition = {
92+
call FUNC(canShowFreeSeats)
93+
&& {
94+
private _cargoIndex = _actionParams select 0;
95+
!(_target lockedCargo _cargoIndex)
96+
&& {!alive ([_target, _cargoIndex] call FUNC(getSeatUnit))}
97+
}
98+
};
99+
_statement = {_player action ["GetInCargo", _target, _actionParams select 1]};
100+
_name = format ["%1 %2", _name, _cargoPositionNumber + 1];
101+
_icon = ICON_CARGO;
102+
};
103+
default {
104+
if (_role == "gunner" && {unitIsUAV _vehicle}) then {continue};
105+
private _turretConfig = [_vehicleConfig, _turretPath] call CBA_fnc_getTurret;
106+
private _proxyType = getText (_turretConfig >> "proxyType");
107+
_proxyGroup = switch (_proxyType) do {
108+
case "CPCommander": {"commander"};
109+
case "CPGunner": {"gunner"};
110+
default {"cargo"};
111+
};
112+
_proxyIndex = getNumber (_turretConfig >> "proxyIndex");
113+
_params = [_turretPath];
114+
_condition = {
115+
call FUNC(canShowFreeSeats)
116+
&& {
117+
private _turretPath = _actionParams select 0;
118+
!(_target lockedTurret _turretPath)
119+
&& {!alive ([_target, _turretPath] call FUNC(getSeatUnit))}
120+
}
121+
};
122+
_statement = {_player action ["GetInTurret", _target, _actionParams select 0]};
123+
_icon = switch true do {
124+
case (getNumber (_turretConfig >> "isCopilot") > 0): {ICON_PILOT};
125+
case (_role == "gunner"): {ICON_GUNNER};
126+
case (_role == "commander"): {ICON_COMMANDER};
127+
case (_isPersonTurret): {ICON_FFV};
128+
case (getText (_turretConfig >> "gun") == ""): {ICON_CARGO};
129+
default {ICON_TURRET};
130+
};
131+
};
132+
};
133+
134+
private _positionString = _seatPositions getOrDefault [_params select 0, ""];
135+
if (_positionString != "") then {
136+
_position = compile _positionString;
137+
_positionCrew = compile format ["(%1) vectorAdd [0, 0, %2]", _positionString, CREW_HEIGHT_ABOVE_SEAT];
138+
TRACE_6("seat position",_role,_cargoIndex,_turretPath,_proxyGroup,_proxyIndex,_position);
139+
} else {
140+
private _seatProxy = _seatProxies getOrDefault [_proxyGroup, createHashMap] getOrDefault [_proxyIndex, []];
141+
TRACE_6("seat proxy",_role,_cargoIndex,_turretPath,_proxyGroup,_proxyIndex,_seatProxy);
142+
if (_seatProxy isEqualTo []) then {continue};
143+
// cannot use static position because some proxy positions move with turret rotation
144+
_position = compile format ["_target selectionPosition ['%1', %2, 'AveragePoint']", _seatProxy select 0, _seatProxy select 1];
145+
_positionCrew = compile format [
146+
"_target selectionPosition ['%1', %2, 'AveragePoint'] vectorAdd [0, 0, %3]",
147+
_seatProxy select 0,
148+
_seatProxy select 1,
149+
CREW_HEIGHT_ABOVE_SEAT
150+
];
151+
};
152+
153+
private _action = [
154+
format ["%1%2%3empty", _role, _cargoIndex, _turretPath],
155+
_name, _icon, _statement, _condition, {}, _params, _position, ACTION_DISTANCE
156+
] call EFUNC(interact_menu,createAction);
157+
[_vehicleClass, 0, [], _action] call EFUNC(interact_menu,addActionToClass);
158+
159+
// modifier function needs icon color
160+
private _actionCrew = [
161+
format ["%1%2%3crew", _role, _cargoIndex, _turretPath],
162+
"", [_icon, "#FFFFFF"], {}, _conditionCrew, _insertChildrenCrew,
163+
_params, _positionCrew, ACTION_DISTANCE, nil, _modifierFunctionCrew
164+
] call EFUNC(interact_menu,createAction);
165+
[_vehicleClass, 0, [], _actionCrew] call EFUNC(interact_menu,addActionToClass);
166+
} forEach _allSeats;

addons/quickmount/functions/fnc_canShowFreeSeats.sqf

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
/*
33
* Author: Dystopian
44
* Checks if Free Seats menu can be shown.
5+
* Result is cached for 1 second for reuse in several visible ATM actions.
56
*
67
* Arguments:
78
* 0: Vehicle <OBJECT>
89
* 1: Unit <OBJECT>
910
* 2: Args <ARRAY>
11+
* 3: Use cache <BOOL> (default: true)
1012
*
1113
* Return Value:
1214
* Can show menu <BOOL>
@@ -17,34 +19,40 @@
1719
* Public: No
1820
*/
1921

20-
params ["_vehicle", "_unit", "_args"];
22+
params ["_vehicle", "_unit", ["_args", []], ["_useCache", true]];
2123

2224
private _isInVehicle = _unit in _vehicle;
2325

26+
// function is called by multiple actions and MainAction
27+
if (!_isInVehicle && {_useCache}) exitWith {
28+
_this set [3, false];
29+
[_this, LINKFUNC(canShowFreeSeats), _vehicle, QGVAR(canShowFreeSeats), 1] call EFUNC(common,cachedCall) // return
30+
};
31+
32+
TRACE_6("canShowFreeSeats",_vehicle,typeOf _vehicle,_unit,_isInVehicle,_args,_useCache);
33+
2434
GVAR(enabled)
2535
&& {
2636
GVAR(enableMenu) == 3
2737
|| {_isInVehicle && {GVAR(enableMenu) == 2}}
2838
|| {!_isInVehicle && {GVAR(enableMenu) == 1}}
2939
}
3040
&& {alive _vehicle}
31-
&& {2 > locked _vehicle}
41+
&& {locked _vehicle < 2}
3242
&& {isNull getConnectedUAVUnit _unit}
3343
&& {simulationEnabled _vehicle}
44+
&& {[_unit, _vehicle] call EFUNC(interaction,canInteractWithVehicleCrew)}
3445
&& {
35-
[_unit, _vehicle] call EFUNC(interaction,canInteractWithVehicleCrew)
36-
}
37-
&& {
38-
0.3 < vectorUp _vehicle select 2 // moveIn* and GetIn* don't work for flipped vehicles
46+
vectorUp _vehicle select 2 > 0.3 // moveIn* and GetIn* don't work for flipped vehicles
3947
|| {_vehicle isKindOf "Air"} // except Air
4048
}
4149
&& {
4250
_isInVehicle
51+
|| {typeOf _vehicle in GVAR(initializedVehicleClasses)}
4352
|| {
44-
// because Get In action has its own statement
45-
// we have to cache subactions in args and reuse them in insertChildren code
46-
private _subActions = call FUNC(addFreeSeatsActions);
47-
_args set [0, _subActions];
48-
[] isNotEqualTo _subActions
53+
// init vehicle actions here to skip useless checks on clients with disabled quickmount
54+
GVAR(initializedVehicleClasses) pushBack typeOf _vehicle;
55+
_vehicle call FUNC(addGetInActions);
56+
true
4957
}
5058
}

0 commit comments

Comments
 (0)