Skip to content

Commit

Permalink
Add Throw Grenade context menu action (#132)
Browse files Browse the repository at this point in the history
  • Loading branch information
mharis001 authored Sep 13, 2021
1 parent a22ac29 commit b9264f6
Show file tree
Hide file tree
Showing 16 changed files with 338 additions and 2 deletions.
1 change: 1 addition & 0 deletions AUTHORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ bux
CesarStef
classicarma
commy2
das attorney
dedmen
DeliciousJaffa
diaverso
Expand Down
2 changes: 2 additions & 0 deletions addons/ai/XEH_PREP.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
PREP(applySkills);
PREP(canThrowGrenade);
PREP(garrison);
PREP(handleObjectEdited);
PREP(handleSkillsChange);
PREP(initMan);
PREP(searchBuilding);
PREP(suppressiveFire);
PREP(throwGrenade);
PREP(unGarrison);
2 changes: 1 addition & 1 deletion addons/ai/XEH_postInit.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ QGVAR(skills) addPublicVariableEventHandler LINKFUNC(handleSkillsChange);
["CAManBase", "InitPost", LINKFUNC(initMan), true, [], false] call CBA_fnc_addClassEventHandler;

[QGVAR(suppressiveFire), LINKFUNC(suppressiveFire)] call CBA_fnc_addEventHandler;

[QGVAR(throwGrenade), LINKFUNC(throwGrenade)] call CBA_fnc_addEventHandler;
[QGVAR(unGarrison), LINKFUNC(unGarrison)] call CBA_fnc_addEventHandler;
28 changes: 28 additions & 0 deletions addons/ai/functions/fnc_canThrowGrenade.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include "script_component.hpp"
/*
* Author: mharis001
* Checks if the given unit can throw a grenade.
* When specified, also checks if the unit has the given grenade magazine.
*
* Arguments:
* 0: Unit <OBJECT>
* 1: Grenade Magazine <STRING> (default: "")
*
* Return Value:
* Can Throw Grenade <BOOL>
*
* Example:
* [_unit, "HandGrenade"] call zen_ai_fnc_canThrowGrenade
*
* Public: No
*/

params [["_unit", objNull, [objNull]], ["_magazine", "", [""]]];

alive _unit
&& {!isPlayer _unit}
&& {vehicle _unit == _unit}
&& {_unit isKindOf "CAManBase"}
&& {lifeState _unit in ["HEALTHY", "INJURED"]}
&& {!(_unit call EFUNC(common,isSwimming))}
&& {_magazine == "" || {_magazine in magazines _unit}}
129 changes: 129 additions & 0 deletions addons/ai/functions/fnc_throwGrenade.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#include "script_component.hpp"
/*
* Author: mharis001
* Makes the unit throw a grenade at the given position.
*
* Arguments:
* 0: Unit <OBJECT>
* 1: Grenade Magazine <STRING>
* 2: Position ASL <ARRAY>
*
* Return Value:
* None
*
* Example:
* [_unit, "HandGrenade", [0, 0, 0]] call zen_ai_fnc_throwGrenade
*
* Public: No
*/

#define AIMING_TIMEOUT 10
#define INITIAL_DELAY 0.5
#define CLEANUP_DELAY 1.5

#define TOLERANCE_TIME 2
#define MIN_TOLERANCE 10
#define MAX_TOLERANCE 45

#define DISABLED_ABILITIES ["AIMINGERROR", "AUTOTARGET", "FSM", "PATH", "SUPPRESSION", "TARGET"]

params [
["_unit", objNull, [objNull]],
["_magazine", "", [""]],
["_position", [0, 0, 0], [[]], 3]
];

if (!local _unit) exitWith {
[QGVAR(throwGrenade), _this, _unit] call CBA_fnc_targetEvent;
};

// Exit if the unit cannot throw the given grenade type
if !([_unit, _magazine] call FUNC(canThrowGrenade)) exitWith {};

// Disable AI abilities to make units more responsive to commands
// Also interrupts a unit's movement to a waypoint, forcing them to stop
private _abilities = DISABLED_ABILITIES apply {_unit checkAIFeature _x};
{_unit disableAI _x} forEach DISABLED_ABILITIES;

// Set the unit's behaviour to COMBAT to make them raise their weapon and aim at the target
private _behaviour = combatBehaviour _unit;
_unit setCombatBehaviour "COMBAT";

// Set the unit's combat mode to BLUE to make them hold their fire
// The unit will still track the target but only fire when told to
private _combatMode = unitCombatMode _unit;
_unit setUnitCombatMode "BLUE";

// Prevent the unit from changing stance due to combat behaviour by
// forcing a unit position that matches their current stance
// Usually only a problem with the AUTO unit position
private _unitPos = unitPos _unit;
private _stanceIndex = ["STAND", "CROUCH", "PRONE"] find stance _unit;
private _unitPosForStance = ["UP", "MIDDLE", "DOWN"] param [_stanceIndex, "UP"];
_unit setUnitPos _unitPosForStance;

// Create a helper object for the unit to target
private _helper = createVehicle ["CBA_B_InvisibleTargetVehicle", [0, 0, 0], [], 0, "CAN_COLLIDE"];
_helper setPosASL _position;

// Make the unit target the helper object
_unit reveal [_helper, 4];
_unit lookAt _helper;
_unit doWatch _helper;
_unit doTarget _helper;

// Wait until the unit is aiming at the helper object before throwing the grenade
// Initial delay helps prevent weird issue when the unit is moving to a waypoint and the helper is directly in front of it
[{
_this set [7, CBA_missionTime];

[{
params ["_unit", "_helper", "_magazine", "_abilities", "_behaviour", "_combatMode", "_unitPos", "_startTime"];

// Check that the unit is aiming at the helper, increase tolerance as more time passes
private _direction = _unit getRelDir _helper;
private _tolerance = linearConversion [0, TOLERANCE_TIME, CBA_missionTime - _startTime, MIN_TOLERANCE, MAX_TOLERANCE, true];
private _throwGrenade = _direction <= _tolerance || {_direction >= 360 - _tolerance};

if (_throwGrenade) then {
private _index = magazines _unit find _magazine;

if (_index != -1) then {
private _magazine = magazinesDetail _unit select _index;
_magazine call EFUNC(common,parseMagazineDetail) params ["_id", "_owner"];
CBA_logic action ["UseMagazine", _unit, _unit, _owner, _id];
};
};

if (
_throwGrenade
|| {!alive _unit}
|| {isNull _helper}
|| {CBA_missionTime >= _startTime + AIMING_TIMEOUT}
) exitWith {
// Restore AI abilities, behaviour, combat mode, stance, and targeting
// Small delay before cleanup to give the unit time to finish throwing the grenade
[{
params ["_unit", "_helper", "_abilities", "_behaviour", "_combatMode", "_unitPos"];

{
if (_x) then {
_unit enableAI (DISABLED_ABILITIES select _forEachIndex);
};
} forEach _abilities;

_unit setCombatBehaviour _behaviour;
_unit setUnitCombatMode _combatMode;
_unit setUnitPos _unitPos;
_unit doWatch objNull;
_unit lookAt objNull;

deleteVehicle _helper;
}, [_unit, _helper, _abilities, _behaviour, _combatMode, _unitPos], CLEANUP_DELAY] call CBA_fnc_waitAndExecute;

true // Exit
};

false // Continue
}, {}, _this] call CBA_fnc_waitUntilAndExecute;
}, [_unit, _helper, _magazine, _abilities, _behaviour, _combatMode, _unitPos], INITIAL_DELAY] call CBA_fnc_waitAndExecute;
1 change: 1 addition & 0 deletions addons/common/XEH_PREP.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ PREP(initSliderEdit);
PREP(isInScreenshotMode);
PREP(isPlacementActive);
PREP(isRemoteControlled);
PREP(isSwimming);
PREP(isUnitFFV);
PREP(isVLS);
PREP(isWeapon);
Expand Down
20 changes: 20 additions & 0 deletions addons/common/functions/fnc_isSwimming.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "script_component.hpp"
/*
* Author: das attorney, Jonpas
* Checks if the given unit is swimming (surface swimming or diving).
*
* Arguments:
* 0: Unit <OBJECT>
*
* Return Value:
* Is Swimming <BOOL>
*
* Example:
* [_unit] call zen_common_fnc_isSwimming
*
* Public: Yes
*/

params [["_unit", objNull, [objNull]]];

animationState _unit select [1, 3] in ["bdv", "bsw", "dve", "sdv", "ssw", "swm"]
6 changes: 6 additions & 0 deletions addons/context_actions/CfgContext.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ class EGVAR(context_menu,actions) {
insertChildren = QUOTE(_objects call FUNC(getArtilleryActions));
priority = 70;
};
class ThrowGrenade {
displayName = CSTRING(ThrowGrenade);
icon = QPATHTOF(ui\grenade_ca.paa);
insertChildren = QUOTE(_objects call FUNC(getGrenadeActions));
priority = 70;
};
class Formation {
displayName = "$STR_3DEN_Group_Attribute_Formation_displayName";
condition = QUOTE(_groups findIf {units _x findIf {!isPlayer _x} != -1} != -1);
Expand Down
3 changes: 3 additions & 0 deletions addons/context_actions/XEH_PREP.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ PREP(canRepairVehicles);
PREP(canToggleSurrender);
PREP(canUnloadViV);
PREP(copyVehicleAppearance);
PREP(compileGrenades);
PREP(getArtilleryActions);
PREP(getGrenadeActions);
PREP(healUnits);
PREP(openEditableObjectsDialog);
PREP(pasteVehicleAppearance);
PREP(rearmVehicles);
PREP(refuelVehicles);
PREP(repairVehicles);
PREP(selectArtilleryPos);
PREP(selectThrowPos);
PREP(setBehaviour);
PREP(setCombatMode);
PREP(setFormation);
Expand Down
2 changes: 2 additions & 0 deletions addons/context_actions/XEH_preStart.sqf
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include "script_component.hpp"

#include "XEH_PREP.hpp"

call FUNC(compileGrenades);
32 changes: 32 additions & 0 deletions addons/context_actions/functions/fnc_compileGrenades.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include "script_component.hpp"
/*
* Author: mharis001
* Compiles a list of all throwable grenades.
*
* Arguments:
* None
*
* Return Value:
* None
*
* Example:
* [] call zen_context_actions_fnc_compileGrenades
*
* Public: No
*/

private _grenades = createHashMap;
private _cfgWeapons = configFile >> "CfgWeapons";
private _cfgMagazines = configFile >> "CfgMagazines";

{
{
private _config = _cfgMagazines >> _x;
private _name = getText (_config >> "displayName");
private _icon = getText (_config >> "picture");

_grenades set [configName _config, [_name, _icon]];
} forEach getArray (_cfgWeapons >> "Throw" >> _x >> "magazines");
} forEach getArray (_cfgWeapons >> "Throw" >> "muzzles");

uiNamespace setVariable [QGVAR(grenades), _grenades];
54 changes: 54 additions & 0 deletions addons/context_actions/functions/fnc_getGrenadeActions.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#include "script_component.hpp"
/*
* Author: mharis001
* Returns children actions for grenades in unit inventories.
*
* Arguments:
* N: Objects <OBJECT>
*
* Return Value:
* Actions <ARRAY>
*
* Example:
* [_object] call zen_context_actions_fnc_getGrenadeActions
*
* Public: No
*/

// Get all magazines in the inventories of units that can throw grenades
private _magazines = flatten (_this select {
_x call EFUNC(ai,canThrowGrenade)
} apply {
magazines _x
});

// Filter out non-grenade magazines and sort them alphabetically by name
private _cache = uiNamespace getVariable QGVAR(grenades);
private _grenades = _magazines arrayIntersect keys _cache apply {
(_cache get _x) params ["_name", "_icon"];
[_name, _icon, _x]
};

_grenades sort true;

// Create actions for every grenade type
private _actions = [];

{
_x params ["_name", "_icon", "_magazine"];

private _action = [
_magazine,
_name,
_icon,
{
[_objects, _args] call FUNC(selectThrowPos);
},
{true},
_magazine
] call EFUNC(context_menu,createAction);

_actions pushBack [_action, [], 0];
} forEach _grenades;

_actions
49 changes: 49 additions & 0 deletions addons/context_actions/functions/fnc_selectThrowPos.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include "script_component.hpp"
/*
* Author: mharis001
* Allows Zeus to select a position to make units throw grenades at.
*
* Arguments:
* 0: Objects <ARRAY>
* 1: Magazine <STRING>
*
* Return Value:
* None
*
* Example:
* [_objects, "HandGrenade"] call zen_context_actions_fnc_selectThrowPos
*
* Public: No
*/

#define MAX_DISTANCE 75

params ["_objects", "_magazine"];

// Get units that can throw the selected grenade type
private _units = _objects select {
[_x, _magazine] call EFUNC(ai,canThrowGrenade)
};

private _name = getText (configFile >> "CfgMagazines" >> _magazine >> "displayName");
private _text = format [LLSTRING(ThrowX), _name];

[_units, {
params ["_confirmed", "_units", "_position", "_magazine"];

if (!_confirmed) exitWith {};

private _notInRange = 0;

{
if (_x distance2D _position <= MAX_DISTANCE) then {
[_x, _magazine, _position] call EFUNC(ai,throwGrenade);
} else {
_notInRange = _notInRange + 1;
};
} forEach _units;

if (_notInRange > 0) then {
[LSTRING(PositionTooFar), _notInRange] call EFUNC(common,showMessage);
};
}, _magazine, _text] call EFUNC(common,selectPosition);
Loading

0 comments on commit b9264f6

Please sign in to comment.