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 000000000..9c4034cc9 Binary files /dev/null and b/addons/context_actions/ui/grenade_ca.paa differ diff --git a/addons/main/script_mod.hpp b/addons/main/script_mod.hpp index 965d7c1ed..0c262f793 100644 --- a/addons/main/script_mod.hpp +++ b/addons/main/script_mod.hpp @@ -11,7 +11,7 @@ // MINIMAL required version for the Mod. Components can specify others.. #define REQUIRED_VERSION 2.04 -#define REQUIRED_CBA_VERSION {3,14,0} +#define REQUIRED_CBA_VERSION {3,15,5} #ifdef COMPONENT_BEAUTIFIED #define COMPONENT_NAME QUOTE(ZEN - COMPONENT_BEAUTIFIED)