From b9264f686dd7775aa7fbd680b22ccb367a5dd1ca Mon Sep 17 00:00:00 2001 From: mharis001 <34453221+mharis001@users.noreply.github.com> Date: Mon, 13 Sep 2021 05:15:49 -0400 Subject: [PATCH] Add Throw Grenade context menu action (#132) --- AUTHORS.txt | 1 + addons/ai/XEH_PREP.hpp | 2 + addons/ai/XEH_postInit.sqf | 2 +- addons/ai/functions/fnc_canThrowGrenade.sqf | 28 ++++ addons/ai/functions/fnc_throwGrenade.sqf | 129 ++++++++++++++++++ addons/common/XEH_PREP.hpp | 1 + addons/common/functions/fnc_isSwimming.sqf | 20 +++ addons/context_actions/CfgContext.hpp | 6 + addons/context_actions/XEH_PREP.hpp | 3 + addons/context_actions/XEH_preStart.sqf | 2 + .../functions/fnc_compileGrenades.sqf | 32 +++++ .../functions/fnc_getGrenadeActions.sqf | 54 ++++++++ .../functions/fnc_selectThrowPos.sqf | 49 +++++++ addons/context_actions/stringtable.xml | 9 ++ addons/context_actions/ui/grenade_ca.paa | Bin 0 -> 5625 bytes addons/main/script_mod.hpp | 2 +- 16 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 addons/ai/functions/fnc_canThrowGrenade.sqf create mode 100644 addons/ai/functions/fnc_throwGrenade.sqf create mode 100644 addons/common/functions/fnc_isSwimming.sqf create mode 100644 addons/context_actions/functions/fnc_compileGrenades.sqf create mode 100644 addons/context_actions/functions/fnc_getGrenadeActions.sqf create mode 100644 addons/context_actions/functions/fnc_selectThrowPos.sqf create mode 100644 addons/context_actions/ui/grenade_ca.paa diff --git a/AUTHORS.txt b/AUTHORS.txt index d99df2a07..e2b5b275b 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -25,6 +25,7 @@ bux CesarStef classicarma commy2 +das attorney dedmen DeliciousJaffa diaverso diff --git a/addons/ai/XEH_PREP.hpp b/addons/ai/XEH_PREP.hpp index 7bcf825d0..30b749fd8 100644 --- a/addons/ai/XEH_PREP.hpp +++ b/addons/ai/XEH_PREP.hpp @@ -1,8 +1,10 @@ PREP(applySkills); +PREP(canThrowGrenade); PREP(garrison); PREP(handleObjectEdited); PREP(handleSkillsChange); PREP(initMan); PREP(searchBuilding); PREP(suppressiveFire); +PREP(throwGrenade); PREP(unGarrison); diff --git a/addons/ai/XEH_postInit.sqf b/addons/ai/XEH_postInit.sqf index af3537a74..bfdb13af1 100644 --- a/addons/ai/XEH_postInit.sqf +++ b/addons/ai/XEH_postInit.sqf @@ -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; diff --git a/addons/ai/functions/fnc_canThrowGrenade.sqf b/addons/ai/functions/fnc_canThrowGrenade.sqf new file mode 100644 index 000000000..6d7cfdae6 --- /dev/null +++ b/addons/ai/functions/fnc_canThrowGrenade.sqf @@ -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 + * 1: Grenade Magazine (default: "") + * + * Return Value: + * Can Throw Grenade + * + * 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}} diff --git a/addons/ai/functions/fnc_throwGrenade.sqf b/addons/ai/functions/fnc_throwGrenade.sqf new file mode 100644 index 000000000..4a427fb70 --- /dev/null +++ b/addons/ai/functions/fnc_throwGrenade.sqf @@ -0,0 +1,129 @@ +#include "script_component.hpp" +/* + * Author: mharis001 + * Makes the unit throw a grenade at the given position. + * + * Arguments: + * 0: Unit + * 1: Grenade Magazine + * 2: Position ASL + * + * 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; diff --git a/addons/common/XEH_PREP.hpp b/addons/common/XEH_PREP.hpp index 8af98d585..a71b40723 100644 --- a/addons/common/XEH_PREP.hpp +++ b/addons/common/XEH_PREP.hpp @@ -42,6 +42,7 @@ PREP(initSliderEdit); PREP(isInScreenshotMode); PREP(isPlacementActive); PREP(isRemoteControlled); +PREP(isSwimming); PREP(isUnitFFV); PREP(isVLS); PREP(isWeapon); diff --git a/addons/common/functions/fnc_isSwimming.sqf b/addons/common/functions/fnc_isSwimming.sqf new file mode 100644 index 000000000..1c2422f5d --- /dev/null +++ b/addons/common/functions/fnc_isSwimming.sqf @@ -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 + * + * Return Value: + * Is Swimming + * + * 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"] diff --git a/addons/context_actions/CfgContext.hpp b/addons/context_actions/CfgContext.hpp index 05b4d353c..8f6f023d3 100644 --- a/addons/context_actions/CfgContext.hpp +++ b/addons/context_actions/CfgContext.hpp @@ -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); diff --git a/addons/context_actions/XEH_PREP.hpp b/addons/context_actions/XEH_PREP.hpp index ba746a072..a4208c039 100644 --- a/addons/context_actions/XEH_PREP.hpp +++ b/addons/context_actions/XEH_PREP.hpp @@ -9,7 +9,9 @@ PREP(canRepairVehicles); PREP(canToggleSurrender); PREP(canUnloadViV); PREP(copyVehicleAppearance); +PREP(compileGrenades); PREP(getArtilleryActions); +PREP(getGrenadeActions); PREP(healUnits); PREP(openEditableObjectsDialog); PREP(pasteVehicleAppearance); @@ -17,6 +19,7 @@ PREP(rearmVehicles); PREP(refuelVehicles); PREP(repairVehicles); PREP(selectArtilleryPos); +PREP(selectThrowPos); PREP(setBehaviour); PREP(setCombatMode); PREP(setFormation); diff --git a/addons/context_actions/XEH_preStart.sqf b/addons/context_actions/XEH_preStart.sqf index 022888575..30f189a74 100644 --- a/addons/context_actions/XEH_preStart.sqf +++ b/addons/context_actions/XEH_preStart.sqf @@ -1,3 +1,5 @@ #include "script_component.hpp" #include "XEH_PREP.hpp" + +call FUNC(compileGrenades); diff --git a/addons/context_actions/functions/fnc_compileGrenades.sqf b/addons/context_actions/functions/fnc_compileGrenades.sqf new file mode 100644 index 000000000..bb8e97de3 --- /dev/null +++ b/addons/context_actions/functions/fnc_compileGrenades.sqf @@ -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]; diff --git a/addons/context_actions/functions/fnc_getGrenadeActions.sqf b/addons/context_actions/functions/fnc_getGrenadeActions.sqf new file mode 100644 index 000000000..32f813d92 --- /dev/null +++ b/addons/context_actions/functions/fnc_getGrenadeActions.sqf @@ -0,0 +1,54 @@ +#include "script_component.hpp" +/* + * Author: mharis001 + * Returns children actions for grenades in unit inventories. + * + * Arguments: + * N: Objects + * + * Return Value: + * Actions + * + * 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 diff --git a/addons/context_actions/functions/fnc_selectThrowPos.sqf b/addons/context_actions/functions/fnc_selectThrowPos.sqf new file mode 100644 index 000000000..d2852232b --- /dev/null +++ b/addons/context_actions/functions/fnc_selectThrowPos.sqf @@ -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 + * 1: Magazine + * + * 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); diff --git a/addons/context_actions/stringtable.xml b/addons/context_actions/stringtable.xml index 2cabef9b7..c3b865190 100644 --- a/addons/context_actions/stringtable.xml +++ b/addons/context_actions/stringtable.xml @@ -169,6 +169,15 @@ 并非所有的车辆都在射程之内,无法开火 並非所有的車輛都在射程之內,無法開火 + + Throw Grenade + + + Throw %1 + + + %1 unit(s) were too far away from the position + Captives Gefangene diff --git a/addons/context_actions/ui/grenade_ca.paa b/addons/context_actions/ui/grenade_ca.paa new file mode 100644 index 0000000000000000000000000000000000000000..9c4034cc9fc0a7bf00423f935d405a206cb799bc GIT binary patch literal 5625 zcmcgw4Nz3q6~0S$VYP!Sx+@5hWyq30lc2#+Q-eH)&KepVH)gk`O;vuPHAWVbbWA%A zt|l3^ooOI6no-QGRcofsw1DVqMn*yKCuoPX!B%hym|&q(g@r7#AaA)n=ic{rmt{w> zX?hv8vcG%9k9lPc^#y1$^grh94a9G=*)+lc;GKI} z^ft8qI{g1((1HDJHi7(j4eA&6act1!j+x(+#&~q-OpYtSm*@Sz$9G5s2J7d4DZ)Zj z7Usta4Q1gP#-W0y$Pgp{J8V4m&sXA){CZ7mmSd=i>4VB6H1~V$KoK#q0ONl?yU8W4 z>tX$0G-zJgc)^LW$oeb}^ugc!@A;E2DYdFOUqYF*MNNXzjkE0YB*nu)oN3-{8Xw@4(1InT-{=y7KkWGA#?c3rTYM=D0 zHmi)ABk;Wi8uxzhPt*@L{y)0X`|6plVo8xFGrn)Ld;(wVDj4jg8Fnwq4H}IGpV$<> z3kKkuW4KX%FAyu>%E)9H8|VZmvpn%Pq5ba{M~znd8XKg~u%9K&`eng|`9S?}{O7sN zc27pp%S^7(7#{W2CAFs=XhUoT2XTWJ+`a*~k{`0V9hrNK_=#@j+m89&ZPLmkTFw7uA5R+tq_qORn*?PGHL;it;!H+ZrSuALQTAyuP3b zrULs*7g!ald)k4U1#6yoA|D-fv;l6;MBTSkcP)MYoNPz19_?~Zw3cpS`p7>- zKNQce7+!O8(Xx8f2m0zzc_>bxCsw<5WWp-N-62pP6@M5VHpJn^|47T-`A6me|KkDv zi64m3xnoYomTf+iBYnH-RqE-K`OkX=*3XP1wZ@)BYi{CuHvY^@Kpv`}_-489nzM|J zfAIc@defQzDP3Z5y;#WRH%xz9(q-yt_+7afsAp64C-sm0)$lU!8+NA8_}@dnuovcM4L`pGp9iPQKbYHezMERb zl6S=7m(jir_D`6fLS@kW-@8xz2#P$L9k?DA?GsDV7Ow1J`amdDpX?(Q@6xfgOb+_d zpl^bb!7yJhlkCTx62F-1kS=rmF!fv}f%55$|8zLtO=rQ7HPb}6PF!QTb9J;FO&;Q-1*jWI__lo!<}f!Abh1B|NX~w<>Mq} zye$?hD?w;6wEe&MXOr9td4~9baF7TESdJq)Ktx&Sh6*v zh%J-+c&&EHz|y$(<2)_IV%5(XZ65Gl>gLuVZrO`}ncNGny#2dqSKSJhW+CWWjQvuf zJ=-o8^~hyQtJTIog=g6GZMRTv(5p%0D5XE6!-pUM#xM39jgekT@%sl%X>JjeWxpJ7 zL;$;M%R2T%vtVJ?BYa!LduZ}S=g;$Nb6A;#dxV-Eh9nT} zb;Y0hd{4PFT8)s!7k{IB;;VAcVFfQc<$ygGkA<*ipl4#xH38h4Lj6BeJn5RFN~ljy zOJ7*Pe%8QkC!wBJy+S0XVn3n&PPHkr1|jpGP~WhB!*(A#2cT`xhieY2RZQMM@|sL5 z`L)snE!6)Ecb7Y}t2hs>VVO2I!m&4_^c}=->(mqnXUMX*$s1RyxhO7&io_2 zQ{C|S+lrj-Mh}5)_#}D$kSXtL&?h9Eeg2Fx_CwF0oq!uc!b&)!|ciW1=-l?R8JOn7&S@H+3Cd_nVBOaUO(Po&Hsf zqwxEG%2f0>T%s}L7oQjv{Dc0wXxE26wk-5I9Z5W#VZSNbAHx1P%^p6tVdERE#Yee4 zI880xGm)Xb)vxqV|6)d7Q$r{028rFUI|>*Zw)uT{+P9LVqvoPM@ij(UrO}XCpFRHP z^!|uurT=3?2a?+1bcFFk_(ThT0u