Skip to content

Commit

Permalink
AI Control Keybinds (#496)
Browse files Browse the repository at this point in the history
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 <neil.evers.1995@gmail.com>
Co-authored-by: mharis001 <mhariszakar@gmail.com>
  • Loading branch information
4 people authored Mar 8, 2023
1 parent 4dc8323 commit 1679273
Show file tree
Hide file tree
Showing 13 changed files with 674 additions and 120 deletions.
61 changes: 5 additions & 56 deletions addons/ai/functions/fnc_suppressiveFire.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions addons/common/XEH_PREP.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
PREP(canFire);
PREP(changeGroupSide);
PREP(collapseTree);
PREP(createZeus);
Expand All @@ -6,20 +7,24 @@ PREP(deserializeInventory);
PREP(deserializeObjects);
PREP(displayCuratorLoad);
PREP(displayCuratorUnload);
PREP(drawHint);
PREP(dumpPerformanceCounters);
PREP(earthquake);
PREP(ejectPassengers);
PREP(exportMissionSQF);
PREP(exportText);
PREP(fireArtillery);
PREP(fireVLS);
PREP(fireWeapon);
PREP(forceFire);
PREP(formatDegrees);
PREP(getActiveTree);
PREP(getAllTurrets);
PREP(getArtilleryETA);
PREP(getCargoPositionsCount);
PREP(getDefaultInventory);
PREP(getDLC);
PREP(getEffectiveGunner);
PREP(getLightingSelections);
PREP(getPhoneticName);
PREP(getPlayers);
Expand All @@ -42,6 +47,7 @@ PREP(initSliderEdit);
PREP(isCursorOnMouseArea);
PREP(isInScreenshotMode);
PREP(isPlacementActive);
PREP(isReloading);
PREP(isRemoteControlled);
PREP(isSwimming);
PREP(isUnitFFV);
Expand Down
2 changes: 2 additions & 0 deletions addons/common/XEH_postInit.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
43 changes: 43 additions & 0 deletions addons/common/functions/fnc_canFire.sqf
Original file line number Diff line number Diff line change
@@ -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 <OBJECT>
* 1: Ignore Ammo <BOOL> (default: false)
* 2: Ignore Reload <BOOL> (default: false)
*
* Return Value:
* Can Fire <BOOL>
*
* 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))}}
};
}
198 changes: 198 additions & 0 deletions addons/common/functions/fnc_drawHint.sqf
Original file line number Diff line number Diff line change
@@ -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 <ARRAY|OBJECT>
* 1: Icon Texture <STRING>
* 2: Color (RGBA) <ARRAY> (default: [1, 1, 1, 1])
* 3: Scale <NUMBER> (default: 1)
* 4: Angle <NUMBER> (default: 0)
* 5: Text <STRING> (default: "")
* 6: Shadow <NUMBER|BOOL> (default: 0)
* 7: Text Size <NUMBER> (default: 0.05)
* 8: Font <STRING> (default: "RobotoCondensed")
* 9: Align <STRING> (default: "center")
*
* The visual properties for "LINE" elements are:
* 0: Start Position <ARRAY|OBJECT>
* 1: End Position <ARRAY|OBJECT>
* 2: Color (RGBA) <ARRAY> (default: [1, 1, 1, 1])
*
* Arguments:
* 0: Elements <ARRAY>
* 0: Type <STRING>
* - either "ICON" or "LINE".
* 1: Visual Properties <ARRAY>
* - depends on element type (see above for details).
* 1: Duration (in seconds) <NUMBER>
* 2: ID <STRING|OBJECT> (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 <STRING>
*
* 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
Loading

0 comments on commit 1679273

Please sign in to comment.