Skip to content

Commit 1679273

Browse files
ampersand38mharis001neilzar
authored
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 <[email protected]> Co-authored-by: Neil Evers <[email protected]> Co-authored-by: mharis001 <[email protected]>
1 parent 4dc8323 commit 1679273

13 files changed

+674
-120
lines changed

addons/ai/functions/fnc_suppressiveFire.sqf

+5-56
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,10 @@ if (_unit isEqualType grpNull) exitWith {
5151
};
5252

5353
// If a vehicle is given directly, use its gunner as the unit
54-
if !(_unit isKindOf "CAManBase") then {
55-
_unit = gunner _unit;
56-
};
57-
58-
if (
59-
!alive _unit
60-
|| {isPlayer _unit}
61-
|| {!(lifeState _unit in ["HEALTHY", "INJURED"])}
62-
|| {_unit getVariable [QGVAR(isSuppressing), false]}
63-
|| {
64-
private _vehicle = vehicle _unit;
54+
private _unit = _unit call EFUNC(common,getEffectiveGunner);
6555

66-
if (_vehicle == _unit || {_unit call EFUNC(common,isUnitFFV)}) then {
67-
currentWeapon _unit == ""
68-
} else {
69-
_vehicle weaponsTurret (_vehicle unitTurret _unit) isEqualTo []
70-
};
71-
}
72-
) exitWith {};
56+
// Exit if the unit cannot fire or is already suppressing
57+
if (!([_unit, true, true] call EFUNC(common,canFire)) || {_unit getVariable [QGVAR(isSuppressing), false]}) exitWith {};
7358

7459
// Prevent the unit from performing other suppressive fire tasks while this one is active
7560
_unit setVariable [QGVAR(isSuppressing), true, true];
@@ -177,47 +162,11 @@ private _endTime = CBA_missionTime + _duration + TARGETING_DELAY;
177162
};
178163

179164
if (CBA_missionTime >= _shotTime) then {
165+
[_unit, true] call EFUNC(common,fireWeapon);
166+
180167
private _vehicle = vehicle _unit;
181168
private _turretPath = _vehicle unitTurret _unit;
182169

183-
switch (true) do {
184-
// On foot
185-
case (_vehicle == _unit): {
186-
weaponState _unit params ["_weapon", "_muzzle", "_fireMode"];
187-
188-
_unit setAmmo [_weapon, 1e6];
189-
_unit forceWeaponFire [_muzzle, _fireMode];
190-
};
191-
192-
// FFV
193-
case (_unit call EFUNC(common,isUnitFFV)): {
194-
// Using UseMagazine action since forceWeaponFire command does not work for FFV units
195-
// UseMagazine action doesn't seem to work with currently loaded magazine (currentMagazineDetail)
196-
// Therefore, this relies on the unit having an extra magazine in their inventory
197-
// but should be fine in most situations
198-
private _weapon = currentWeapon _unit;
199-
private _compatibleMagazines = _weapon call CBA_fnc_compatibleMagazines;
200-
private _index = magazines _unit findAny _compatibleMagazines;
201-
if (_index == -1) exitWith {};
202-
203-
private _magazine = magazinesDetail _unit select _index;
204-
_magazine call EFUNC(common,parseMagazineDetail) params ["_id", "_owner"];
205-
206-
_unit setAmmo [_weapon, 1e6];
207-
CBA_logic action ["UseMagazine", _unit, _unit, _owner, _id];
208-
};
209-
210-
// Vehicle gunner
211-
default {
212-
private _muzzle = weaponState [_vehicle, _turretPath] select 1;
213-
_unit setAmmo [_muzzle, 1e6];
214-
215-
private _magazine = _vehicle currentMagazineDetailTurret _turretPath;
216-
_magazine call EFUNC(common,parseMagazineDetail) params ["_id", "_owner"];
217-
_vehicle action ["UseMagazine", _vehicle, _unit, _owner, _id];
218-
};
219-
};
220-
221170
// Set time until the next shot based on the weapon's ammo reloading time and whether the current burst is finished
222171
private _reloadTime = [_vehicle, _turretPath] call EFUNC(common,getWeaponReloadTime);
223172
_currentBurstRounds = _currentBurstRounds + 1;

addons/common/XEH_PREP.hpp

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
PREP(canFire);
12
PREP(changeGroupSide);
23
PREP(collapseTree);
34
PREP(createZeus);
@@ -6,20 +7,24 @@ PREP(deserializeInventory);
67
PREP(deserializeObjects);
78
PREP(displayCuratorLoad);
89
PREP(displayCuratorUnload);
10+
PREP(drawHint);
911
PREP(dumpPerformanceCounters);
1012
PREP(earthquake);
1113
PREP(ejectPassengers);
1214
PREP(exportMissionSQF);
1315
PREP(exportText);
1416
PREP(fireArtillery);
1517
PREP(fireVLS);
18+
PREP(fireWeapon);
19+
PREP(forceFire);
1620
PREP(formatDegrees);
1721
PREP(getActiveTree);
1822
PREP(getAllTurrets);
1923
PREP(getArtilleryETA);
2024
PREP(getCargoPositionsCount);
2125
PREP(getDefaultInventory);
2226
PREP(getDLC);
27+
PREP(getEffectiveGunner);
2328
PREP(getLightingSelections);
2429
PREP(getPhoneticName);
2530
PREP(getPlayers);
@@ -42,6 +47,7 @@ PREP(initSliderEdit);
4247
PREP(isCursorOnMouseArea);
4348
PREP(isInScreenshotMode);
4449
PREP(isPlacementActive);
50+
PREP(isReloading);
4551
PREP(isRemoteControlled);
4652
PREP(isSwimming);
4753
PREP(isUnitFFV);

addons/common/XEH_postInit.sqf

+2
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,8 @@
319319

320320
[QGVAR(earthquake), LINKFUNC(earthquake)] call CBA_fnc_addEventHandler;
321321
[QGVAR(fireArtillery), LINKFUNC(fireArtillery)] call CBA_fnc_addEventHandler;
322+
[QGVAR(fireWeapon), LINKFUNC(fireWeapon)] call CBA_fnc_addEventHandler;
323+
[QGVAR(forceFire), LINKFUNC(forceFire)] call CBA_fnc_addEventHandler;
322324
[QGVAR(setLampState), LINKFUNC(setLampState)] call CBA_fnc_addEventHandler;
323325
[QGVAR(setMagazineAmmo), LINKFUNC(setMagazineAmmo)] call CBA_fnc_addEventHandler;
324326
[QGVAR(setTurretAmmo), LINKFUNC(setTurretAmmo)] call CBA_fnc_addEventHandler;
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include "script_component.hpp"
2+
/*
3+
* Author: mharis001, Ampersand
4+
* Checks if the given unit or vehicle can fire its current weapon.
5+
*
6+
* Arguments:
7+
* 0: Unit or Vehicle <OBJECT>
8+
* 1: Ignore Ammo <BOOL> (default: false)
9+
* 2: Ignore Reload <BOOL> (default: false)
10+
*
11+
* Return Value:
12+
* Can Fire <BOOL>
13+
*
14+
* Example:
15+
* [_unit] call zen_common_fnc_canFire
16+
*
17+
* Public: No
18+
*/
19+
20+
params [["_unit", objNull, [objNull]], ["_ignoreAmmo", false, [false]], ["_ignoreReload", false, [false]]];
21+
22+
private _unit = _unit call FUNC(getEffectiveGunner);
23+
24+
alive _unit
25+
&& {!isPlayer _unit}
26+
&& {lifeState _unit in ["HEALTHY", "INJURED"]}
27+
&& {
28+
private _vehicle = vehicle _unit;
29+
30+
if (_vehicle == _unit || {_unit call FUNC(isUnitFFV)}) then {
31+
currentWeapon _unit != ""
32+
&& {_ignoreAmmo || {_unit ammo currentMuzzle _unit > 0}}
33+
&& {_ignoreReload || {!(_unit call FUNC(isReloading))}}
34+
} else {
35+
private _turretPath = _vehicle unitTurret _unit;
36+
weaponState [_vehicle, _turretPath] params ["_weapon", "", "", "", "_ammoCount"];
37+
38+
_weapon != ""
39+
&& {!("fake" in toLower _weapon)}
40+
&& {_ignoreAmmo || {_ammoCount > 0} || {_weapon isKindOf ["CarHorn", configFile >> "CfgWeapons"]}}
41+
&& {_ignoreReload || {!([_vehicle, _turretPath] call FUNC(isReloading))}}
42+
};
43+
}
+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#include "script_component.hpp"
2+
/*
3+
* Author: Ampersand, mharis001
4+
* Draws a hint that contains icon and line elements in 2D (Zeus display map)
5+
* and 3D (in world) for the given duration.
6+
*
7+
* Will overwrite an existing hint when called using the same ID. Position
8+
* arguments can be given as OBJECTs, in which case the hint elements will
9+
* follow objects as they move and will be hidden if the object is deleted.
10+
*
11+
* The visual properties for "ICON" elements are:
12+
* 0: Position <ARRAY|OBJECT>
13+
* 1: Icon Texture <STRING>
14+
* 2: Color (RGBA) <ARRAY> (default: [1, 1, 1, 1])
15+
* 3: Scale <NUMBER> (default: 1)
16+
* 4: Angle <NUMBER> (default: 0)
17+
* 5: Text <STRING> (default: "")
18+
* 6: Shadow <NUMBER|BOOL> (default: 0)
19+
* 7: Text Size <NUMBER> (default: 0.05)
20+
* 8: Font <STRING> (default: "RobotoCondensed")
21+
* 9: Align <STRING> (default: "center")
22+
*
23+
* The visual properties for "LINE" elements are:
24+
* 0: Start Position <ARRAY|OBJECT>
25+
* 1: End Position <ARRAY|OBJECT>
26+
* 2: Color (RGBA) <ARRAY> (default: [1, 1, 1, 1])
27+
*
28+
* Arguments:
29+
* 0: Elements <ARRAY>
30+
* 0: Type <STRING>
31+
* - either "ICON" or "LINE".
32+
* 1: Visual Properties <ARRAY>
33+
* - depends on element type (see above for details).
34+
* 1: Duration (in seconds) <NUMBER>
35+
* 2: ID <STRING|OBJECT> (default: "")
36+
* - an ID is generated when an empty string is given.
37+
* - in the case of an OBJECT, the hash value is used.
38+
*
39+
* Return Value:
40+
* ID <STRING>
41+
*
42+
* Example:
43+
* [["ICON", [_unit, _texture]], 3] call zen_common_fnc_drawHint
44+
*
45+
* Public: No
46+
*/
47+
48+
#define MAP_ICON_SIZE 24
49+
50+
params [
51+
["_elements", [], [[]]],
52+
["_duration", 0, [0]],
53+
["_id", "", ["", objNull]]
54+
];
55+
56+
private _ctrlMap = findDisplay IDD_RSCDISPLAYCURATOR displayCtrl IDC_RSCDISPLAYCURATOR_MAINMAP;
57+
58+
// Map of hint IDs and their corresponding draw (2D and 3D) event handler IDs
59+
if (isNil QGVAR(drawHintMap)) then {
60+
GVAR(drawHintMap) = createHashMap;
61+
};
62+
63+
// Use an object's hash value as its hint ID
64+
if (_id isEqualType objNull) then {
65+
_id = hashValue _id;
66+
};
67+
68+
// Generate a hint ID if one is not given
69+
if (_id isEqualTo "") then {
70+
if (isNil QGVAR(drawHintCounter)) then {
71+
GVAR(drawHintCounter) = -1;
72+
};
73+
74+
GVAR(drawHintCounter) = GVAR(drawHintCounter) + 1;
75+
76+
_id = [CBA_clientID, GVAR(drawHintCounter)] joinString ":";
77+
};
78+
79+
// Remove an existing hint with the same ID
80+
if (_id in GVAR(drawHintMap)) then {
81+
GVAR(drawHintMap) deleteAt _id params ["_id2D", "_id3D"];
82+
83+
_ctrlMap ctrlRemoveEventHandler ["Draw", _id2D];
84+
removeMissionEventHandler ["Draw3D", _id3D];
85+
};
86+
87+
// Validate the given hint elements and separate them by type
88+
private _icons = [];
89+
private _lines = [];
90+
91+
{
92+
_x params [["_type", "", [""]], ["_args", [], [[]]]];
93+
94+
switch (_type) do {
95+
case "ICON": {
96+
_args params [
97+
["_position", [0, 0, 0], [[], objNull], 3],
98+
["_icon", "", [""]],
99+
["_color", [1, 1, 1, 1], [[]], 4],
100+
["_scale", 1, [0]],
101+
["_angle", 0, [0]],
102+
["_text", "", [""]],
103+
["_shadow", 0, [0, false]],
104+
["_textSize", 0.05, [0]],
105+
["_font", "RobotoCondensed", [""]],
106+
["_align", "center", [""]]
107+
];
108+
109+
_icons pushBack [_position, _icon, _color, _scale, _angle, _text, _shadow, _textSize, _font, _align];
110+
};
111+
case "LINE": {
112+
_args params [
113+
["_begPos", [0, 0, 0], [[], objNull], 3],
114+
["_endPos", [0, 0, 0], [[], objNull], 3],
115+
["_color", [1, 1, 1, 1], [[]], 4]
116+
];
117+
118+
_lines pushBack [_begPos, _endPos, _color];
119+
};
120+
default {
121+
ERROR_1("Invalid hint element type - %1.",_type);
122+
};
123+
};
124+
} forEach _elements;
125+
126+
// Add event handlers to draw the hint elements
127+
private _fnc_draw2D = {
128+
params ["_ctrlMap"];
129+
_thisArgs params ["_icons", "_lines"];
130+
131+
{
132+
_x params ["_position", "_icon", "_color", "_scale", "_angle", "_text", "_shadow", "_textSize", "_font", "_align"];
133+
134+
if (_position isEqualTo objNull) then {continue};
135+
136+
_ctrlMap drawIcon [_icon, _color, _position, _scale * MAP_ICON_SIZE, _scale * MAP_ICON_SIZE, _angle, _text, _shadow, _textSize, _font, _align];
137+
} forEach _icons;
138+
139+
{
140+
_x params ["_begPos", "_endPos", "_color"];
141+
142+
if (objNull in [_begPos, _endPos]) then {continue};
143+
144+
_ctrlMap drawLine [_begPos, _endPos, _color];
145+
} forEach _lines;
146+
};
147+
148+
private _fnc_draw3D = {
149+
_thisArgs params ["_icons", "_lines", "_endTime", "_id"];
150+
151+
// Exit if the Zeus display is closed or hint duration is complete
152+
if (isNull curatorCamera || {CBA_missionTime >= _endTime}) exitWith {
153+
GVAR(drawHintMap) deleteAt _id params ["_id2D", "_id3D"];
154+
155+
private _ctrlMap = findDisplay IDD_RSCDISPLAYCURATOR displayCtrl IDC_RSCDISPLAYCURATOR_MAINMAP;
156+
_ctrlMap ctrlRemoveEventHandler ["Draw", _id2D];
157+
removeMissionEventHandler ["Draw3D", _id3D];
158+
};
159+
160+
// No 3D drawing needed if the map is visible
161+
if (visibleMap) exitWith {};
162+
163+
{
164+
_x params ["_position", "_icon", "_color", "_scale", "_angle", "_text", "_shadow", "_textSize", "_font", "_align"];
165+
166+
if (_position isEqualTo objNull) then {continue};
167+
168+
if (_position isEqualType objNull) then {
169+
_position = unitAimPositionVisual _position;
170+
};
171+
172+
drawIcon3D [_icon, _color, _position, _scale, _scale, _angle, _text, _shadow, _textSize, _font, _align];
173+
} forEach _icons;
174+
175+
{
176+
_x params ["_begPos", "_endPos", "_color"];
177+
178+
if (objNull in [_begPos, _endPos]) then {continue};
179+
180+
if (_begPos isEqualType objNull) then {
181+
_begPos = unitAimPositionVisual _begPos;
182+
};
183+
184+
if (_endPos isEqualType objNull) then {
185+
_endPos = unitAimPositionVisual _endPos;
186+
};
187+
188+
drawLine3D [_begPos, _endPos, _color];
189+
} forEach _lines;
190+
};
191+
192+
private _args = [_icons, _lines, CBA_missionTime + _duration, _id];
193+
private _id2D = [_ctrlMap, "Draw", _fnc_draw2D, _args] call CBA_fnc_addBISEventHandler;
194+
private _id3D = [missionNamespace, "Draw3D", _fnc_draw3D, _args] call CBA_fnc_addBISEventHandler;
195+
GVAR(drawHintMap) set [_id, [_id2D, _id3D]];
196+
197+
// Return the hint ID (in case a generated one was used)
198+
_id

0 commit comments

Comments
 (0)