diff --git a/addons/quickmount/CfgVehicles.hpp b/addons/quickmount/CfgVehicles.hpp index afab89565e4..b0870c6f40e 100644 --- a/addons/quickmount/CfgVehicles.hpp +++ b/addons/quickmount/CfgVehicles.hpp @@ -35,15 +35,17 @@ class CfgVehicles { }; }; +// because Get In action has its own statement +// we have to cache subactions in args and reuse them in insertChildren code #define GETIN_ACTIONS \ class ACE_Actions { \ class ACE_MainActions { \ class GVAR(GetIn) { \ displayName = "$STR_rscMenu.hppRscGroupRootMenu_Items_GetIn1"; \ - condition = QUOTE(call DFUNC(canShowFreeSeats)); \ + condition = QUOTE(call DFUNC(canShowFreeSeats) && {_actionParams set [ARR_2(0,call DFUNC(addFreeSeatsActions))];_actionParams select 0 isNotEqualTo []}); \ statement = QUOTE(call DFUNC(getInNearest)); \ exceptions[] = {"isNotSwimming"}; \ - insertChildren = QUOTE((_this select 2) param [ARR_2(0,[])]); \ + insertChildren = QUOTE(_actionParams param [ARR_2(0,[])]); \ }; \ }; \ }; \ diff --git a/addons/quickmount/XEH_PREP.hpp b/addons/quickmount/XEH_PREP.hpp index 5b0ec5a0425..5dd76c020af 100644 --- a/addons/quickmount/XEH_PREP.hpp +++ b/addons/quickmount/XEH_PREP.hpp @@ -1,4 +1,7 @@ PREP(addFreeSeatsActions); +PREP(addGetInActions); PREP(canShowFreeSeats); PREP(getInNearest); +PREP(getSeatProxies); +PREP(getSeatUnit); PREP(moduleInit); diff --git a/addons/quickmount/XEH_postInitClient.sqf b/addons/quickmount/XEH_postInitClient.sqf index 30f655edd82..dc278a6c284 100644 --- a/addons/quickmount/XEH_postInitClient.sqf +++ b/addons/quickmount/XEH_postInitClient.sqf @@ -7,3 +7,5 @@ if (!hasInterface) exitWith {}; [] call FUNC(getInNearest); }; }] call CBA_fnc_addKeybind; + +GVAR(initializedVehicleClasses) = []; diff --git a/addons/quickmount/dev/drawSeatProxies.sqf b/addons/quickmount/dev/drawSeatProxies.sqf new file mode 100644 index 00000000000..de7de712a0f --- /dev/null +++ b/addons/quickmount/dev/drawSeatProxies.sqf @@ -0,0 +1,73 @@ +#include "..\script_component.hpp" + +// call compileScript ["z\ace\addons\quickmount\dev\drawSeatProxies.sqf"] +// [_showSingleProxies, _minProxyDistance, _showAnyProxy] call compileScript ["z\ace\addons\quickmount\dev\drawSeatProxies.sqf"] + +params [["_showSingleProxies", false], ["_minProxyDistance", 0.1], ["_showAnyProxy", false]]; + +GVAR(showSingleProxies) = _showSingleProxies; // show seats that have only one proxy point +GVAR(minProxyDistance) = _minProxyDistance; // minimum distance between nearby proxies to show both +GVAR(showAnyProxy) = _showAnyProxy; // show any proxy not only seat + +if (!isNil QGVAR(draw3DEH)) then { + removeMissionEventHandler ["Draw3D", GVAR(draw3DEH)]; +}; + +GVAR(draw3DEH) = addMissionEventHandler ["Draw3D", { + { + private _vehicle = _x; + if (!(_vehicle isKindOf "AllVehicles") || {_vehicle isKindOf "Man"}) then {continue}; + + // make unique var name to recalc proxies + private _seatProxies = _vehicle getVariable format ["%1%2", QGVAR(seatProxies), GVAR(draw3DEH)]; + if (isNil "_seatProxies") then { + // see fnc_getSeatProxies + _seatProxies = createHashMap; + private _proxyRolePrefixes = ["driver", "gunner", "commander", "cargo", "pilot"]; + private _proxyRoleMappings = ["driver", "gunner", "commander", "cargo", "driver", ""]; + private _allLODsNumbers = allLODs _vehicle apply {_x select 2}; + { + private _lodNumber = _x; + private _proxyPaths = _vehicle selectionNames _lodNumber select { + _x select [0,6] == "proxy:" + }; + private _color = [random 1, random 1, random 1, 1]; + { + private _proxyPath = _x; + private _substrings = _proxyPath splitString ":\."; + if (count _substrings < 3) then {continue}; + private _proxyIndex = parseNumber (_substrings select -1); + if (_proxyIndex < 1) then {continue}; + private _proxyRole = toLower (_substrings select -2); + private _proxyGroup = _proxyRoleMappings select (_proxyRolePrefixes findIf {_proxyRole find _x == 0}); + if (_proxyGroup == "" && {!GVAR(showAnyProxy)}) then {continue}; + + private _proxyHashMap = _seatProxies getOrDefault [_proxyGroup, createHashMap, true]; + private _proxies = _proxyHashMap getOrDefault [_proxyIndex, [], true]; + private _pos = _vehicle selectionPosition [_proxyPath, _lodNumber, "AveragePoint"]; + // skip nearby proxies + if (-1 < _proxies findIf {GVAR(minProxyDistance) > _vehicle selectionPosition (_x select 0) vectorDistance _pos}) then {continue}; + _proxies pushBack [[_proxyPath, _lodNumber, "AveragePoint"], format ["%1:%2.%3", _lodNumber, _proxyRole, _proxyIndex], _color]; + } forEach _proxyPaths; + } forEach _allLODsNumbers; + _vehicle setVariable [format ["%1%2", QGVAR(seatProxies), GVAR(draw3DEH)], _seatProxies]; + }; + + { + { + if (!GVAR(showSingleProxies) && {count _y < 2}) then {continue}; // skip seats with only one proxy + { + drawIcon3D [ + "\a3\ui_f\data\gui\cfg\hints\icon_text\group_1_ca.paa", + _x select 2, + _vehicle modelToWorldVisual (_vehicle selectionPosition (_x select 0)), + 0.5, 0.5, 0, + _x select 1 + ]; + } forEach _y; + } forEach _y; + } forEach _seatProxies; + } forEach [cursorObject, objectParent ACE_player]; +}]; + +GVAR(draw3DEH) diff --git a/addons/quickmount/functions/fnc_addFreeSeatsActions.sqf b/addons/quickmount/functions/fnc_addFreeSeatsActions.sqf index 7f020edf9db..a510b24999b 100644 --- a/addons/quickmount/functions/fnc_addFreeSeatsActions.sqf +++ b/addons/quickmount/functions/fnc_addFreeSeatsActions.sqf @@ -18,14 +18,6 @@ #define TAKEN_SEAT_TIMEOUT 0.5 -#define ICON_DRIVER "A3\ui_f\data\IGUI\RscIngameUI\RscUnitInfo\role_driver_ca.paa" -#define ICON_PILOT "A3\ui_f\data\IGUI\Cfg\Actions\getinpilot_ca.paa" -#define ICON_CARGO "A3\ui_f\data\IGUI\RscIngameUI\RscUnitInfo\role_cargo_ca.paa" -#define ICON_GUNNER "A3\ui_f\data\IGUI\Cfg\Actions\getingunner_ca.paa" -#define ICON_COMMANDER "A3\ui_f\data\IGUI\RscIngameUI\RscUnitInfo\role_commander_ca.paa" -#define ICON_TURRET "A3\ui_f\data\IGUI\RscIngameUI\RscUnitInfo\role_gunner_ca.paa" -#define ICON_FFV "A3\ui_f\data\IGUI\Cfg\CrewAimIndicator\gunnerAuto_ca.paa" - #define TO_COMPARTMENT_STRING(var) if !(var isEqualType "") then {var = format [ARR_2("Compartment%1",var)]} // if unit isn't moved to new seat in TAKEN_SEAT_TIMEOUT, we move him back to his seat diff --git a/addons/quickmount/functions/fnc_addGetInActions.sqf b/addons/quickmount/functions/fnc_addGetInActions.sqf new file mode 100644 index 00000000000..7af829b76fc --- /dev/null +++ b/addons/quickmount/functions/fnc_addGetInActions.sqf @@ -0,0 +1,166 @@ +#include "..\script_component.hpp" +/* + * Author: Dystopian + * Adds Get In actions for vehicle seats and Passenger Actions for vehicle crew. + * + * Arguments: + * 0: Vehicle + * + * Return Value: + * None + * + * Example: + * cursorObject call ace_quickmount_fnc_addGetInActions + * + * Public: No + */ + +#define ACTION_DISTANCE 4 +#define CREW_HEIGHT_ABOVE_SEAT 0.3 + +params ["_vehicle"]; + +private _vehicleClass = typeOf _vehicle; +private _vehicleConfig = configOf _vehicle; +private _cargoProxyIndexes = getArray (_vehicleConfig >> "cargoProxyIndexes"); +private _seatProxies = _vehicle call FUNC(getSeatProxies); + +private _seatPositions = GETVAR(_vehicle,GVAR(seatPositions),[]); +if (_seatPositions isEqualType []) then { + _seatPositions = createHashMapFromArray _seatPositions; +}; +private _seatPositionsConfig = getArray (_vehicleConfig >> QGVAR(seatPositions)); +_seatPositionsConfig params [["_model", ""], ["_seatPositionsArray", []]]; +// handle inherited classes with different model +if (_model == getText (_vehicleConfig >> "model")) then { + _seatPositions merge createHashMapFromArray _seatPositionsArray; +}; + +TRACE_4("addGetInActions",_vehicleClass,_cargoProxyIndexes,_seatPositions,_seatProxies); + +private _conditionCrew = { + call FUNC(canShowFreeSeats) + && {!isNull ([_target, _actionParams select 0] call FUNC(getSeatUnit))} +}; +private _insertChildrenCrew = { + private _unit = [_target, _actionParams select 0] call FUNC(getSeatUnit); + [nil, nil, _unit] call EFUNC(interaction,addPassengerActions) +}; +private _modifierFunctionCrew = { + params ["_target", "", "_params", "_actionData"]; + _params params ["_seat"]; + private _unit = [_target, _seat] call FUNC(getSeatUnit); + if (isNull _unit) exitWith {}; // skip empty seats + _actionData set [1, [_unit, true] call EFUNC(common,getName)]; + if (["ace_medical_gui"] call EFUNC(common,isModLoaded)) then { + _this set [0, _unit]; // == _target + call EFUNC(medical_gui,modifyActionTriageLevel); + }; +}; + +private _allSeats = fullCrew [_vehicle, "", true]; +private _cargoPositionNumber = -1; + +{ + _x params ["", "_role", "_cargoIndex", "_turretPath", "_isPersonTurret", "", "_positionName"]; + private _name = if (isLocalized _positionName) then {localize _positionName} else {_positionName}; + + private ["_proxyGroup", "_proxyIndex", "_icon", "_condition", "_statement", "_params", "_position", "_positionCrew"]; + switch (_role) do { + case "driver": { + if ( + unitIsUAV _vehicle + || {getNumber (_vehicleConfig >> "hasDriver") == 0} + ) then {continue}; + _proxyGroup = "driver"; + _proxyIndex = 1; + _params = [_turretPath]; + _condition = { + call FUNC(canShowFreeSeats) + && {!lockedDriver _target} + && {!alive driver _target} + }; + _statement = {_player action ["GetInDriver", _target]}; + _icon = [ICON_DRIVER, ICON_PILOT] select (_vehicle isKindOf "Air"); + }; + case "cargo": { + INC(_cargoPositionNumber); + _proxyGroup = "cargo"; + _proxyIndex = _cargoProxyIndexes param [_cargoPositionNumber, _cargoPositionNumber + 1]; + _params = [_cargoIndex, _cargoPositionNumber]; + _condition = { + call FUNC(canShowFreeSeats) + && { + private _cargoIndex = _actionParams select 0; + !(_target lockedCargo _cargoIndex) + && {!alive ([_target, _cargoIndex] call FUNC(getSeatUnit))} + } + }; + _statement = {_player action ["GetInCargo", _target, _actionParams select 1]}; + _name = format ["%1 %2", _name, _cargoPositionNumber + 1]; + _icon = ICON_CARGO; + }; + default { + if (_role == "gunner" && {unitIsUAV _vehicle}) then {continue}; + private _turretConfig = [_vehicleConfig, _turretPath] call CBA_fnc_getTurret; + private _proxyType = getText (_turretConfig >> "proxyType"); + _proxyGroup = switch (_proxyType) do { + case "CPCommander": {"commander"}; + case "CPGunner": {"gunner"}; + default {"cargo"}; + }; + _proxyIndex = getNumber (_turretConfig >> "proxyIndex"); + _params = [_turretPath]; + _condition = { + call FUNC(canShowFreeSeats) + && { + private _turretPath = _actionParams select 0; + !(_target lockedTurret _turretPath) + && {!alive ([_target, _turretPath] call FUNC(getSeatUnit))} + } + }; + _statement = {_player action ["GetInTurret", _target, _actionParams select 0]}; + _icon = switch true do { + case (getNumber (_turretConfig >> "isCopilot") > 0): {ICON_PILOT}; + case (_role == "gunner"): {ICON_GUNNER}; + case (_role == "commander"): {ICON_COMMANDER}; + case (_isPersonTurret): {ICON_FFV}; + case (getText (_turretConfig >> "gun") == ""): {ICON_CARGO}; + default {ICON_TURRET}; + }; + }; + }; + + private _positionString = _seatPositions getOrDefault [_params select 0, ""]; + if (_positionString != "") then { + _position = compile _positionString; + _positionCrew = compile format ["(%1) vectorAdd [0, 0, %2]", _positionString, CREW_HEIGHT_ABOVE_SEAT]; + TRACE_6("seat position",_role,_cargoIndex,_turretPath,_proxyGroup,_proxyIndex,_position); + } else { + private _seatProxy = _seatProxies getOrDefault [_proxyGroup, createHashMap] getOrDefault [_proxyIndex, []]; + TRACE_6("seat proxy",_role,_cargoIndex,_turretPath,_proxyGroup,_proxyIndex,_seatProxy); + if (_seatProxy isEqualTo []) then {continue}; + // cannot use static position because some proxy positions move with turret rotation + _position = compile format ["_target selectionPosition ['%1', %2, 'AveragePoint']", _seatProxy select 0, _seatProxy select 1]; + _positionCrew = compile format [ + "_target selectionPosition ['%1', %2, 'AveragePoint'] vectorAdd [0, 0, %3]", + _seatProxy select 0, + _seatProxy select 1, + CREW_HEIGHT_ABOVE_SEAT + ]; + }; + + private _action = [ + format ["%1%2%3empty", _role, _cargoIndex, _turretPath], + _name, _icon, _statement, _condition, {}, _params, _position, ACTION_DISTANCE + ] call EFUNC(interact_menu,createAction); + [_vehicleClass, 0, [], _action] call EFUNC(interact_menu,addActionToClass); + + // modifier function needs icon color + private _actionCrew = [ + format ["%1%2%3crew", _role, _cargoIndex, _turretPath], + "", [_icon, "#FFFFFF"], {}, _conditionCrew, _insertChildrenCrew, + _params, _positionCrew, ACTION_DISTANCE, nil, _modifierFunctionCrew + ] call EFUNC(interact_menu,createAction); + [_vehicleClass, 0, [], _actionCrew] call EFUNC(interact_menu,addActionToClass); +} forEach _allSeats; diff --git a/addons/quickmount/functions/fnc_canShowFreeSeats.sqf b/addons/quickmount/functions/fnc_canShowFreeSeats.sqf index 2103e0c9035..a5605d26917 100644 --- a/addons/quickmount/functions/fnc_canShowFreeSeats.sqf +++ b/addons/quickmount/functions/fnc_canShowFreeSeats.sqf @@ -2,11 +2,13 @@ /* * Author: Dystopian * Checks if Free Seats menu can be shown. + * Result is cached for 1 second for reuse in several visible ATM actions. * * Arguments: * 0: Vehicle * 1: Unit * 2: Args + * 3: Use cache (default: true) * * Return Value: * Can show menu @@ -17,10 +19,18 @@ * Public: No */ -params ["_vehicle", "_unit", "_args"]; +params ["_vehicle", "_unit", ["_args", []], ["_useCache", true]]; private _isInVehicle = _unit in _vehicle; +// function is called by multiple actions and MainAction +if (!_isInVehicle && {_useCache}) exitWith { + _this set [3, false]; + [_this, LINKFUNC(canShowFreeSeats), _vehicle, QGVAR(canShowFreeSeats), 1] call EFUNC(common,cachedCall) // return +}; + +TRACE_6("canShowFreeSeats",_vehicle,typeOf _vehicle,_unit,_isInVehicle,_args,_useCache); + GVAR(enabled) && { GVAR(enableMenu) == 3 @@ -28,23 +38,21 @@ GVAR(enabled) || {!_isInVehicle && {GVAR(enableMenu) == 1}} } && {alive _vehicle} -&& {2 > locked _vehicle} +&& {locked _vehicle < 2} && {isNull getConnectedUAVUnit _unit} && {simulationEnabled _vehicle} +&& {[_unit, _vehicle] call EFUNC(interaction,canInteractWithVehicleCrew)} && { - [_unit, _vehicle] call EFUNC(interaction,canInteractWithVehicleCrew) -} -&& { - 0.3 < vectorUp _vehicle select 2 // moveIn* and GetIn* don't work for flipped vehicles + vectorUp _vehicle select 2 > 0.3 // moveIn* and GetIn* don't work for flipped vehicles || {_vehicle isKindOf "Air"} // except Air } && { _isInVehicle + || {typeOf _vehicle in GVAR(initializedVehicleClasses)} || { - // because Get In action has its own statement - // we have to cache subactions in args and reuse them in insertChildren code - private _subActions = call FUNC(addFreeSeatsActions); - _args set [0, _subActions]; - [] isNotEqualTo _subActions + // init vehicle actions here to skip useless checks on clients with disabled quickmount + GVAR(initializedVehicleClasses) pushBack typeOf _vehicle; + _vehicle call FUNC(addGetInActions); + true } } diff --git a/addons/quickmount/functions/fnc_getSeatProxies.sqf b/addons/quickmount/functions/fnc_getSeatProxies.sqf new file mode 100644 index 00000000000..99c5a8ac044 --- /dev/null +++ b/addons/quickmount/functions/fnc_getSeatProxies.sqf @@ -0,0 +1,49 @@ +#include "..\script_component.hpp" +/* + * Author: Dystopian + * Returns vehicle seat proxies grouped by role and index. + * + * Arguments: + * 0: Vehicle + * + * Return Value: + * Seat proxies + * + * Example: + * cursorObject call ace_quickmount_fnc_getSeatProxies + * + * Public: No + */ + +params ["_vehicle"]; + +private _seatProxies = createHashMap; +private _allProxyPaths = []; +private _proxyRolePrefixes = ["driver", "gunner", "commander", "cargo", "pilot"]; +private _proxyRoleMappings = ["driver", "gunner", "commander", "cargo", "driver", ""]; +private _allLODsNumbers = allLODs _vehicle apply {_x select 2}; + +{ + private _lodNumber = _x; + private _proxyPathsUnique = _vehicle selectionNames _lodNumber select { + _x select [0,6] == "proxy:" + && {_allProxyPaths pushBackUnique _x > -1} + }; + { + private _proxyPath = _x; // ex. "proxy:\ca\temp\proxies\suv\cargo02.005" + private _substrings = _proxyPath splitString ":\."; + if (count _substrings < 3) then {continue}; + private _proxyIndex = parseNumber (_substrings select -1); // "005" -> 5 + if (_proxyIndex < 1) then {continue}; + private _proxyRole = toLower (_substrings select -2); // "cargo02" + // match role by prefix + private _proxyGroup = _proxyRoleMappings select (_proxyRolePrefixes findIf {_proxyRole find _x == 0}); // "cargo" + if (_proxyGroup == "") then {continue}; + TRACE_5("proxy",_lodNumber,_proxyPath,_proxyIndex,_proxyRole,_proxyGroup); + + private _proxyHashMap = _seatProxies getOrDefault [_proxyGroup, createHashMap, true]; + _proxyHashMap set [_proxyIndex, [_proxyPath, _lodNumber], true]; + } forEach _proxyPathsUnique; +} forEach _allLODsNumbers; + +_seatProxies diff --git a/addons/quickmount/functions/fnc_getSeatUnit.sqf b/addons/quickmount/functions/fnc_getSeatUnit.sqf new file mode 100644 index 00000000000..0516d3972e9 --- /dev/null +++ b/addons/quickmount/functions/fnc_getSeatUnit.sqf @@ -0,0 +1,31 @@ +#include "..\script_component.hpp" +/* + * Author: Dystopian + * Returns unit occupying specified vehicle seat. + * Unit list is cached for 1 second. + * + * Arguments: + * 0: Vehicle + * 1: Seat (Cargo Index or Turret Path) + * + * Return Value: + * Unit or objNull if seat is empty + * + * Example: + * [cursorObject, []] call ace_quickmount_fnc_getSeatUnit + * + * Public: No + */ + +params ["_vehicle", "_seat"]; + +private _seatUnits = [_this, { + params ["_vehicle", "_seat"]; + createHashMapFromArray (fullCrew _vehicle apply { + _x params ["_unit", "_role", "_cargoIndex", "_turretPath"]; + private _seatId = [_turretPath, _cargoIndex] select (_role isEqualTo "cargo"); + [_seatId, _unit] + }) +}, _vehicle, QGVAR(seatUnits), 1] call EFUNC(common,cachedCall); + +_seatUnits getOrDefault [_seat, objNull] diff --git a/addons/quickmount/script_component.hpp b/addons/quickmount/script_component.hpp index a0ec1debd0e..52b40119587 100644 --- a/addons/quickmount/script_component.hpp +++ b/addons/quickmount/script_component.hpp @@ -19,3 +19,11 @@ #define DEFAULT_DISTANCE 3 #define DEFAULT_SPEED 18 #define DEFAULT_PRIORITY 0 + +#define ICON_DRIVER "A3\ui_f\data\IGUI\RscIngameUI\RscUnitInfo\role_driver_ca.paa" +#define ICON_PILOT "A3\ui_f\data\IGUI\Cfg\Actions\getinpilot_ca.paa" +#define ICON_CARGO "A3\ui_f\data\IGUI\RscIngameUI\RscUnitInfo\role_cargo_ca.paa" +#define ICON_GUNNER "A3\ui_f\data\IGUI\Cfg\Actions\getingunner_ca.paa" +#define ICON_COMMANDER "A3\ui_f\data\IGUI\RscIngameUI\RscUnitInfo\role_commander_ca.paa" +#define ICON_TURRET "A3\ui_f\data\IGUI\RscIngameUI\RscUnitInfo\role_gunner_ca.paa" +#define ICON_FFV "A3\ui_f\data\IGUI\Cfg\CrewAimIndicator\gunnerAuto_ca.paa"