From 1679273b67ef9256cfb8810285a3cbdd73fe21f0 Mon Sep 17 00:00:00 2001 From: Ampersand Date: Wed, 8 Mar 2023 00:28:49 -0500 Subject: [PATCH] AI Control Keybinds (#496) Add keybinds for controlling AI near players - Watch Cursor: Makes all selected units watch the cursor location. Pressing this command with the cursor over a selected unit will cancel the watch target - Watch Curator Camera: Makes all selected units watch the curator camera position, useful when camera is near players or making AI look up - Move To Cursor: Makes all selected units move to the cursor location. Units will return to formation and waypoint afterward - Toggle AI PATH: Makes all selected units start or stop moving. Does not affect aiming or shooting - Force Fire: Makes selected units fire their current or their vehicle turret's weapon Keybinds are UNBOUND by default Co-authored-by: mharis001 <34453221+mharis001@users.noreply.github.com> Co-authored-by: Neil Evers Co-authored-by: mharis001 --- addons/ai/functions/fnc_suppressiveFire.sqf | 61 +----- addons/common/XEH_PREP.hpp | 6 + addons/common/XEH_postInit.sqf | 2 + addons/common/functions/fnc_canFire.sqf | 43 ++++ addons/common/functions/fnc_drawHint.sqf | 198 ++++++++++++++++++ addons/common/functions/fnc_fireWeapon.sqf | 78 +++++++ addons/common/functions/fnc_forceFire.sqf | 55 +++++ addons/common/functions/fnc_getAllTurrets.sqf | 4 +- .../functions/fnc_getEffectiveGunner.sqf | 40 ++++ addons/common/functions/fnc_isReloading.sqf | 38 ++++ addons/common/functions/fnc_isUnitFFV.sqf | 1 + addons/editor/initKeybinds.sqf | 150 +++++++++++-- addons/editor/stringtable.xml | 118 +++++++---- 13 files changed, 674 insertions(+), 120 deletions(-) create mode 100644 addons/common/functions/fnc_canFire.sqf create mode 100644 addons/common/functions/fnc_drawHint.sqf create mode 100644 addons/common/functions/fnc_fireWeapon.sqf create mode 100644 addons/common/functions/fnc_forceFire.sqf create mode 100644 addons/common/functions/fnc_getEffectiveGunner.sqf create mode 100644 addons/common/functions/fnc_isReloading.sqf diff --git a/addons/ai/functions/fnc_suppressiveFire.sqf b/addons/ai/functions/fnc_suppressiveFire.sqf index 635317ffb..ef06f265a 100644 --- a/addons/ai/functions/fnc_suppressiveFire.sqf +++ b/addons/ai/functions/fnc_suppressiveFire.sqf @@ -51,25 +51,10 @@ if (_unit isEqualType grpNull) exitWith { }; // If a vehicle is given directly, use its gunner as the unit -if !(_unit isKindOf "CAManBase") then { - _unit = gunner _unit; -}; - -if ( - !alive _unit - || {isPlayer _unit} - || {!(lifeState _unit in ["HEALTHY", "INJURED"])} - || {_unit getVariable [QGVAR(isSuppressing), false]} - || { - private _vehicle = vehicle _unit; +private _unit = _unit call EFUNC(common,getEffectiveGunner); - if (_vehicle == _unit || {_unit call EFUNC(common,isUnitFFV)}) then { - currentWeapon _unit == "" - } else { - _vehicle weaponsTurret (_vehicle unitTurret _unit) isEqualTo [] - }; - } -) exitWith {}; +// Exit if the unit cannot fire or is already suppressing +if (!([_unit, true, true] call EFUNC(common,canFire)) || {_unit getVariable [QGVAR(isSuppressing), false]}) exitWith {}; // Prevent the unit from performing other suppressive fire tasks while this one is active _unit setVariable [QGVAR(isSuppressing), true, true]; @@ -177,47 +162,11 @@ private _endTime = CBA_missionTime + _duration + TARGETING_DELAY; }; if (CBA_missionTime >= _shotTime) then { + [_unit, true] call EFUNC(common,fireWeapon); + private _vehicle = vehicle _unit; private _turretPath = _vehicle unitTurret _unit; - switch (true) do { - // On foot - case (_vehicle == _unit): { - weaponState _unit params ["_weapon", "_muzzle", "_fireMode"]; - - _unit setAmmo [_weapon, 1e6]; - _unit forceWeaponFire [_muzzle, _fireMode]; - }; - - // FFV - case (_unit call EFUNC(common,isUnitFFV)): { - // Using UseMagazine action since forceWeaponFire command does not work for FFV units - // UseMagazine action doesn't seem to work with currently loaded magazine (currentMagazineDetail) - // Therefore, this relies on the unit having an extra magazine in their inventory - // but should be fine in most situations - private _weapon = currentWeapon _unit; - private _compatibleMagazines = _weapon call CBA_fnc_compatibleMagazines; - private _index = magazines _unit findAny _compatibleMagazines; - if (_index == -1) exitWith {}; - - private _magazine = magazinesDetail _unit select _index; - _magazine call EFUNC(common,parseMagazineDetail) params ["_id", "_owner"]; - - _unit setAmmo [_weapon, 1e6]; - CBA_logic action ["UseMagazine", _unit, _unit, _owner, _id]; - }; - - // Vehicle gunner - default { - private _muzzle = weaponState [_vehicle, _turretPath] select 1; - _unit setAmmo [_muzzle, 1e6]; - - private _magazine = _vehicle currentMagazineDetailTurret _turretPath; - _magazine call EFUNC(common,parseMagazineDetail) params ["_id", "_owner"]; - _vehicle action ["UseMagazine", _vehicle, _unit, _owner, _id]; - }; - }; - // Set time until the next shot based on the weapon's ammo reloading time and whether the current burst is finished private _reloadTime = [_vehicle, _turretPath] call EFUNC(common,getWeaponReloadTime); _currentBurstRounds = _currentBurstRounds + 1; diff --git a/addons/common/XEH_PREP.hpp b/addons/common/XEH_PREP.hpp index 1269314cd..c98afd338 100644 --- a/addons/common/XEH_PREP.hpp +++ b/addons/common/XEH_PREP.hpp @@ -1,3 +1,4 @@ +PREP(canFire); PREP(changeGroupSide); PREP(collapseTree); PREP(createZeus); @@ -6,6 +7,7 @@ PREP(deserializeInventory); PREP(deserializeObjects); PREP(displayCuratorLoad); PREP(displayCuratorUnload); +PREP(drawHint); PREP(dumpPerformanceCounters); PREP(earthquake); PREP(ejectPassengers); @@ -13,6 +15,8 @@ PREP(exportMissionSQF); PREP(exportText); PREP(fireArtillery); PREP(fireVLS); +PREP(fireWeapon); +PREP(forceFire); PREP(formatDegrees); PREP(getActiveTree); PREP(getAllTurrets); @@ -20,6 +24,7 @@ PREP(getArtilleryETA); PREP(getCargoPositionsCount); PREP(getDefaultInventory); PREP(getDLC); +PREP(getEffectiveGunner); PREP(getLightingSelections); PREP(getPhoneticName); PREP(getPlayers); @@ -42,6 +47,7 @@ PREP(initSliderEdit); PREP(isCursorOnMouseArea); PREP(isInScreenshotMode); PREP(isPlacementActive); +PREP(isReloading); PREP(isRemoteControlled); PREP(isSwimming); PREP(isUnitFFV); diff --git a/addons/common/XEH_postInit.sqf b/addons/common/XEH_postInit.sqf index f00ef720b..799aa67c5 100644 --- a/addons/common/XEH_postInit.sqf +++ b/addons/common/XEH_postInit.sqf @@ -319,6 +319,8 @@ [QGVAR(earthquake), LINKFUNC(earthquake)] call CBA_fnc_addEventHandler; [QGVAR(fireArtillery), LINKFUNC(fireArtillery)] call CBA_fnc_addEventHandler; +[QGVAR(fireWeapon), LINKFUNC(fireWeapon)] call CBA_fnc_addEventHandler; +[QGVAR(forceFire), LINKFUNC(forceFire)] call CBA_fnc_addEventHandler; [QGVAR(setLampState), LINKFUNC(setLampState)] call CBA_fnc_addEventHandler; [QGVAR(setMagazineAmmo), LINKFUNC(setMagazineAmmo)] call CBA_fnc_addEventHandler; [QGVAR(setTurretAmmo), LINKFUNC(setTurretAmmo)] call CBA_fnc_addEventHandler; diff --git a/addons/common/functions/fnc_canFire.sqf b/addons/common/functions/fnc_canFire.sqf new file mode 100644 index 000000000..0c1d333d6 --- /dev/null +++ b/addons/common/functions/fnc_canFire.sqf @@ -0,0 +1,43 @@ +#include "script_component.hpp" +/* + * Author: mharis001, Ampersand + * Checks if the given unit or vehicle can fire its current weapon. + * + * Arguments: + * 0: Unit or Vehicle + * 1: Ignore Ammo (default: false) + * 2: Ignore Reload (default: false) + * + * Return Value: + * Can Fire + * + * Example: + * [_unit] call zen_common_fnc_canFire + * + * Public: No + */ + +params [["_unit", objNull, [objNull]], ["_ignoreAmmo", false, [false]], ["_ignoreReload", false, [false]]]; + +private _unit = _unit call FUNC(getEffectiveGunner); + +alive _unit +&& {!isPlayer _unit} +&& {lifeState _unit in ["HEALTHY", "INJURED"]} +&& { + private _vehicle = vehicle _unit; + + if (_vehicle == _unit || {_unit call FUNC(isUnitFFV)}) then { + currentWeapon _unit != "" + && {_ignoreAmmo || {_unit ammo currentMuzzle _unit > 0}} + && {_ignoreReload || {!(_unit call FUNC(isReloading))}} + } else { + private _turretPath = _vehicle unitTurret _unit; + weaponState [_vehicle, _turretPath] params ["_weapon", "", "", "", "_ammoCount"]; + + _weapon != "" + && {!("fake" in toLower _weapon)} + && {_ignoreAmmo || {_ammoCount > 0} || {_weapon isKindOf ["CarHorn", configFile >> "CfgWeapons"]}} + && {_ignoreReload || {!([_vehicle, _turretPath] call FUNC(isReloading))}} + }; +} diff --git a/addons/common/functions/fnc_drawHint.sqf b/addons/common/functions/fnc_drawHint.sqf new file mode 100644 index 000000000..448364b68 --- /dev/null +++ b/addons/common/functions/fnc_drawHint.sqf @@ -0,0 +1,198 @@ +#include "script_component.hpp" +/* + * Author: Ampersand, mharis001 + * Draws a hint that contains icon and line elements in 2D (Zeus display map) + * and 3D (in world) for the given duration. + * + * Will overwrite an existing hint when called using the same ID. Position + * arguments can be given as OBJECTs, in which case the hint elements will + * follow objects as they move and will be hidden if the object is deleted. + * + * The visual properties for "ICON" elements are: + * 0: Position + * 1: Icon Texture + * 2: Color (RGBA) (default: [1, 1, 1, 1]) + * 3: Scale (default: 1) + * 4: Angle (default: 0) + * 5: Text (default: "") + * 6: Shadow (default: 0) + * 7: Text Size (default: 0.05) + * 8: Font (default: "RobotoCondensed") + * 9: Align (default: "center") + * + * The visual properties for "LINE" elements are: + * 0: Start Position + * 1: End Position + * 2: Color (RGBA) (default: [1, 1, 1, 1]) + * + * Arguments: + * 0: Elements + * 0: Type + * - either "ICON" or "LINE". + * 1: Visual Properties + * - depends on element type (see above for details). + * 1: Duration (in seconds) + * 2: ID (default: "") + * - an ID is generated when an empty string is given. + * - in the case of an OBJECT, the hash value is used. + * + * Return Value: + * ID + * + * Example: + * [["ICON", [_unit, _texture]], 3] call zen_common_fnc_drawHint + * + * Public: No + */ + +#define MAP_ICON_SIZE 24 + +params [ + ["_elements", [], [[]]], + ["_duration", 0, [0]], + ["_id", "", ["", objNull]] +]; + +private _ctrlMap = findDisplay IDD_RSCDISPLAYCURATOR displayCtrl IDC_RSCDISPLAYCURATOR_MAINMAP; + +// Map of hint IDs and their corresponding draw (2D and 3D) event handler IDs +if (isNil QGVAR(drawHintMap)) then { + GVAR(drawHintMap) = createHashMap; +}; + +// Use an object's hash value as its hint ID +if (_id isEqualType objNull) then { + _id = hashValue _id; +}; + +// Generate a hint ID if one is not given +if (_id isEqualTo "") then { + if (isNil QGVAR(drawHintCounter)) then { + GVAR(drawHintCounter) = -1; + }; + + GVAR(drawHintCounter) = GVAR(drawHintCounter) + 1; + + _id = [CBA_clientID, GVAR(drawHintCounter)] joinString ":"; +}; + +// Remove an existing hint with the same ID +if (_id in GVAR(drawHintMap)) then { + GVAR(drawHintMap) deleteAt _id params ["_id2D", "_id3D"]; + + _ctrlMap ctrlRemoveEventHandler ["Draw", _id2D]; + removeMissionEventHandler ["Draw3D", _id3D]; +}; + +// Validate the given hint elements and separate them by type +private _icons = []; +private _lines = []; + +{ + _x params [["_type", "", [""]], ["_args", [], [[]]]]; + + switch (_type) do { + case "ICON": { + _args params [ + ["_position", [0, 0, 0], [[], objNull], 3], + ["_icon", "", [""]], + ["_color", [1, 1, 1, 1], [[]], 4], + ["_scale", 1, [0]], + ["_angle", 0, [0]], + ["_text", "", [""]], + ["_shadow", 0, [0, false]], + ["_textSize", 0.05, [0]], + ["_font", "RobotoCondensed", [""]], + ["_align", "center", [""]] + ]; + + _icons pushBack [_position, _icon, _color, _scale, _angle, _text, _shadow, _textSize, _font, _align]; + }; + case "LINE": { + _args params [ + ["_begPos", [0, 0, 0], [[], objNull], 3], + ["_endPos", [0, 0, 0], [[], objNull], 3], + ["_color", [1, 1, 1, 1], [[]], 4] + ]; + + _lines pushBack [_begPos, _endPos, _color]; + }; + default { + ERROR_1("Invalid hint element type - %1.",_type); + }; + }; +} forEach _elements; + +// Add event handlers to draw the hint elements +private _fnc_draw2D = { + params ["_ctrlMap"]; + _thisArgs params ["_icons", "_lines"]; + + { + _x params ["_position", "_icon", "_color", "_scale", "_angle", "_text", "_shadow", "_textSize", "_font", "_align"]; + + if (_position isEqualTo objNull) then {continue}; + + _ctrlMap drawIcon [_icon, _color, _position, _scale * MAP_ICON_SIZE, _scale * MAP_ICON_SIZE, _angle, _text, _shadow, _textSize, _font, _align]; + } forEach _icons; + + { + _x params ["_begPos", "_endPos", "_color"]; + + if (objNull in [_begPos, _endPos]) then {continue}; + + _ctrlMap drawLine [_begPos, _endPos, _color]; + } forEach _lines; +}; + +private _fnc_draw3D = { + _thisArgs params ["_icons", "_lines", "_endTime", "_id"]; + + // Exit if the Zeus display is closed or hint duration is complete + if (isNull curatorCamera || {CBA_missionTime >= _endTime}) exitWith { + GVAR(drawHintMap) deleteAt _id params ["_id2D", "_id3D"]; + + private _ctrlMap = findDisplay IDD_RSCDISPLAYCURATOR displayCtrl IDC_RSCDISPLAYCURATOR_MAINMAP; + _ctrlMap ctrlRemoveEventHandler ["Draw", _id2D]; + removeMissionEventHandler ["Draw3D", _id3D]; + }; + + // No 3D drawing needed if the map is visible + if (visibleMap) exitWith {}; + + { + _x params ["_position", "_icon", "_color", "_scale", "_angle", "_text", "_shadow", "_textSize", "_font", "_align"]; + + if (_position isEqualTo objNull) then {continue}; + + if (_position isEqualType objNull) then { + _position = unitAimPositionVisual _position; + }; + + drawIcon3D [_icon, _color, _position, _scale, _scale, _angle, _text, _shadow, _textSize, _font, _align]; + } forEach _icons; + + { + _x params ["_begPos", "_endPos", "_color"]; + + if (objNull in [_begPos, _endPos]) then {continue}; + + if (_begPos isEqualType objNull) then { + _begPos = unitAimPositionVisual _begPos; + }; + + if (_endPos isEqualType objNull) then { + _endPos = unitAimPositionVisual _endPos; + }; + + drawLine3D [_begPos, _endPos, _color]; + } forEach _lines; +}; + +private _args = [_icons, _lines, CBA_missionTime + _duration, _id]; +private _id2D = [_ctrlMap, "Draw", _fnc_draw2D, _args] call CBA_fnc_addBISEventHandler; +private _id3D = [missionNamespace, "Draw3D", _fnc_draw3D, _args] call CBA_fnc_addBISEventHandler; +GVAR(drawHintMap) set [_id, [_id2D, _id3D]]; + +// Return the hint ID (in case a generated one was used) +_id diff --git a/addons/common/functions/fnc_fireWeapon.sqf b/addons/common/functions/fnc_fireWeapon.sqf new file mode 100644 index 000000000..82181c706 --- /dev/null +++ b/addons/common/functions/fnc_fireWeapon.sqf @@ -0,0 +1,78 @@ +#include "script_component.hpp" +/* + * Author: mharis001, Ampersand + * Makes the given unit fire their current or their vehicle turret's weapon. + * + * Arguments: + * 0: Unit + * 1: Infinite Ammo (default: false) + * + * Return Value: + * None + * + * Example: + * [_unit] call zen_common_fnc_fireWeapon + * + * Public: No + */ + +params [["_unit", objNull, [objNull]], ["_infiniteAmmo", false, [false]]]; + +if (!local _unit) exitWith { + [QGVAR(fireWeapon), _this, _unit] call CBA_fnc_targetEvent; +}; + +private _unit = _unit call FUNC(getEffectiveGunner); +private _vehicle = vehicle _unit; + +switch (true) do { + // On foot + case (_vehicle == _unit): { + weaponState _unit params ["_weapon", "_muzzle", "_fireMode"]; + + if (_infiniteAmmo) then { + _unit setAmmo [_weapon, 1e6]; + }; + + _unit forceWeaponFire [_muzzle, _fireMode]; + }; + + // FFV + case (_unit call EFUNC(common,isUnitFFV)): { + // Using UseMagazine action since forceWeaponFire command does not work for FFV units + // UseMagazine action doesn't seem to work with currently loaded magazine (currentMagazineDetail) + // Therefore, this relies on the unit having an extra magazine in their inventory + // but should be fine in most situations + private _weapon = currentWeapon _unit; + private _compatibleMagazines = _weapon call CBA_fnc_compatibleMagazines; + private _index = magazines _unit findAny _compatibleMagazines; + if (_index == -1) exitWith {}; + + private _magazine = magazinesDetail _unit select _index; + _magazine call EFUNC(common,parseMagazineDetail) params ["_id", "_owner"]; + + if (_infiniteAmmo) then { + _unit setAmmo [_weapon, 1e6]; + }; + + CBA_logic action ["UseMagazine", _unit, _unit, _owner, _id]; + }; + + // Vehicle gunner + default { + private _turretPath = _vehicle unitTurret _unit; + weaponState [_vehicle, _turretPath] params ["_weapon", "_muzzle", "_fireMode"]; + + if (_weapon isKindOf ["CarHorn", configFile >> "CfgWeapons"]) exitWith { + _unit forceWeaponFire [_muzzle, _fireMode]; + }; + + if (_infiniteAmmo) then { + _unit setAmmo [_muzzle, 1e6]; + }; + + private _magazine = _vehicle currentMagazineDetailTurret _turretPath; + _magazine call EFUNC(common,parseMagazineDetail) params ["_id", "_owner"]; + _vehicle action ["UseMagazine", _vehicle, _unit, _owner, _id]; + }; +}; diff --git a/addons/common/functions/fnc_forceFire.sqf b/addons/common/functions/fnc_forceFire.sqf new file mode 100644 index 000000000..e55304ec7 --- /dev/null +++ b/addons/common/functions/fnc_forceFire.sqf @@ -0,0 +1,55 @@ +#include "script_component.hpp" +/* + * Author: Ampersand + * Makes the given units or vehicles fire their current weapons. + * + * Arguments: + * 0: Units + * 1: ID + * + * Return Value: + * None + * + * Example: + * [_units, 0] call zen_common_fnc_forceFire + * + * Public: No + */ + +#define FORCE_FIRE_TIMEOUT 10 + +params ["_units", "_id"]; + +if (isNil QGVAR(forceFiringCurators)) then { + GVAR(forceFiringCurators) = []; +}; + +// If no units are given, then stop firing +if (_units isEqualTo []) exitWith { + GVAR(forceFiringCurators) deleteAt (GVAR(forceFiringCurators) find _id); +}; + +// Track which curators are forcing fire on local machine +GVAR(forceFiringCurators) pushBackUnique _id; + +// Repeatedly fire weapons of local units +private _units = _units apply { + _x call EFUNC(common,getEffectiveGunner) +} select { + local _x +}; + +[{ + params ["_args", "_pfhID"]; + _args params ["_units", "_id", "_endTime"]; + + if (CBA_missionTime >= _endTime || {!(_id in GVAR(forceFiringCurators))}) exitWith { + [_pfhID] call CBA_fnc_removePerFrameHandler; + }; + + { + if (_x call EFUNC(common,canFire)) then { + _x call EFUNC(common,fireWeapon); + }; + } forEach _units; +}, 0.05, [_units, _id, CBA_missionTime + FORCE_FIRE_TIMEOUT]] call CBA_fnc_addPerFrameHandler; diff --git a/addons/common/functions/fnc_getAllTurrets.sqf b/addons/common/functions/fnc_getAllTurrets.sqf index 951bd29e9..2ce53487e 100644 --- a/addons/common/functions/fnc_getAllTurrets.sqf +++ b/addons/common/functions/fnc_getAllTurrets.sqf @@ -18,9 +18,9 @@ params ["_vehicle"]; private _turrets = if (_vehicle isEqualType objNull) then { - allTurrets _vehicle; + allTurrets _vehicle } else { - [_vehicle] call BIS_fnc_allTurrets; + [_vehicle] call BIS_fnc_allTurrets }; // Add the driver turret diff --git a/addons/common/functions/fnc_getEffectiveGunner.sqf b/addons/common/functions/fnc_getEffectiveGunner.sqf new file mode 100644 index 000000000..a81242ed3 --- /dev/null +++ b/addons/common/functions/fnc_getEffectiveGunner.sqf @@ -0,0 +1,40 @@ +#include "script_component.hpp" +/* + * Author: Ampersand + * Return the given vehicle's effective gunner (unit seated in a turret with weapons). + * Priority order is the gunner, commander, other turrets (excluding FFV), and the driver. + * + * Arguments: + * 0: Vehicle + * + * Return Value: + * Gunner + * + * Example: + * [_vehicle] call zen_common_fnc_getEffectiveGunner + * + * Public: No + */ + +params [["_vehicle", objNull, [objNull]]]; + +// If a infantry unit is given, return it directly +if (_vehicle isKindOf "CAManBase") exitWith {_vehicle}; + +// Get units in the vehicle occupying turrets with wepaons +private _units = [gunner _vehicle, commander _vehicle]; +_units insert [-1, _vehicle call FUNC(getAllTurrets) apply {_vehicle turretUnit _x}, true]; + +private _index = _units findIf { + alive _x && {_vehicle weaponsTurret (_vehicle unitTurret _x) isNotEqualTo []} +}; + +// Select the first available gunner +// Fall back to effective commander if one in not found and manual fire is enabled +private _gunner = _units param [_index, objNull]; + +if (isNull _gunner && {isManualFire _vehicle}) then { + _gunner = effectiveCommander _vehicle; +}; + +_gunner diff --git a/addons/common/functions/fnc_isReloading.sqf b/addons/common/functions/fnc_isReloading.sqf new file mode 100644 index 000000000..850b8aa6b --- /dev/null +++ b/addons/common/functions/fnc_isReloading.sqf @@ -0,0 +1,38 @@ +#include "script_component.hpp" +/* + * Author: mharis001 + * Checks if the given unit or vehicle's weapon or muzzle is reloading. + * + * Arguments: + * 0: Unit or Vehicle + * 1: Turret Path (default: [0]) + * - Only used if the given entity is a vehicle. + * 2: Weapon or Muzzle (default: "") + * - Use "" for current weapon. + * + * Return Value: + * Is Reloading + * + * Example: + * [_unit, "arifle_MX_F"] call zen_common_fnc_isReloading + * + * Public: No + */ + +params [ + ["_entity", objNull, [objNull]], + ["_turretPath", [0], [[]]], + ["_weaponOrMuzzle", "", [""]] +]; + +// Get given weapon or muzzle's current state +private _state = if (_entity isKindOf "CAManBase") then { + _entity weaponState _weaponOrMuzzle +} else { + weaponState [_entity, _turretPath, _weaponOrMuzzle] +}; + +_state params ["", "", "", "", "", "_roundReloadPhase", "_magazineReloadPhase"]; + +// Is reloading if round or magazine reload phase is > 0 +_roundReloadPhase > 0 || {_magazineReloadPhase > 0} diff --git a/addons/common/functions/fnc_isUnitFFV.sqf b/addons/common/functions/fnc_isUnitFFV.sqf index c26e03245..db4475ba7 100644 --- a/addons/common/functions/fnc_isUnitFFV.sqf +++ b/addons/common/functions/fnc_isUnitFFV.sqf @@ -18,3 +18,4 @@ params [["_unit", objNull, [objNull]]]; fullCrew vehicle _unit select {_x select 0 == _unit} param [0, []] param [4, false] +&& {_unit call CBA_fnc_isTurnedOut} // Tank commander is only FFV when turned out diff --git a/addons/editor/initKeybinds.sqf b/addons/editor/initKeybinds.sqf index c0ad0c57e..3c984fc6d 100644 --- a/addons/editor/initKeybinds.sqf +++ b/addons/editor/initKeybinds.sqf @@ -7,27 +7,7 @@ }; }, {}, [DIK_B, [false, false, false]]] call CBA_fnc_addKeybind; // Default: B -[ELSTRING(main,DisplayName), QGVAR(deployCountermeasures), [LSTRING(DeployCountermeasures), LSTRING(DeployCountermeasures_Description)], { - if (!isNull curatorCamera && {!GETMVAR(RscDisplayCurator_search,false)}) then { - { - [_x] call EFUNC(common,deployCountermeasures); - } forEach SELECTED_OBJECTS; - - true // handled - }; -}, {}, [DIK_C, [true, false, false]]] call CBA_fnc_addKeybind; // Default: SHIFT + C - -[ELSTRING(main,DisplayName), QGVAR(ejectPassengers), [LSTRING(EjectPassengers), LSTRING(EjectPassengers_Description)], { - if (!isNull curatorCamera && {!GETMVAR(RscDisplayCurator_search,false)}) then { - { - [_x] call EFUNC(common,ejectPassengers); - } forEach SELECTED_OBJECTS; - - true // handled, prevents vanilla eject - }; -}, {}, [DIK_G, [false, true, false]]] call CBA_fnc_addKeybind; // Default: CTRL + G - -[ELSTRING(main,DisplayName), QGVAR(unloadViV), [localize "STR_A3_ModuleDepot_Unload", LSTRING(UnloadViV_Description)], { +[ELSTRING(main,DisplayName), QGVAR(unloadViV), ["STR_A3_ModuleDepot_Unload", LSTRING(UnloadViV_Description)], { if (!isNull curatorCamera && {!GETMVAR(RscDisplayCurator_search,false)}) then { { if (isNull isVehicleCargo _x) then { @@ -108,3 +88,131 @@ true // handled }; }, {}, [DIK_R, [true, true, false]]] call CBA_fnc_addKeybind; // Default: CTRL + SHIFT + R + +[[ELSTRING(main,DisplayName), LSTRING(AIControl)], QGVAR(ejectPassengers), [LSTRING(EjectPassengers), LSTRING(EjectPassengers_Description)], { + if (!isNull curatorCamera && {!GETMVAR(RscDisplayCurator_search,false)}) then { + { + [_x] call EFUNC(common,ejectPassengers); + } forEach SELECTED_OBJECTS; + + true // handled, prevents vanilla eject + }; +}, {}, [DIK_G, [false, true, false]]] call CBA_fnc_addKeybind; // Default: CTRL + G + +[[ELSTRING(main,DisplayName), LSTRING(AIControl)], QGVAR(deployCountermeasures), [LSTRING(DeployCountermeasures), LSTRING(DeployCountermeasures_Description)], { + if (!isNull curatorCamera && {!GETMVAR(RscDisplayCurator_search,false)}) then { + { + [_x] call EFUNC(common,deployCountermeasures); + } forEach SELECTED_OBJECTS; + + true // handled + }; +}, {}, [DIK_C, [true, false, false]]] call CBA_fnc_addKeybind; // Default: SHIFT + C + +[[ELSTRING(main,DisplayName), LSTRING(AIControl)], QGVAR(watchCursor), [LSTRING(WatchCursor), LSTRING(WatchCursor_Description)], { + if (!isNull curatorCamera && {!GETMVAR(RscDisplayCurator_search,false)}) then { + curatorMouseOver params ["_type", "_target"]; + + if (_type != "OBJECT") then { + _target = ASLToAGL ([] call EFUNC(common,getPosFromScreen)); + }; + + { + if (!isNull group _x && {!isPlayer _x}) then { + // Cancel if target is self + private _isSelf = _x isEqualTo _target; + private _target = [_target, objNull] select _isSelf; + [QEGVAR(common,doWatch), [[_x, gunner _x], _target], _x] call CBA_fnc_targetEvent; + if (_isSelf) then {continue}; + + [[ + ["ICON", [_target, "\a3\ui_f\data\igui\cfg\simpletasks\types\scout_ca.paa"]], + ["LINE", [_x, _target]] + ], 3, _x] call EFUNC(common,drawHint); + }; + } forEach SELECTED_OBJECTS; + + true // handled + }; +}, {}, [0, [false, false, false]]] call CBA_fnc_addKeybind; // Default: Unbound + +[[ELSTRING(main,DisplayName), LSTRING(AIControl)], QGVAR(watchCuratorCamera), [LSTRING(WatchCuratorCamera), LSTRING(WatchCuratorCamera_Description)], { + if (!isNull curatorCamera && {!GETMVAR(RscDisplayCurator_search,false)}) then { + private _position = ASLToAGL getPosASL curatorCamera; + + { + if (!isNull group _x && {!isPlayer _x}) then { + [QEGVAR(common,doWatch), [_x, _position], _x] call CBA_fnc_targetEvent; + + [[ + ["ICON", [_position, "\a3\ui_f\data\igui\cfg\simpletasks\types\scout_ca.paa"]], + ["LINE", [_x, _position]] + ], 3, _x] call EFUNC(common,drawHint); + }; + } forEach SELECTED_OBJECTS; + + true // handled + }; +}, {}, [0, [false, false, false]]] call CBA_fnc_addKeybind; // Default: Unbound + +[[ELSTRING(main,DisplayName), LSTRING(AIControl)], QGVAR(forceFire), [LSTRING(ForceFire), LSTRING(ForceFire_Description)], { + if (!isNull curatorCamera && {!GETMVAR(RscDisplayCurator_search,false)}) then { + private _units = SELECTED_OBJECTS select {!isPlayer _x && {!isNull group _x}}; + [QEGVAR(common,forceFire), [_units, CBA_clientID]] call CBA_fnc_globalEvent; + + true // handled + }; +}, { + [QEGVAR(common,forceFire), [[], CBA_clientID]] call CBA_fnc_globalEvent; +}, [0, [false, false, false]]] call CBA_fnc_addKeybind; // Default: Unbound + +[[ELSTRING(main,DisplayName), LSTRING(AIControl)], QGVAR(moveToCursor), [LSTRING(MoveToCursor), LSTRING(MoveToCursor_Description)], { + if (!isNull curatorCamera && {!GETMVAR(RscDisplayCurator_search,false)}) then { + private _position = ASLToAGL ([] call EFUNC(common,getPosFromScreen)); + + { + if (!isNull driver _x && {!isPlayer _x}) then { + [QEGVAR(common,doMove), [_x, _position], _x] call CBA_fnc_targetEvent; + + [[ + ["ICON", [_position, "\a3\ui_f\data\igui\cfg\simpletasks\types\walk_ca.paa"]], + ["LINE", [_x, _position]] + ], 3, _x] call EFUNC(common,drawHint); + }; + } forEach SELECTED_OBJECTS; + + true // handled + }; +}, {}, [0, [false, false, false]]] call CBA_fnc_addKeybind; // Default: Unbound + +[[ELSTRING(main,DisplayName), LSTRING(AIControl)], QGVAR(toggleAIPATH), [LSTRING(ToggleAIPATH), LSTRING(ToggleAIPATH_Description)], { + if (!isNull curatorCamera && {!GETMVAR(RscDisplayCurator_search,false)}) then { + private _enabled = 0; + private _disabled = 0; + + { + if (!isPlayer _x && {_x == vehicle _x || {_x == driver vehicle _x}}) then { + private _isPathEnabled = _x checkAIFeature "PATH"; + private _eventName = [QEGVAR(common,enableAI), QEGVAR(common,disableAI)] select _isPathEnabled; + [_eventName, [_x, "PATH"], _x] call CBA_fnc_globalEvent; + + if (_isPathEnabled) then { + _disabled = _disabled + 1; + } else { + _enabled = _enabled + 1; + }; + }; + } forEach SELECTED_OBJECTS; + + [ + "%1 - %2: %3 - %4: %5", + LLSTRING(AIPathToggled), + LELSTRING(common,Enabled), + _enabled, + LELSTRING(common,Disabled), + _disabled + ] call EFUNC(common,showMessage); + + true // handled + }; +}, {}, [0, [false, false, false]]] call CBA_fnc_addKeybind; // Default: Unbound diff --git a/addons/editor/stringtable.xml b/addons/editor/stringtable.xml index 74734f6fb..f112e7030 100644 --- a/addons/editor/stringtable.xml +++ b/addons/editor/stringtable.xml @@ -220,27 +220,6 @@ 切換包含乘員 Toggle Includi equipaggio - - Eject Passengers - Ejecter les passagers - Passagiere rauswerfen - Wysadź pasażerów - 後部座席を脱出 - 승객 탈출 - 弹出乘客 - 彈出乘客 - Espellere i passeggeri - - - Ejects the passengers of all selected vehicles. If the vehicle is currently flying, the units will be given parachutes.\nUnlike vanilla eject, units will disembark one after another (not immediately, with a short delay in-between). - Éjecte les passagers de tous les véhicules sélectionnés. Si le véhicule est actuellement en vol, les unités recevront des parachutes. Contrairement à l'éjection standard, les unités débarqueront les unes après les autres (pas immédiatement, avec un court délai entre les deux). - Wirft die Fahrgäste aller ausgewählten Fahrzeuge raus. Wenn das Fahrzeug gerade fliegt, erhalten die Einheiten Fallschirme.\nIm Gegensatz zu "Vanilla" sitzen die Einheiten nacheinander ab (nicht alle sofort, sondern mit einer kurzen Verzögerung dazwischen). - Wysadza pasażerów ze wszystkich wybranych pojazdów. Jeżeli pojazd jest w powietrzu, pasażerowie dostaną spadochron.\nJednostki zostaną wysadzone jedna po drugiej (nie wszystkie na raz). - 選択された全車両から後部座席のユニットを脱出させます。もし車両が飛行中の場合はパラシュートが与えられます。\nなお脱出は一人づつ降りるようになっています (即時ではなく数秒づつ)。 - 弹出所有选定载具的乘客。如果载具正在飞行,这些单位将获得降落伞。与原版弹射不同的是,单位将一个接一个地离开(而不是立即离开,中间会有短暂间隙)。 - 彈出所有選定載具的乘客。如果載具正在飛行,這些單位將獲得降落傘。與原版彈射不同的是,單位將一個接一個地離開(而不是立即離開,中間會有短暫間隙)。 - Espelle i passeggeri di tutti i veicoli selezionati. Se il veicolo è attualmente in volo, alle unità verranno forniti paracadute.\nA differenza dell'espulsione vaniglia, le unità sbarcheranno una dopo l'altra (non immediatamente, con un breve ritardo nel mezzo). - Unloads all selected vehicle-in-vehicle cargo and carriers. A vehicle that is both cargo and carrying cargo will only be unloaded from its carrier. Entladet alle ausgewählte Fahrzeug-in-Fahrzeug Fracht und Transporter. Fahrzeuge, die sowohl Fracht als auch Transporter sind, werden nur als Fracht entladen. @@ -249,26 +228,6 @@ 卸下所有選定的載具中的貨物和車輛。既有貨物又有運載車輛的載具將只卸下貨物。 Scarica tutte le merci e i vettori selezionati veicolo-in-veicolo. Un veicolo che è sia un carico che un carico verrà scaricato solo dal suo vettore. - - Deploy Countermeasures - Déployer des contre-mesures - Gegenmaßnahmen einleiten - 欺瞞装置作動 - Odpal środki przeciw namierzające - 发射反制措施 - 發射反制措施 - Implementare contromisure - - - Makes all selected vehicles deploy countermeasures such as smokes and flares. - Tous les véhicules sélectionnés déploient des contre-mesures telles que des fumées et des fusées éclairantes. - Alle ausgewählten Fahrzeuge setzen Gegenmaßnahmen wie Rauch und Leuchtfackeln ein. - 選択された全車両から煙幕やフレアといった欺瞞装置を作動させます。 - Powoduje że wszystkie wybrane pojazdy odpalają środki przeciw namierzające, czyli flary, dymy itp. - 使所有选定的车辆发射反制措施,如烟雾和诱饵弹。 - 使所有選定的車輛發射反制措施,如煙霧和誘餌彈。 - Fa in modo che tutti i veicoli selezionati utilizzino contromisure come fumogeni e razzi. - Deep Copy Komplette Kopie @@ -366,5 +325,82 @@ 重新加載Zeus界面。可用於修復鎖定問題。 Ricarica l'interfaccia di Zeus. Può essere utilizzato per risolvere i problemi di blocco. + + AI Control + + + Eject Passengers + Ejecter les passagers + Passagiere rauswerfen + Wysadź pasażerów + 後部座席を脱出 + 승객 탈출 + 弹出乘客 + 彈出乘客 + Espellere i passeggeri + + + Ejects the passengers of all selected vehicles. If the vehicle is currently flying, the units will be given parachutes.\nUnlike vanilla eject, units will disembark one after another (not immediately, with a short delay in-between). + Éjecte les passagers de tous les véhicules sélectionnés. Si le véhicule est actuellement en vol, les unités recevront des parachutes. Contrairement à l'éjection standard, les unités débarqueront les unes après les autres (pas immédiatement, avec un court délai entre les deux). + Wirft die Fahrgäste aller ausgewählten Fahrzeuge raus. Wenn das Fahrzeug gerade fliegt, erhalten die Einheiten Fallschirme.\nIm Gegensatz zu "Vanilla" sitzen die Einheiten nacheinander ab (nicht alle sofort, sondern mit einer kurzen Verzögerung dazwischen). + Wysadza pasażerów ze wszystkich wybranych pojazdów. Jeżeli pojazd jest w powietrzu, pasażerowie dostaną spadochron.\nJednostki zostaną wysadzone jedna po drugiej (nie wszystkie na raz). + 選択された全車両から後部座席のユニットを脱出させます。もし車両が飛行中の場合はパラシュートが与えられます。\nなお脱出は一人づつ降りるようになっています (即時ではなく数秒づつ)。 + 弹出所有选定载具的乘客。如果载具正在飞行,这些单位将获得降落伞。与原版弹射不同的是,单位将一个接一个地离开(而不是立即离开,中间会有短暂间隙)。 + 彈出所有選定載具的乘客。如果載具正在飛行,這些單位將獲得降落傘。與原版彈射不同的是,單位將一個接一個地離開(而不是立即離開,中間會有短暫間隙)。 + Espelle i passeggeri di tutti i veicoli selezionati. Se il veicolo è attualmente in volo, alle unità verranno forniti paracadute.\nA differenza dell'espulsione vaniglia, le unità sbarcheranno una dopo l'altra (non immediatamente, con un breve ritardo nel mezzo). + + + Deploy Countermeasures + Déployer des contre-mesures + Gegenmaßnahmen einleiten + 欺瞞装置作動 + Odpal środki przeciw namierzające + 发射反制措施 + 發射反制措施 + Implementare contromisure + + + Makes all selected vehicles deploy countermeasures such as smokes and flares. + Tous les véhicules sélectionnés déploient des contre-mesures telles que des fumées et des fusées éclairantes. + Alle ausgewählten Fahrzeuge setzen Gegenmaßnahmen wie Rauch und Leuchtfackeln ein. + 選択された全車両から煙幕やフレアといった欺瞞装置を作動させます。 + Powoduje że wszystkie wybrane pojazdy odpalają środki przeciw namierzające, czyli flary, dymy itp. + 使所有选定的车辆发射反制措施,如烟雾和诱饵弹。 + 使所有選定的車輛發射反制措施,如煙霧和誘餌彈。 + Fa in modo che tutti i veicoli selezionati utilizzino contromisure come fumogeni e razzi. + + + Watch Cursor + + + Makes selected AI units watch the cursor's position. Order a unit to watch itself (i.e., with the cursor hovering over the unit) to cancel the watch target. + + + Watch Curator Camera + + + Makes selected AI units watch the Zeus camera's position. + + + Force Fire + + + Makes selected AI units fire their current weapon while the key is held down. Vehicles will fire their first available turret with a weapon. + + + Move To Cursor + + + Makes selected AI units move to the cursor's position. Afterwards, AI units will return to formation and resume waypoints. + + + Toggle AI Pathing + + + Makes selected AI units start or stop moving. Does not affect aiming or shooting. Similar to "Hold Position" in RTS games. + + + AI Pathing Toggled +