mirror of
https://github.com/acemod/ACE3.git
synced 2024-08-30 18:23:18 +00:00
Add Advanced Throwing (#3477)
* Add Advanced Throwing * Add feature documentation
This commit is contained in:
parent
9a0d569c56
commit
f8dfa4f933
1
addons/advancedthrowing/$PBOPREFIX$
Normal file
1
addons/advancedthrowing/$PBOPREFIX$
Normal file
@ -0,0 +1 @@
|
||||
z\ace\addons\advancedthrowing
|
40
addons/advancedthrowing/ACE_Settings.hpp
Normal file
40
addons/advancedthrowing/ACE_Settings.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
class ACE_Settings {
|
||||
class GVAR(enabled) {
|
||||
category = CSTRING(Category);
|
||||
displayName = CSTRING(Enable_DisplayName);
|
||||
description = CSTRING(Enable_Description);
|
||||
typeName = "BOOL";
|
||||
value = 1;
|
||||
isClientSettable = 1;
|
||||
};
|
||||
class GVAR(showThrowArc) {
|
||||
category = CSTRING(Category);
|
||||
displayName = CSTRING(ShowThrowArc_DisplayName);
|
||||
description = CSTRING(ShowThrowArc_Description);
|
||||
typeName = "BOOL";
|
||||
value = 1;
|
||||
isClientSettable = 1;
|
||||
};
|
||||
class GVAR(showMouseControls) {
|
||||
category = CSTRING(Category);
|
||||
displayName = CSTRING(ShowMouseControls_DisplayName);
|
||||
description = CSTRING(ShowMouseControls_Description);
|
||||
typeName = "BOOL";
|
||||
value = 1;
|
||||
isClientSettable = 1;
|
||||
};
|
||||
class GVAR(enablePickUp) {
|
||||
category = CSTRING(Category);
|
||||
displayName = CSTRING(EnablePickUp_DisplayName);
|
||||
description = CSTRING(EnablePickUp_Description);
|
||||
typeName = "BOOL";
|
||||
value = 1;
|
||||
};
|
||||
class GVAR(enablePickUpAttached) {
|
||||
category = CSTRING(Category);
|
||||
displayName = CSTRING(EnablePickUpAttached_DisplayName);
|
||||
description = CSTRING(EnablePickUpAttached_Description);
|
||||
typeName = "BOOL";
|
||||
value = 1;
|
||||
};
|
||||
};
|
17
addons/advancedthrowing/CfgEventHandlers.hpp
Normal file
17
addons/advancedthrowing/CfgEventHandlers.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
class Extended_PreStart_EventHandlers {
|
||||
class ADDON {
|
||||
init = QUOTE(call COMPILE_FILE(XEH_preStart));
|
||||
};
|
||||
};
|
||||
|
||||
class Extended_PreInit_EventHandlers {
|
||||
class ADDON {
|
||||
init = QUOTE(call COMPILE_FILE(XEH_preInit));
|
||||
};
|
||||
};
|
||||
|
||||
class Extended_PostInit_EventHandlers {
|
||||
class ADDON {
|
||||
clientInit = QUOTE(call COMPILE_FILE(XEH_postInitClient));
|
||||
};
|
||||
};
|
72
addons/advancedthrowing/CfgVehicles.hpp
Normal file
72
addons/advancedthrowing/CfgVehicles.hpp
Normal file
@ -0,0 +1,72 @@
|
||||
class CBA_Extended_EventHandlers;
|
||||
|
||||
class CfgVehicles {
|
||||
class ACE_Module;
|
||||
class GVAR(Module): ACE_Module {
|
||||
author = ECSTRING(common,ACETeam);
|
||||
category = "ACE";
|
||||
displayName = CSTRING(Category);
|
||||
function = QFUNC(moduleInit);
|
||||
scope = 2;
|
||||
isGlobal = 1;
|
||||
icon = QPATHTOF(UI\Icon_Module_AdvancedThrowing_ca.paa);
|
||||
class Arguments {
|
||||
class enabled {
|
||||
displayName = CSTRING(Enable_DisplayName);
|
||||
description = CSTRING(Enable_Description);
|
||||
typeName = "BOOL";
|
||||
defaultValue = 1;
|
||||
};
|
||||
class showThrowArc {
|
||||
displayName = CSTRING(ShowThrowArc_DisplayName);
|
||||
description = CSTRING(ShowThrowArc_Description);
|
||||
typeName = "BOOL";
|
||||
defaultValue = 1;
|
||||
};
|
||||
class showMouseControls {
|
||||
displayName = CSTRING(ShowMouseControls_DisplayName);
|
||||
description = CSTRING(ShowMouseControls_Description);
|
||||
typeName = "BOOL";
|
||||
defaultValue = 1;
|
||||
};
|
||||
class enablePickUp {
|
||||
displayName = CSTRING(EnablePickUp_DisplayName);
|
||||
description = CSTRING(EnablePickUp_Description);
|
||||
typeName = "BOOL";
|
||||
defaultValue = 1;
|
||||
};
|
||||
class enablePickUpAttached {
|
||||
displayName = CSTRING(EnablePickUpAttached_DisplayName);
|
||||
description = CSTRING(EnablePickUpAttached_Description);
|
||||
typeName = "BOOL";
|
||||
defaultValue = 1;
|
||||
};
|
||||
};
|
||||
class ModuleDescription {
|
||||
description = CSTRING(Module_Description);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
class Items_base_F;
|
||||
class GVAR(pickUpHelper): Items_base_F {
|
||||
author = ECSTRING(common,ACETeam);
|
||||
displayName = "ACE Throwable Pick Up Helper";
|
||||
model = "\a3\weapons_f\dummyweapon.p3d";
|
||||
scope = 1;
|
||||
|
||||
class ACE_Actions {
|
||||
class GVAR(pickUp) {
|
||||
displayName = CSTRING(PickUp);
|
||||
condition = QUOTE(_player call FUNC(canPrepare));
|
||||
statement = QUOTE(_this call FUNC(pickUp));
|
||||
distance = 1.8; // Requires >1.7 to work when standing with weapon on back
|
||||
icon = "\a3\ui_f\data\igui\cfg\actions\obsolete\ui_action_takemine_ca.paa";
|
||||
};
|
||||
};
|
||||
|
||||
class EventHandlers {
|
||||
class CBA_Extended_EventHandlers: CBA_Extended_EventHandlers {};
|
||||
};
|
||||
};
|
||||
};
|
11
addons/advancedthrowing/README.md
Normal file
11
addons/advancedthrowing/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
ace_advancedthrowing
|
||||
===================
|
||||
|
||||
Integrates advanced throwing by [Dslyecxi](https://github.com/dslyecxi).
|
||||
|
||||
|
||||
## Maintainers
|
||||
|
||||
The people responsible for merging changes to this component or answering potential questions.
|
||||
|
||||
- [Jonpas](https://github.com/jonpas)
|
BIN
addons/advancedthrowing/UI/Icon_Module_AdvancedThrowing_ca.paa
Normal file
BIN
addons/advancedthrowing/UI/Icon_Module_AdvancedThrowing_ca.paa
Normal file
Binary file not shown.
17
addons/advancedthrowing/XEH_PREP.hpp
Normal file
17
addons/advancedthrowing/XEH_PREP.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
PREP(canPrepare);
|
||||
PREP(canThrow);
|
||||
PREP(drawArc);
|
||||
PREP(drawThrowable);
|
||||
PREP(exitThrowMode);
|
||||
PREP(getMuzzle);
|
||||
PREP(moduleInit);
|
||||
PREP(onKeyDown);
|
||||
PREP(onMouseButtonDown);
|
||||
PREP(onMouseScroll);
|
||||
PREP(pickUp);
|
||||
PREP(prepare);
|
||||
PREP(prime);
|
||||
PREP(renderPickUpInteraction);
|
||||
PREP(throw);
|
||||
PREP(throwFiredXEH);
|
||||
PREP(updateControlsHint);
|
107
addons/advancedthrowing/XEH_postInitClient.sqf
Normal file
107
addons/advancedthrowing/XEH_postInitClient.sqf
Normal file
@ -0,0 +1,107 @@
|
||||
#include "script_component.hpp"
|
||||
|
||||
// Exit on HC
|
||||
if (!hasInterface) exitWith {};
|
||||
|
||||
// Ammo/Magazines look-up hash for correctness of initSpeed
|
||||
GVAR(ammoMagLookup) = call CBA_fnc_createNamespace;
|
||||
{
|
||||
{
|
||||
private _ammo = getText (configFile >> "CfgMagazines" >> _x >> "ammo");
|
||||
GVAR(ammoMagLookup) setVariable [_ammo, _x];
|
||||
} count (getArray (configFile >> "CfgWeapons" >> "Throw" >> _x >> "magazines"));
|
||||
nil
|
||||
} count getArray (configFile >> "CfgWeapons" >> "Throw" >> "muzzles");
|
||||
|
||||
|
||||
// Add keybinds
|
||||
["ACE3 Weapons", QGVAR(prepare), localize LSTRING(Prepare), {
|
||||
// Condition
|
||||
if (!([ACE_player] call FUNC(canPrepare))) exitWith {false};
|
||||
|
||||
// Statement
|
||||
[ACE_player] call FUNC(prepare);
|
||||
|
||||
true
|
||||
}, {false}, [34, [true, false, false]], false] call CBA_fnc_addKeybind; // Shift + G
|
||||
|
||||
["ACE3 Weapons", QGVAR(dropModeToggle), localize LSTRING(DropModeToggle), {
|
||||
// Condition
|
||||
if !(ACE_player getVariable [QGVAR(inHand), false]) exitWith {false};
|
||||
|
||||
// Statement
|
||||
private _currentDropMode = ACE_player getVariable [QGVAR(dropMode), false];
|
||||
ACE_player setVariable [QGVAR(dropMode), !_currentDropMode];
|
||||
|
||||
ACE_player setVariable [QGVAR(throwType), THROW_TYPE_DEFAULT]; // Reset for consistency when opening
|
||||
call FUNC(updateControlsHint); // Change controls hint for MMB
|
||||
true
|
||||
}, {false}, [34, [false, true, false]], false] call CBA_fnc_addKeybind; // Ctrl + G
|
||||
|
||||
["ACE3 Weapons", QGVAR(dropModeHold), localize LSTRING(DropModeHold), {
|
||||
// Condition
|
||||
if !(ACE_player getVariable [QGVAR(inHand), false]) exitWith {false};
|
||||
|
||||
// Statement
|
||||
ACE_player setVariable [QGVAR(dropMode), true];
|
||||
ACE_player setVariable [QGVAR(throwType), THROW_TYPE_DEFAULT]; // Reset for consistency when opening
|
||||
call FUNC(updateControlsHint); // Change controls hint for MMB
|
||||
true
|
||||
}, {
|
||||
// Condition
|
||||
if !(ACE_player getVariable [QGVAR(inHand), false]) exitWith {false};
|
||||
|
||||
// Statement
|
||||
ACE_player setVariable [QGVAR(dropMode), false];
|
||||
call FUNC(updateControlsHint); // Change controls hint for MMB
|
||||
true
|
||||
}, [0, [false, false, false]], false] call CBA_fnc_addKeybind; // Empty
|
||||
|
||||
|
||||
// Event handlers
|
||||
["unit", {
|
||||
[_this select 1, "Player changed"] call FUNC(exitThrowMode);
|
||||
}] call CBA_fnc_addPlayerEventhandler;
|
||||
|
||||
["ace_interactMenuOpened", {
|
||||
// Exit if advanced throwing is disabled (pick up only supports advanced throwing)
|
||||
if (!GVAR(enabled)) exitWith {};
|
||||
|
||||
if (ACE_player getVariable [QGVAR(inHand), false]) then {
|
||||
[ACE_player, "Interact menu opened"] call FUNC(exitThrowMode);
|
||||
} else {
|
||||
params ["_interactionType"];
|
||||
// Ignore self-interaction menu, when in vehicle and when pick up is disabled
|
||||
if (GVAR(enablePickUp) && {_interactionType == 0} && {vehicle ACE_player == ACE_player}) then {
|
||||
// Show pick up actions on CfgAmmo's
|
||||
call FUNC(renderPickUpInteraction);
|
||||
};
|
||||
};
|
||||
}] call CBA_fnc_addEventHandler;
|
||||
|
||||
|
||||
// Fired XEH
|
||||
[QGVAR(throwFiredXEH), FUNC(throwFiredXEH)] call CBA_fnc_addEventHandler;
|
||||
|
||||
|
||||
// Display handlers
|
||||
["KeyDown", {_this call FUNC(onKeyDown)}] call CBA_fnc_addDisplayHandler;
|
||||
["MouseButtonDown", {_this call FUNC(onMouseButtonDown)}] call CBA_fnc_addDisplayHandler;
|
||||
["MouseZChanged", {_this call FUNC(onMouseScroll)}] call CBA_fnc_addDisplayHandler;
|
||||
|
||||
|
||||
#ifdef DRAW_THROW_PATH
|
||||
GVAR(predictedPath) = [];
|
||||
GVAR(flightPath) = [];
|
||||
|
||||
addMissionEventHandler ["Draw3D", { // Blue is predicted before throw, red is real
|
||||
{
|
||||
_x params ["", "_newTrajAGL"];
|
||||
drawIcon3D ["\a3\ui_f\data\gui\cfg\hints\icon_text\group_1_ca.paa", [0,0,1,1], _newTrajAGL, 1, 1, 0, "", 2];
|
||||
} forEach GVAR(predictedPath);
|
||||
{
|
||||
_newTrajAGL = _x;
|
||||
drawIcon3D ["\a3\ui_f\data\gui\cfg\hints\icon_text\group_1_ca.paa", [1,0,0,1], _newTrajAGL, 1, 1, 0, "", 2];
|
||||
} forEach GVAR(flightPath)
|
||||
}];
|
||||
#endif
|
7
addons/advancedthrowing/XEH_preInit.sqf
Normal file
7
addons/advancedthrowing/XEH_preInit.sqf
Normal file
@ -0,0 +1,7 @@
|
||||
#include "script_component.hpp"
|
||||
|
||||
ADDON = false;
|
||||
|
||||
#include "XEH_PREP.hpp"
|
||||
|
||||
ADDON = true;
|
3
addons/advancedthrowing/XEH_preStart.sqf
Normal file
3
addons/advancedthrowing/XEH_preStart.sqf
Normal file
@ -0,0 +1,3 @@
|
||||
#include "script_component.hpp"
|
||||
|
||||
#include "XEH_PREP.hpp"
|
19
addons/advancedthrowing/config.cpp
Normal file
19
addons/advancedthrowing/config.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "script_component.hpp"
|
||||
|
||||
class CfgPatches {
|
||||
class ADDON {
|
||||
name = COMPONENT_NAME;
|
||||
units[] = {};
|
||||
weapons[] = {};
|
||||
requiredVersion = REQUIRED_VERSION;
|
||||
requiredAddons[] = {"ace_common", "ace_weaponselect"};
|
||||
author = ECSTRING(common,ACETeam);
|
||||
authors[] = {"Jonpas", "Dslyecxi", "Zapat"};
|
||||
url = ECSTRING(main,URL);
|
||||
VERSION_CONFIG;
|
||||
};
|
||||
};
|
||||
|
||||
#include "ACE_Settings.hpp"
|
||||
#include "CfgEventHandlers.hpp"
|
||||
#include "CfgVehicles.hpp"
|
31
addons/advancedthrowing/functions/fnc_canPrepare.sqf
Normal file
31
addons/advancedthrowing/functions/fnc_canPrepare.sqf
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Author: Jonpas
|
||||
* Checks if a throwable can be prepared.
|
||||
*
|
||||
* Arguments:
|
||||
* 0: Unit <OBJECT>
|
||||
*
|
||||
* Return Value:
|
||||
* Can Prepare <BOOL>
|
||||
*
|
||||
* Example:
|
||||
* [unit] call ace_advancedthrowing_fnc_canPrepare
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
params ["_unit"];
|
||||
|
||||
GVAR(enabled) &&
|
||||
|
||||
#ifndef DEBUG_MODE_FULL
|
||||
{_unit getVariable [QGVAR(lastThrownTime), CBA_missionTime - 3] < CBA_missionTime - 2} && // Prevent throwing in quick succession
|
||||
#else
|
||||
{true} &&
|
||||
#endif
|
||||
|
||||
{!(call EFUNC(common,isFeatureCameraActive))} &&
|
||||
{!EGVAR(common,isReloading)} &&
|
||||
{[_unit, objNull, ["isNotInside", "isNotSitting"/*, "isNotOnLadder"*/]] call EFUNC(common,canInteractWith)} && // Ladder needs positioning fixes on throw
|
||||
{_unit call CBA_fnc_canUseWeapon} // Disable in non-FFV seats due to surface detection issues
|
31
addons/advancedthrowing/functions/fnc_canThrow.sqf
Normal file
31
addons/advancedthrowing/functions/fnc_canThrow.sqf
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Author: Jonpas
|
||||
* Checks if a throwable can be thrown.
|
||||
*
|
||||
* Arguments:
|
||||
* 0: Unit <OBJECT>
|
||||
*
|
||||
* Return Value:
|
||||
* Can Throw <BOOL>
|
||||
*
|
||||
* Example:
|
||||
* [unit] call ace_advancedthrowing_fnc_canThrow
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
params ["_unit"];
|
||||
|
||||
if !(_unit getVariable [QGVAR(inHand), false]) exitWith {false};
|
||||
|
||||
if (vehicle _unit != _unit) exitWith {
|
||||
private _startPos = eyePos _unit;
|
||||
private _aimLinePos = AGLToASL (positionCameraToWorld [0, 0, 1]);
|
||||
private _intersections = lineIntersectsSurfaces [_startPos, _aimLinePos, _unit, objNull, false];
|
||||
//TRACE_1("Intersections",_intersections);
|
||||
|
||||
(_intersections select {(vehicle _unit) in (_x select 3)}) isEqualTo []
|
||||
};
|
||||
|
||||
true
|
87
addons/advancedthrowing/functions/fnc_drawArc.sqf
Normal file
87
addons/advancedthrowing/functions/fnc_drawArc.sqf
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Author: Zapat, Dslyecxi, Jonpas
|
||||
* Draws throw arc.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* Flight path (just for debug) <ARRAY>
|
||||
*
|
||||
* Example:
|
||||
* call ace_advancedthrowing_fnc_drawArc
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
// Disable drawing when intersecting with the vehicle
|
||||
if !([ACE_player] call FUNC(canThrow)) exitWith {
|
||||
drawIcon3D ["\a3\ui_f\data\igui\cfg\actions\obsolete\ui_action_cancel_manualfire_ca.paa", [1, 0, 0, 1], positionCameraToWorld [0, 0, 1], 1, 1, 0, "", 1];
|
||||
};
|
||||
|
||||
private _activeThrowable = ACE_player getVariable [QGVAR(activeThrowable), objNull];
|
||||
|
||||
// Exit during switches and similar where object can be null for a very short amount of time
|
||||
if (isNull _activeThrowable) exitWith {};
|
||||
|
||||
private _dropMode = ACE_player getVariable [QGVAR(dropMode), false];
|
||||
private _throwType = ACE_player getVariable [QGVAR(throwType), THROW_TYPE_DEFAULT];
|
||||
private _throwSpeed = ACE_player getVariable [QGVAR(throwSpeed), THROW_SPEED_DEFAULT];
|
||||
|
||||
private _direction = [THROWSTYLE_NORMAL_DIR, THROWSTYLE_HIGH_DIR] select (_throwType == "high" || {_dropMode});
|
||||
private _velocity = [_throwSpeed, _throwSpeed / THROWSTYLE_HIGH_VEL_COEF / 1.25] select (_throwType == "high");
|
||||
_velocity = [_velocity, THROWSTYLE_DROP_VEL] select _dropMode;
|
||||
|
||||
private _viewStart = AGLToASL (positionCameraToWorld [0, 0, 0]);
|
||||
private _viewEnd = AGLToASL (positionCameraToWorld _direction);
|
||||
|
||||
private _initialVelocity = (vectorNormalized (_viewEnd vectorDiff _viewStart)) vectorMultiply (_velocity);
|
||||
private _prevTrajASL = getPosASLVisual _activeThrowable;
|
||||
|
||||
private _pathData = [];
|
||||
|
||||
for "_i" from 0.05 to 1.45 step 0.1 do {
|
||||
private _newTrajASL = (getPosASLVisual _activeThrowable) vectorAdd (_initialVelocity vectorMultiply _i) vectorAdd ([0, 0, -4.9] vectorMultiply (_i * _i));
|
||||
private _cross = 0;
|
||||
|
||||
if (_newTrajASL distance (getPosASLVisual ACE_player) <= 20) then {
|
||||
if ((ASLToATL _newTrajASL) select 2 <= 0) then {
|
||||
_cross = 1
|
||||
} else {
|
||||
// Even vanilla throwables go through glass, only "GEOM" LOD will stop it but that will also stop it when there is glass in a window
|
||||
if (lineIntersects [_prevTrajASL, _newTrajASL]) then {
|
||||
_cross = 2;
|
||||
};
|
||||
};
|
||||
|
||||
private _iDim = linearConversion [20, 0, _newTrajASL distance (getPosASLVisual ACE_player), 0.3, 2.5, true];
|
||||
private _alpha = linearConversion [20, 0, _newTrajASL distance (getPosASLVisual ACE_player), 0.05, 0.7, true];
|
||||
private _movePerc = linearConversion [3, 0, vectorMagnitude (velocity ACE_player), 0, 1, true];
|
||||
_alpha = _alpha * _movePerc;
|
||||
|
||||
private _col = [ [1, 1, 1, _alpha], [0, 1, 0, _alpha], [1, 0, 0, _alpha] ] select _cross;
|
||||
|
||||
if (_cross != 2 && {lineIntersects [eyePos ACE_player, _newTrajASL]}) then {
|
||||
_col set [3, 0.1]
|
||||
};
|
||||
|
||||
_pathData pushBack [_col, ASLToAGL _newTrajASL, _iDim];
|
||||
};
|
||||
|
||||
if (_cross > 0) exitWith {};
|
||||
|
||||
_prevTrajASL = _newTrajASL;
|
||||
};
|
||||
|
||||
reverse _pathData;
|
||||
// To get the sort order correct from our POV, particularly when using outlined dots
|
||||
{
|
||||
_x params ["_col", "_newTrajAGL", "_iDim"];
|
||||
drawIcon3D ["\a3\ui_f\data\gui\cfg\hints\icon_text\group_1_ca.paa", _col, _newTrajAGL, _iDim, _iDim, 0, "", 2];
|
||||
#ifdef DEBUG_MODE_FULL
|
||||
drawIcon3D ["", _col, _newTrajAGL, _iDim, _iDim, 0, str (ACE_player distance _newTrajAGL), 2, 0.05, "RobotoCondensed"];
|
||||
#endif
|
||||
} forEach _pathData;
|
||||
|
||||
_pathData
|
135
addons/advancedthrowing/functions/fnc_drawThrowable.sqf
Normal file
135
addons/advancedthrowing/functions/fnc_drawThrowable.sqf
Normal file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Author: Dslyecxi, Jonpas, SilentSpike
|
||||
* Handles drawing the currently selected or cooked throwable.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
*
|
||||
* Example:
|
||||
* call ace_advancedthrowing_fnc_drawThrowable
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
if (dialog || {!(ACE_player getVariable [QGVAR(inHand), false])} || {!([ACE_player] call FUNC(canPrepare))}) exitWith {
|
||||
[ACE_player, "In dialog or no throwable in hand or cannot prepare throwable"] call FUNC(exitThrowMode);
|
||||
};
|
||||
|
||||
private _primed = ACE_player getVariable [QGVAR(primed), false];
|
||||
private _activeThrowable = ACE_player getVariable [QGVAR(activeThrowable), objNull];
|
||||
|
||||
// Exit if throwable died primed in hand
|
||||
if (isNull _activeThrowable && {_primed}) exitWith {
|
||||
[ACE_player, "Throwable died primed in hand"] call FUNC(exitThrowMode);
|
||||
};
|
||||
|
||||
private _throwable = currentThrowable ACE_player;
|
||||
|
||||
// Inventory check
|
||||
if (_throwable isEqualTo [] && {!_primed}) exitWith {
|
||||
[ACE_player, "No valid throwables"] call FUNC(exitThrowMode);
|
||||
};
|
||||
|
||||
private _throwableMag = _throwable param [0, "#none"];
|
||||
|
||||
// Get correct throw power for primed grenade
|
||||
if (_primed) then {
|
||||
private _ammoType = typeOf _activeThrowable;
|
||||
_throwableMag = GVAR(ammoMagLookup) getVariable _ammoType;
|
||||
if (isNil "_throwableMag") then {
|
||||
// What we're trying to throw must not be a normal throwable because it is not in our lookup hash (e.g. 40mm smoke)
|
||||
// Just use HandGrenade as it has an average initSpeed value
|
||||
_throwableMag = "HandGrenade";
|
||||
};
|
||||
};
|
||||
|
||||
// Some throwables have different classname for magazine and ammo
|
||||
// Primed magazine may be different, read speed before checking primed magazine!
|
||||
private _throwSpeed = getNumber (configFile >> "CfgMagazines" >> _throwableMag >> "initSpeed");
|
||||
|
||||
// Reduce power of throw over shoulder and to sides
|
||||
private _unitDirVisual = getDirVisual ACE_player;
|
||||
private _cameraDir = getCameraViewDirection ACE_player;
|
||||
_cameraDir = (_cameraDir select 0) atan2 (_cameraDir select 1);
|
||||
|
||||
private _phi = abs (_cameraDir - _unitDirVisual) % 360;
|
||||
_phi = [_phi, 360 - _phi] select (_phi > 180);
|
||||
|
||||
private _power = linearConversion [0, 180, _phi - 30, 1, 0.3, true];
|
||||
ACE_player setVariable [QGVAR(throwSpeed), _throwSpeed * _power];
|
||||
|
||||
#ifdef DEBUG_MODE_FULL
|
||||
hintSilent format ["Heading: %1\nPower: %2\nSpeed: %3\nThrowMag: %4", _phi, _power, _throwSpeed * _power, _throwableMag];
|
||||
#endif
|
||||
|
||||
private _throwableType = getText (configFile >> "CfgMagazines" >> _throwableMag >> "ammo");
|
||||
|
||||
if (!([ACE_player] call FUNC(canThrow)) && {!_primed}) exitWith {
|
||||
if (!isNull _activeThrowable) then {
|
||||
deleteVehicle _activeThrowable;
|
||||
};
|
||||
};
|
||||
|
||||
if (isNull _activeThrowable || {(_throwableType != typeOf _activeThrowable) && {!_primed}}) then {
|
||||
if (!isNull _activeThrowable) then {
|
||||
deleteVehicle _activeThrowable;
|
||||
};
|
||||
_activeThrowable = _throwableType createVehicleLocal [0, 0, 0];
|
||||
_activeThrowable enableSimulation false;
|
||||
ACE_player setVariable [QGVAR(activeThrowable), _activeThrowable];
|
||||
};
|
||||
|
||||
// Exit in case of explosion in hand
|
||||
if (isNull _activeThrowable) exitWith {
|
||||
[ACE_player, "No active throwable (explosion in hand)"] call FUNC(exitThrowMode);
|
||||
};
|
||||
|
||||
// Set position
|
||||
private _posHeadRel = ACE_player selectionPosition "head";
|
||||
|
||||
private _leanCoef = (_posHeadRel select 0) - 0.15; // 0.15 counters the base offset
|
||||
// Don't take leaning into account when weapon is lowered due to jiggling when walking side-ways (bandaid)
|
||||
if (abs _leanCoef < 0.15 || {vehicle ACE_player != ACE_player} || {weaponLowered ACE_player}) then {
|
||||
_leanCoef = 0;
|
||||
};
|
||||
|
||||
private _posCameraWorld = AGLToASL (positionCameraToWorld [0, 0, 0]);
|
||||
_posHeadRel = _posHeadRel vectorAdd [-0.03, 0.01, 0.15]; // Bring closer to eyePos value
|
||||
private _posFin = AGLToASL (ACE_player modelToWorldVisual _posHeadRel);
|
||||
|
||||
private _throwType = ACE_player getVariable [QGVAR(throwType), THROW_TYPE_DEFAULT];
|
||||
|
||||
// Orient it nicely, point towards player
|
||||
_activeThrowable setDir (_unitDirVisual + 90);
|
||||
|
||||
private _pitch = [-30, -90] select (_throwType == "high");
|
||||
[_activeThrowable, _pitch, 0] call BIS_fnc_setPitchBank;
|
||||
|
||||
|
||||
if (ACE_player getVariable [QGVAR(dropMode), false]) then {
|
||||
_posFin = _posFin vectorAdd (AGLToASL (positionCameraToWorld [_leanCoef, 0, ACE_player getVariable [QGVAR(dropDistance), DROP_DISTANCE_DEFAULT]]));
|
||||
|
||||
// Even vanilla throwables go through glass, only "GEOM" LOD will stop it but that will also stop it when there is no glass in a window
|
||||
if (lineIntersects [_posCameraWorld, _posFin vectorDiff _posCameraWorld]) then {
|
||||
ACE_player setVariable [QGVAR(dropDistance), ((ACE_player getVariable [QGVAR(dropDistance), DROP_DISTANCE_DEFAULT]) - 0.1) max DROP_DISTANCE_DEFAULT];
|
||||
};
|
||||
} else {
|
||||
private _xAdjustBonus = [0, -0.075] select (_throwType == "high");
|
||||
private _yAdjustBonus = [0, 0.1] select (_throwType == "high");
|
||||
private _cameraOffset = [_leanCoef, 0, 0.3] vectorAdd [-0.1, -0.15, -0.03] vectorAdd [_xAdjustBonus, _yAdjustBonus, 0];
|
||||
|
||||
_posFin = _posFin vectorAdd (AGLToASL (positionCameraToWorld _cameraOffset));
|
||||
|
||||
if (vehicle ACE_player != ACE_player) then {
|
||||
// Counteract vehicle velocity including acceleration
|
||||
private _vectorDiff = (velocity (vehicle ACE_player)) vectorMultiply (time - (ACE_player getVariable [QGVAR(lastTick), time]) + 0.01);
|
||||
_posFin = _posFin vectorAdd _vectorDiff;
|
||||
ACE_player setVariable [QGVAR(lastTick), time];
|
||||
};
|
||||
};
|
||||
|
||||
_activeThrowable setPosASL (_posFin vectorDiff _posCameraWorld);
|
50
addons/advancedthrowing/functions/fnc_exitThrowMode.sqf
Normal file
50
addons/advancedthrowing/functions/fnc_exitThrowMode.sqf
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Author: Dslyecxi, Jonpas
|
||||
* Exits throw mode.
|
||||
*
|
||||
* Arguments:
|
||||
* 0: Unit <OBJECT>
|
||||
* 1: Reason <STRING>
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
*
|
||||
* Example:
|
||||
* [unit, "reason"] call ace_advancedthrowing_fnc_exitThrowMode
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
params ["_unit", "_reason"];
|
||||
TRACE_2("params",_unit,_reason);
|
||||
|
||||
if !(_unit getVariable [QGVAR(inHand), false]) exitWith {};
|
||||
|
||||
#ifdef DEBUG_MODE_FULL
|
||||
systemChat format ["Exit Throw Mode: %1", _reason];
|
||||
#endif
|
||||
|
||||
if !(_unit getVariable [QGVAR(primed), false]) then {
|
||||
deleteVehicle (_unit getVariable [QGVAR(activeThrowable), objNull]);
|
||||
};
|
||||
|
||||
_unit setVariable [QGVAR(inHand), false];
|
||||
_unit setVariable [QGVAR(primed), false];
|
||||
_unit setVariable [QGVAR(activeThrowable), objNull];
|
||||
_unit setVariable [QGVAR(throwType), THROW_TYPE_DEFAULT];
|
||||
_unit setVariable [QGVAR(throwSpeed), THROW_SPEED_DEFAULT];
|
||||
_unit setVariable [QGVAR(dropMode), false];
|
||||
_unit setVariable [QGVAR(dropDistance), DROP_DISTANCE_DEFAULT];
|
||||
|
||||
// Remove controls hint (check if ever enabled is inside the function)
|
||||
call EFUNC(interaction,hideMouseHint);
|
||||
|
||||
// Remove throw action
|
||||
[_unit, "DefaultAction", _unit getVariable [QGVAR(throwAction), -1]] call EFUNC(common,removeActionEventHandler);
|
||||
|
||||
// Remove throw arc draw
|
||||
if (!isNil QGVAR(draw3DHandle)) then {
|
||||
removeMissionEventHandler ["Draw3D", GVAR(draw3DHandle)];
|
||||
GVAR(draw3DHandle) = nil;
|
||||
};
|
25
addons/advancedthrowing/functions/fnc_getMuzzle.sqf
Normal file
25
addons/advancedthrowing/functions/fnc_getMuzzle.sqf
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Author: PabstMirror
|
||||
* Retrieve muzzle name from config.
|
||||
*
|
||||
* Arguments:
|
||||
* 0: Magazine Classname <STRING>
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
*
|
||||
* Example:
|
||||
* "magazine" call ace_advancedthrowing_fnc_getMuzzle
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
params ["_magazineClassname"];
|
||||
|
||||
_magazineClassname = toLower _magazineClassname;
|
||||
private _throwMuzzles = getArray (configFile >> "CfgWeapons" >> "Throw" >> "muzzles");
|
||||
|
||||
_throwMuzzles = _throwMuzzles select {_magazineClassname in ((getArray (configFile >> "CfgWeapons" >> "Throw" >> _x >> "magazines")) apply {toLower _x})};
|
||||
|
||||
[_throwMuzzles select 0, ""] select (_throwMuzzles isEqualTo [])
|
33
addons/advancedthrowing/functions/fnc_moduleInit.sqf
Normal file
33
addons/advancedthrowing/functions/fnc_moduleInit.sqf
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Author: Jonpas
|
||||
* Initializes the Advanced Throwing module.
|
||||
*
|
||||
* Arguments:
|
||||
* 0: Logic <OBJECT>
|
||||
* 1: Synchronized Units <ARRAY>
|
||||
* 2: Module Activated <BOOL>
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
*
|
||||
* Example:
|
||||
* [logic, [unit1, unit2], true] call ace_advancedthrowing_fnc_moduleInit
|
||||
*
|
||||
* Public:
|
||||
* No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
if (!isServer) exitWith {};
|
||||
|
||||
params ["_logic", "_units", "_activated"];
|
||||
|
||||
if (!_activated) exitWith {};
|
||||
|
||||
[_logic, QGVAR(enabled), "enabled"] call EFUNC(common,readSettingFromModule);
|
||||
[_logic, QGVAR(showThrowArc), "showThrowArc"] call EFUNC(common,readSettingFromModule);
|
||||
[_logic, QGVAR(showMouseControls), "showMouseControls"] call EFUNC(common,readSettingFromModule);
|
||||
[_logic, QGVAR(enablePickUp), "enablePickUp"] call EFUNC(common,readSettingFromModule);
|
||||
[_logic, QGVAR(enablePickUpAttached), "enablePickUpAttached"] call EFUNC(common,readSettingFromModule);
|
||||
|
||||
ACE_LOGINFO_1("Advanced Throwing Module Initialized. Enabled: %1",GVAR(enabled));
|
45
addons/advancedthrowing/functions/fnc_onKeyDown.sqf
Normal file
45
addons/advancedthrowing/functions/fnc_onKeyDown.sqf
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Author: Dslyecxi, Jonpas
|
||||
* Key down event.
|
||||
*
|
||||
* Arguments:
|
||||
* 0: Control <CONTROL>
|
||||
* 1: Key <NUMBER>
|
||||
* 2: Shift <BOOL>
|
||||
* 3: Ctrl <BOOL>
|
||||
* 4: Alt <BOOL>
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
*
|
||||
* Example:
|
||||
* [control, 5, false, true, false] call ace_advancedthrowing_fnc_onKeyDown
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
if !(ACE_player getVariable [QGVAR(inHand), false]) exitWith {false};
|
||||
|
||||
params ["", "_key", "_shift", "_ctrl", "_alt"];
|
||||
|
||||
// Exit if any of the action keys is pressed
|
||||
{
|
||||
if (_key in _x) exitWith {
|
||||
[ACE_player, "Pressed a key that cycles us out of throwables"] call FUNC(exitThrowMode);
|
||||
};
|
||||
} forEach [
|
||||
actionKeys "ReloadMagazine",
|
||||
actionKeys "Handgun",
|
||||
actionKeys "Binoculars",
|
||||
actionKeys "SwitchWeapon",
|
||||
actionKeys "Optics",
|
||||
actionKeys "NextWeapon",
|
||||
actionKeys "PrevWeapon",
|
||||
actionKeys "OpticsTemp",
|
||||
actionKeys "SwitchPrimary",
|
||||
actionKeys "SwitchHandgun",
|
||||
actionKeys "SwitchSecondary"
|
||||
];
|
||||
|
||||
false
|
46
addons/advancedthrowing/functions/fnc_onMouseButtonDown.sqf
Normal file
46
addons/advancedthrowing/functions/fnc_onMouseButtonDown.sqf
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Author: Dslyecxi, Jonpas
|
||||
* Mouse button down event.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
*
|
||||
* Example:
|
||||
* [] call ace_advancedthrowing_fnc_onMouseButtonDown
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
if !(ACE_player getVariable [QGVAR(inHand), false]) exitWith {};
|
||||
|
||||
params ["", "_key"];
|
||||
|
||||
// Left mouse button
|
||||
// "DefaultAction" doesn't get executed when in driver seat or in FFV seat with weapon lowered
|
||||
if (_key == 0) exitWith {
|
||||
if (!isNull (ACE_player getVariable [QGVAR(activeThrowable), objNull])) then {
|
||||
// Look gets automatically pointed at weapon direction on first LMB press when in FFV seat, require weapon to be up if in vehicle
|
||||
private _inVehicle = vehicle ACE_player != ACE_player;
|
||||
if (!_inVehicle || {_inVehicle && {!weaponLowered ACE_player}}) then {
|
||||
[ACE_player] call FUNC(throw);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
private _primed = ACE_player getVariable [QGVAR(primed), false];
|
||||
|
||||
// Right mouse button
|
||||
if (_key == 1) exitWith {
|
||||
if (!_primed) then {
|
||||
[ACE_player, "Storing throwable"] call FUNC(exitThrowMode);
|
||||
};
|
||||
};
|
||||
|
||||
// Middle mouse button
|
||||
if (_key == 2 && {!_primed}) exitWith {
|
||||
[ACE_player, true] call FUNC(prime);
|
||||
};
|
48
addons/advancedthrowing/functions/fnc_onMouseScroll.sqf
Normal file
48
addons/advancedthrowing/functions/fnc_onMouseScroll.sqf
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Author: Dslyecxi, Jonpas
|
||||
* Mouse scroll wheel changed event.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
*
|
||||
* Example:
|
||||
* [] call ace_advancedthrowing_fnc_onMouseScroll
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
if !(ACE_player getVariable [QGVAR(inHand), false]) exitWith {};
|
||||
|
||||
params ["", "_amount"];
|
||||
|
||||
if (ACE_player getVariable [QGVAR(dropMode), false]) then {
|
||||
private _dropDistance = ACE_player getVariable [QGVAR(dropDistance), DROP_DISTANCE_DEFAULT];
|
||||
if (_amount < 0) then {
|
||||
// Move closer
|
||||
ACE_player setVariable [QGVAR(dropDistance), (_dropDistance - 0.1) max DROP_DISTANCE_DEFAULT];
|
||||
} else {
|
||||
// Move further
|
||||
ACE_player setVariable [QGVAR(dropDistance), (_dropDistance + 0.1) min 1];
|
||||
};
|
||||
|
||||
// Limit distance in vehicle
|
||||
if (vehicle ACE_player != ACE_player) then {
|
||||
ACE_player setVariable [QGVAR(dropDistance), (ACE_player getVariable [QGVAR(dropDistance), DROP_DISTANCE_DEFAULT]) min 0.5];
|
||||
};
|
||||
} else {
|
||||
private _throwType = ACE_player getVariable [QGVAR(throwType), THROW_TYPE_DEFAULT];
|
||||
if (_amount < 0) then {
|
||||
if (_throwType == "high") then {
|
||||
ACE_player setVariable [QGVAR(throwType), THROW_TYPE_DEFAULT];
|
||||
};
|
||||
} else {
|
||||
if (_throwType == "normal") then {
|
||||
ACE_player setVariable [QGVAR(throwType), "high"];
|
||||
};
|
||||
};
|
||||
TRACE_2("Change Throw Type",_amount,ACE_player getVariable QGVAR(throwType));
|
||||
};
|
52
addons/advancedthrowing/functions/fnc_pickUp.sqf
Normal file
52
addons/advancedthrowing/functions/fnc_pickUp.sqf
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Author: Jonpas
|
||||
* Picks up a throwable from the ground.
|
||||
*
|
||||
* Arguments:
|
||||
* 0: Pick Up Helper <OBJECT>
|
||||
* 1: Unit <OBJECT>
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
*
|
||||
* Example:
|
||||
* [helper, player] call ace_advancedthrowing_fnc_pickUp
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
params ["_helper", "_unit"];
|
||||
TRACE_2("params",_helper,_unit);
|
||||
|
||||
private _activeThrowable = _helper getVariable [QGVAR(throwable), objNull];
|
||||
|
||||
if (isNull _activeThrowable) exitWith {TRACE_2("throwable is null",_helper,_activeThrowable);};
|
||||
|
||||
// Detach if attached (to vehicle for example or another player)
|
||||
private _attachedTo = attachedTo _activeThrowable;
|
||||
if (!isNull _attachedTo) then {
|
||||
private _attachedList = _attachedTo getVariable [QEGVAR(attach,attached), []];
|
||||
{
|
||||
_x params ["_xObject"];
|
||||
if (_activeThrowable == _xObject) exitWith {
|
||||
TRACE_2("removing from ace_attach",_attachedTo,_attachedList);
|
||||
_attachedList deleteAt _forEachIndex;
|
||||
_attachedTo setVariable [QEGVAR(attach,attached), _attachedList, true];
|
||||
};
|
||||
} forEach _attachedList;
|
||||
detach _activeThrowable;
|
||||
};
|
||||
|
||||
// Change locality for manipulation (some commands require local object, such as setVelocity)
|
||||
if (!local _activeThrowable) then {
|
||||
["ace_setOwner", [_activeThrowable, CBA_clientID]] call CBA_fnc_serverEvent;
|
||||
};
|
||||
|
||||
// Invoke listenable event
|
||||
["ace_throwablePickedUp", [_activeThrowable, _unit, _attachedTo]] call CBA_fnc_localEvent;
|
||||
|
||||
_unit setVariable [QGVAR(primed), true];
|
||||
_unit setVariable [QGVAR(activeThrowable), _activeThrowable];
|
||||
|
||||
_unit call FUNC(prepare);
|
50
addons/advancedthrowing/functions/fnc_prepare.sqf
Normal file
50
addons/advancedthrowing/functions/fnc_prepare.sqf
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Author: Dslyecxi, Jonpas
|
||||
* Prepares throwable or selects the next.
|
||||
*
|
||||
* Arguments:
|
||||
* 0: Unit <OBJECT>
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
*
|
||||
* Example:
|
||||
* [unit] call ace_advancedthrowing_fnc_prepare
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
params ["_unit"];
|
||||
TRACE_1("params",_unit);
|
||||
|
||||
// Select next throwable if one already in hand
|
||||
if (_unit getVariable [QGVAR(inHand), false]) exitWith {
|
||||
TRACE_1("inHand",_unit);
|
||||
if (!(_unit getVariable [QGVAR(primed), false])) then {
|
||||
TRACE_1("not primed",_unit);
|
||||
[_unit] call EFUNC(weaponselect,selectNextGrenade);
|
||||
};
|
||||
};
|
||||
|
||||
// Try selecting next throwable if none currently selected
|
||||
if ((isNull (_unit getVariable [QGVAR(activeThrowable), objNull])) && {(currentThrowable _unit) isEqualTo []} && {!([_unit] call EFUNC(weaponselect,selectNextGrenade))}) exitWith {
|
||||
TRACE_1("no throwables",_unit);
|
||||
};
|
||||
|
||||
|
||||
_unit setVariable [QGVAR(inHand), true];
|
||||
|
||||
// Add controls hint
|
||||
call FUNC(updateControlsHint);
|
||||
|
||||
// Add throw action to suppress weapon firing (not possible to suppress mouseButtonDown event)
|
||||
_unit setVariable [QGVAR(throwAction), [_unit, "DefaultAction", {true}, {true}] call EFUNC(common,addActionEventHandler)];
|
||||
|
||||
// Draw throwable and throw arc if enabled
|
||||
GVAR(draw3DHandle) = addMissionEventHandler ["Draw3D", {
|
||||
call FUNC(drawThrowable);
|
||||
if (GVAR(showThrowArc)) then {
|
||||
call FUNC(drawArc);
|
||||
};
|
||||
}];
|
57
addons/advancedthrowing/functions/fnc_prime.sqf
Normal file
57
addons/advancedthrowing/functions/fnc_prime.sqf
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Author: Dslyecxi, Jonpas
|
||||
* Primes the throwable, creates global throwable vehicle and throws Fired XEH.
|
||||
*
|
||||
* Arguments:
|
||||
* 0: Unit <OBJECT>
|
||||
* 1: Show Hint <BOOL> (default: false)
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
*
|
||||
* Example:
|
||||
* [unit] call ace_advancedthrowing_fnc_prime
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
params ["_unit", ["_showHint", false]];
|
||||
TRACE_2("params",_unit,_showHint);
|
||||
|
||||
_unit setVariable [QGVAR(primed), true];
|
||||
|
||||
// Remove item before cooking to prevent weaponselect showing more throwables than there actually are in inventory
|
||||
private _throwableMag = (currentThrowable _unit) select 0;
|
||||
_unit removeItem _throwableMag;
|
||||
|
||||
private _throwableType = getText (configFile >> "CfgMagazines" >> _throwableMag >> "ammo");
|
||||
private _muzzle = _throwableMag call FUNC(getMuzzle);
|
||||
|
||||
// Create actual throwable globally
|
||||
private _activeThrowableOld = _unit getVariable [QGVAR(activeThrowable), objNull];
|
||||
private _activeThrowable = createVehicle [_throwableType, _activeThrowableOld, [], 0, "CAN_COLLIDE"];
|
||||
_unit setVariable [QGVAR(activeThrowable), _activeThrowable];
|
||||
deleteVehicle _activeThrowableOld;
|
||||
|
||||
// Throw Fired XEH
|
||||
[QGVAR(throwFiredXEH), [
|
||||
_unit, // unit
|
||||
"Throw", // weapon
|
||||
_muzzle, // muzzle
|
||||
_muzzle, // mode
|
||||
_throwableType, // ammo
|
||||
_throwableMag, // magazine
|
||||
_activeThrowable // projectile
|
||||
]] call CBA_fnc_globalEvent;
|
||||
|
||||
if (_showHint) then {
|
||||
// Show primed hint
|
||||
private _displayNameShort = getText (configFile >> "CfgMagazines" >> _throwableMag >> "displayNameShort");
|
||||
private _picture = getText (configFile >> "CfgMagazines" >> _throwableMag >> "picture");
|
||||
|
||||
[[_displayNameShort, localize LSTRING(Primed)] joinString " ", _picture] call EFUNC(common,displayTextPicture);
|
||||
|
||||
// Change controls hint for RMB
|
||||
call FUNC(updateControlsHint);
|
||||
};
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Author: PabstMirror, Jonpas
|
||||
* When interact_menu starts rendering (from "interact_keyDown" event).
|
||||
* Add pick up helpers to all nearby throwables and keep setting them to their position (setVariable and attachTo does not work on CfgAmmo).
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* Nothing
|
||||
*
|
||||
* Example:
|
||||
* call ace_advancedthrowing_fnc_renderPickUpInteraction
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
[{
|
||||
params ["_args", "_idPFH"];
|
||||
_args params ["_setPosition", "_addedPickUpHelpers", "_throwablesHelped", "_nearThrowables"];
|
||||
|
||||
// isNull is necessarry to prevent rare error when ending mission with interact key down
|
||||
if (EGVAR(interact_menu,keyDown) && {!isNull ACE_player}) then {
|
||||
// Rescan when player moved >5 meters from last pos, nearObjects is costly
|
||||
if ((getPosASL ACE_player) distance _setPosition > 5) then {
|
||||
// IR throwbles inherit from GrenadeCore, others from GrenadeHand
|
||||
_nearThrowables = ACE_player nearObjects ["GrenadeHand", 10];
|
||||
_nearThrowables append (ACE_player nearObjects ["GrenadeCore", 10]);
|
||||
|
||||
{
|
||||
if (_x in _throwablesHelped) exitWith {};
|
||||
|
||||
TRACE_2("Making PickUp Helper",_x,typeOf _x);
|
||||
private _pickUpHelper = QGVAR(pickUpHelper) createVehicleLocal [0, 0, 0];
|
||||
|
||||
_pickUpHelper attachTo [_x, [0, 0, 0]];
|
||||
_pickUpHelper setVariable [QGVAR(throwable), _x];
|
||||
|
||||
_addedPickUpHelpers pushBack _pickUpHelper;
|
||||
_throwablesHelped pushBack _x;
|
||||
true
|
||||
} count _nearThrowables;
|
||||
|
||||
_args set [0, getPosASL ACE_player];
|
||||
_args set [3, _nearThrowables];
|
||||
};
|
||||
|
||||
{
|
||||
if (!GVAR(enablePickUpAttached) && {!isNull (attachedTo _x)}) exitWith {};
|
||||
|
||||
// Only handling with attachTo works nicely
|
||||
_x attachTo [_x getVariable [QGVAR(throwable), objNull], [0, 0, 0]];
|
||||
true
|
||||
} count _addedPickUpHelpers;
|
||||
} else {
|
||||
TRACE_1("Cleaning Pick Up Helpers",count _addedPickUpHelpers);
|
||||
{deleteVehicle _x} count _addedPickUpHelpers;
|
||||
[_idPFH] call CBA_fnc_removePerFrameHandler;
|
||||
};
|
||||
}, 0, [(getPosASL ACE_player) vectorAdd [-100, 0, 0], [], [], []]] call CBA_fnc_addPerFrameHandler;
|
85
addons/advancedthrowing/functions/fnc_throw.sqf
Normal file
85
addons/advancedthrowing/functions/fnc_throw.sqf
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Author: Dslyecxi, Jonpas
|
||||
* Throw selected throwable.
|
||||
*
|
||||
* Arguments:
|
||||
* 0: Unit <OBJECT>
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
*
|
||||
* Example:
|
||||
* unit call ace_advancedthrowing_fnc_throw
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
params ["_unit"];
|
||||
TRACE_1("params",_unit);
|
||||
|
||||
// Prime the throwable if it hasn't been cooking already
|
||||
// Next to proper simulation this also has to happen before delay for orientation of the throwable to be set
|
||||
if (!(_unit getVariable [QGVAR(primed), false])) then {
|
||||
[_unit] call FUNC(prime);
|
||||
};
|
||||
|
||||
[_unit, "ThrowGrenade"] call EFUNC(common,doGesture);
|
||||
|
||||
// Pass position to reset later because animation may change it in certain stances
|
||||
[{
|
||||
params ["_unit", "_activeThrowable", "_posThrown", "_throwType", "_throwSpeed", "_dropMode"];
|
||||
TRACE_6("delayParams",_unit,_activeThrowable,_posThrown,_throwType,_throwSpeed,_dropMode);
|
||||
|
||||
// Reset position in case animation changed it
|
||||
_activeThrowable setPosASL _posThrown;
|
||||
|
||||
// Launch actual throwable
|
||||
private _direction = [THROWSTYLE_NORMAL_DIR, THROWSTYLE_HIGH_DIR] select (_throwType == "high" || {_dropMode});
|
||||
private _velocity = [_throwSpeed, _throwSpeed / THROWSTYLE_HIGH_VEL_COEF / 1.25] select (_throwType == "high");
|
||||
_velocity = [_velocity, THROWSTYLE_DROP_VEL] select _dropMode;
|
||||
|
||||
private _p2 = (eyePos _unit) vectorAdd (AGLToASL (positionCameraToWorld _direction)) vectorDiff (AGLToASL (positionCameraToWorld [0, 0, 0]));
|
||||
private _p1 = AGLtoASL (_activeThrowable modelToWorldVisual [0, 0, 0]);
|
||||
|
||||
private _newVelocity = (_p1 vectorFromTo _p2) vectorMultiply _velocity;
|
||||
|
||||
// Adjust for throwing from inside vehicles, where we have a vehicle-based velocity that can't be compensated for by a human
|
||||
if (vehicle _unit != _unit) then {
|
||||
_newVelocity = _newVelocity vectorAdd (velocity (vehicle _unit));
|
||||
};
|
||||
|
||||
// Drop if unit dies during throw process
|
||||
if (alive _unit) then {
|
||||
_activeThrowable setVelocity _newVelocity;
|
||||
};
|
||||
|
||||
_unit setVariable [QGVAR(lastThrownTime), CBA_missionTime];
|
||||
|
||||
// Invoke listenable event
|
||||
["ace_throwableThrown", [_unit, _activeThrowable]] call CBA_fnc_localEvent;
|
||||
}, [
|
||||
_unit,
|
||||
_unit getVariable [QGVAR(activeThrowable), objNull],
|
||||
getPosASLVisual (_unit getVariable [QGVAR(activeThrowable), objNull]),
|
||||
_unit getVariable [QGVAR(throwType), THROW_TYPE_DEFAULT],
|
||||
_unit getVariable [QGVAR(throwSpeed), THROW_SPEED_DEFAULT],
|
||||
_unit getVariable [QGVAR(dropMode), false]
|
||||
], 0.3] call CBA_fnc_waitAndExecute;
|
||||
|
||||
|
||||
#ifdef DRAW_THROW_PATH
|
||||
GVAR(predictedPath) = call FUNC(drawArc); // save the current throw arc
|
||||
GVAR(flightPath) = [];
|
||||
[_unit getVariable QGVAR(activeThrowable)] spawn {
|
||||
params ["_grenade"];
|
||||
while {!isNull _grenade} do {
|
||||
GVAR(flightPath) pushBack ASLtoAGL getPosASL _grenade;
|
||||
sleep 0.05;
|
||||
};
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
// Stop rendering arc and doing rendering magic while throw is happening
|
||||
[_unit, "Completed a throw fully"] call FUNC(exitThrowMode);
|
28
addons/advancedthrowing/functions/fnc_throwFiredXEH.sqf
Normal file
28
addons/advancedthrowing/functions/fnc_throwFiredXEH.sqf
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Author: CBA Team
|
||||
* Throws Fired XEH.
|
||||
*
|
||||
* Arguments:
|
||||
* 0: unit - Object the event handler is assigned to <OBJECT>
|
||||
* 1: weapon - Fired weapon <STRING>
|
||||
* 2: muzzle - Muzzle that was used <STRING>
|
||||
* 3: mode - Current mode of the fired weapon <STRING>
|
||||
* 4: ammo - Ammo used <STRING>
|
||||
* 5: magazine - magazine name which was used <STRING>
|
||||
* 6: projectile - Object of the projectile that was shot <OBJECT>
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
*
|
||||
* Example:
|
||||
* [unit, "weapon", "muzle", "mode", "ammo", "magazine", projectile] call ace_advancedthrowing_fnc_throwFiredXEH
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
TRACE_1("Fired",_this);
|
||||
|
||||
{
|
||||
_this call _x;
|
||||
} forEach ((_this select 0) getVariable "cba_xeh_fired");
|
32
addons/advancedthrowing/functions/fnc_updateControlsHint.sqf
Normal file
32
addons/advancedthrowing/functions/fnc_updateControlsHint.sqf
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Author: Jonpas
|
||||
* Updates controls hints based on current state.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
*
|
||||
* Example:
|
||||
* call ace_advancedthrowing_fnc_updateControlsHint
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
if (!GVAR(showMouseControls)) exitWith {};
|
||||
|
||||
private _primed = ACE_player getVariable [QGVAR(primed), false];
|
||||
|
||||
private _mmb = [localize LSTRING(ChangeMode), localize LSTRING(Extend)] select (ACE_player getVariable [QGVAR(dropMode), false]);
|
||||
|
||||
if (!_primed) then {
|
||||
_mmb = [_mmb, localize LSTRING(Cook)] joinString " / ";
|
||||
};
|
||||
|
||||
[
|
||||
localize LSTRING(Throw),
|
||||
[localize ELSTRING(common,Cancel), ""] select _primed,
|
||||
_mmb
|
||||
] call EFUNC(interaction,showMouseHint);
|
1
addons/advancedthrowing/functions/script_component.hpp
Normal file
1
addons/advancedthrowing/functions/script_component.hpp
Normal file
@ -0,0 +1 @@
|
||||
#include "\z\ace\addons\advancedthrowing\script_component.hpp"
|
29
addons/advancedthrowing/script_component.hpp
Normal file
29
addons/advancedthrowing/script_component.hpp
Normal file
@ -0,0 +1,29 @@
|
||||
#define COMPONENT advancedthrowing
|
||||
#define COMPONENT_BEAUTIFIED Advanced Throwing
|
||||
#include "\z\ace\addons\main\script_mod.hpp"
|
||||
|
||||
// #define DRAW_THROW_PATH
|
||||
// #define DEBUG_MODE_FULL
|
||||
// #define DISABLE_COMPILE_CACHE
|
||||
// #define CBA_DEBUG_SYNCHRONOUS
|
||||
// #define ENABLE_PERFORMANCE_COUNTERS
|
||||
|
||||
#ifdef DEBUG_ENABLED_ADVANCEDTHROWING
|
||||
#define DEBUG_MODE_FULL
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_SETTINGS_ADVANCEDTHROWING
|
||||
#define DEBUG_SETTINGS DEBUG_SETTINGS_ADVANCEDTHROWING
|
||||
#endif
|
||||
|
||||
#include "\z\ace\addons\main\script_macros.hpp"
|
||||
|
||||
|
||||
#define THROWSTYLE_NORMAL_DIR [0, 70, 500]
|
||||
#define THROWSTYLE_HIGH_DIR [0, 200, 500]
|
||||
#define THROWSTYLE_HIGH_VEL_COEF 2
|
||||
#define THROWSTYLE_DROP_VEL 2
|
||||
|
||||
#define THROW_TYPE_DEFAULT "normal"
|
||||
#define THROW_SPEED_DEFAULT 18
|
||||
#define DROP_DISTANCE_DEFAULT 0.2
|
68
addons/advancedthrowing/stringtable.xml
Normal file
68
addons/advancedthrowing/stringtable.xml
Normal file
@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project name="ACE">
|
||||
<Package name="AdvancedThrowing">
|
||||
<Key ID="STR_ACE_AdvancedThrowing_Category">
|
||||
<English>Advanced Throwing</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_Module_Description">
|
||||
<English>Allows changing advanced throwing behaviour.</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_Enable_DisplayName">
|
||||
<English>Enable Advanced Throwing</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_Enable_Description">
|
||||
<English>Enables advanced throwing system.</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_ShowThrowArc_DisplayName">
|
||||
<English>Show Throw Arc</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_ShowThrowArc_Description">
|
||||
<English>Enables visualization of the throw arc (where throwable will fly).</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_ShowMouseControls_DisplayName">
|
||||
<English>Show Throwing Mouse Controls</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_ShowMouseControls_Description">
|
||||
<English>Enables visual cues for mouse controls when throwable is prepared.</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_EnablePickUp_DisplayName">
|
||||
<English>Enable Throwables Pick Up</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_EnablePickUp_Description">
|
||||
<English>Enables ability to pick up throwables from the ground.</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_EnablePickUpAttached_DisplayName">
|
||||
<English>Enable Attached Throwables Pick Up</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_EnablePickUpAttached_Description">
|
||||
<English>Enables ability to pick up throwables from attached objects.</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_Prepare">
|
||||
<English>Prepare/Change Throwable</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_dropModeHold">
|
||||
<English>Throwable Drop Mode (Hold)</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_DropModeToggle">
|
||||
<English>Throwable Drop Mode (Toggle)</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_Primed">
|
||||
<English>Primed</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_Throw">
|
||||
<English>Throw</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_ChangeMode">
|
||||
<English>(Scroll) Change Mode</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_Extend">
|
||||
<English>(Scroll) Extend</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_Cook">
|
||||
<English>(Click) Cook</English>
|
||||
</Key>
|
||||
<Key ID="STR_ACE_AdvancedThrowing_PickUp">
|
||||
<English>Pick Up</English>
|
||||
</Key>
|
||||
</Package>
|
||||
</Project>
|
@ -15,6 +15,8 @@
|
||||
//IGNORE_PRIVATE_WARNING ["_unit", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle", "_gunner", "_turret"];
|
||||
TRACE_10("firedEH:",_unit, _weapon, _muzzle, _mode, _ammo, _magazine, _projectile, _vehicle, _gunner, _turret);
|
||||
|
||||
disableSerialization;
|
||||
|
||||
// check if compatible scope is used
|
||||
private _display = uiNamespace getVariable [QGVAR(RscWeaponInfo2D), displayNull];
|
||||
|
||||
|
@ -72,8 +72,6 @@ if (!hasInterface) exitWith {};
|
||||
["ACE3 Weapons", QGVAR(SelectGrenadeFrag), localize LSTRING(SelectGrenadeFrag), {
|
||||
// Conditions: canInteract
|
||||
if !([ACE_player, ACE_player, ["isNotInside", "isNotEscorting"]] call EFUNC(common,canInteractWith)) exitWith {false};
|
||||
// Conditions: specific
|
||||
if !([ACE_player] call CBA_fnc_canUseWeapon) exitWith {false};
|
||||
|
||||
// Statement
|
||||
[ACE_player, 1] call FUNC(selectNextGrenade);
|
||||
@ -85,8 +83,6 @@ if (!hasInterface) exitWith {};
|
||||
["ACE3 Weapons", QGVAR(SelectGrenadeOther), localize LSTRING(SelectGrenadeOther), {
|
||||
// Conditions: canInteract
|
||||
if !([ACE_player, ACE_player, ["isNotInside", "isNotEscorting"]] call EFUNC(common,canInteractWith)) exitWith {false};
|
||||
// Conditions: specific
|
||||
if !([ACE_player] call CBA_fnc_canUseWeapon) exitWith {false};
|
||||
|
||||
// Statement
|
||||
[ACE_player, 2] call FUNC(selectNextGrenade);
|
||||
|
60
docs/wiki/feature/advancedthrowing.md
Normal file
60
docs/wiki/feature/advancedthrowing.md
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
layout: wiki
|
||||
title: Advanced Throwing
|
||||
description: Precise system for throwing grenades and other throwable items.
|
||||
group: feature
|
||||
category: interaction
|
||||
parent: wiki
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
Advanced Throwing adds an alternate way of throwing grenades and other throwable items. It solves the issues vanilla Arma throwing has, such as hard to effectively throw through a window even when you are standing right next to it, hard to judge distance and similar. Additionally, it adds other features, such as cooking, picking up already thrown grenades, different throw modes, distance and flight arc indicators, which vanilla lacks. It is also entirely optional (can be disabled through settings) and configurable.
|
||||
|
||||
### 1.1 Throwing Modes
|
||||
|
||||
There are 3 throwing modes in Advanced Throwing, normal, high and drop. Normal mode is similar in power to vanilla throwing, high mode lobs the grenade higher, for example for throwing over walls, drop mode drops the grenade at your feet or slightly away, for example for dropping smoke or chemlights. Drop mods are indicated by both the throw arc indicator and the actual representation of the grenade in the world.
|
||||
|
||||
Drop mode has additional functionality which allows you to extend the arm and drop the grenade slightly further away, for example for dropping through windows, especially helpful where Arma's geometry would simply make it bounce back into your lap.
|
||||
|
||||
After cooking a grenade any mode can be used to throw it further or it can simply be held in hand (at least with smoke grenades, chemlights and similar, not recommended with frag grenades).
|
||||
|
||||
Grenade will be thrown where you are currently looking at, free-look (including TrackIR) is fully supported. However, the further to the side and back you are looking, the lower the throw power will be.
|
||||
|
||||
### 1.2 Rethrowing
|
||||
|
||||
Advanced Throwing allows you to pick up grenades that have already been thrown. You have to be very close to it to effectively pick it up. Useful for rethrowing smoke grenades or repositioning chemlights. Frag grenades can also be thrown back, for example out of the house, however doing so is extremely risky.
|
||||
|
||||
### 1.3 Settings
|
||||
|
||||
Various settings can be set according to your likeness. Next to global toggle to disable Advanced Throwing there are settings to hide the throw arc indicating approximate throw distance and the arc it will fly through and hide mouse controls. Additionally for server administrators and mission makers, picking up grenades can be entirely disabled, as well as a setting to enable picking up attached items such as chemlights attached to vehicles or other player's shoulder.
|
||||
|
||||
|
||||
## 2. Usage
|
||||
|
||||
Make sure to use different keybinds for Advanced Throwing or unbind vanilla throwing keybinds to prevent incidents.
|
||||
|
||||
### 2.1 Throwing
|
||||
- You need a throwable item.
|
||||
- Press <kbd>⇧ Shift</kbd>+<kbd>G</kbd> (ACE3 default) to prepare the item.
|
||||
- Press the keybind again to select next throwable item in the inventory.
|
||||
- Press <kbd>LMB</kbd> to throw.
|
||||
- Press <kbd>MMB</kbd> to prime/cook.
|
||||
- Press <kbd>RMB</kbd> to cancel throw.
|
||||
- Throw will also be cancelled when opening any interface, reloading, switching weapons and aiming.
|
||||
|
||||
### 2.2 Changing Mode
|
||||
- Use <kbd>Scroll Wheel</kbd> to change between **normal** and **high** modes.
|
||||
- Press <kbd>Ctrl</kbd>+<kbd>G</kbd> (ACE3 default) to change to **drop** mode.
|
||||
- Use <kbd>Scroll Wheel</kbd> to extend or contract drop distance.
|
||||
|
||||
### 2.3 Picking Up
|
||||
- Approach a throwable item on the ground.
|
||||
- Interact with the item <kbd>⊞ Win</kbd> (ACE3 default).
|
||||
- Select `Pick Up`.
|
||||
- Item will be placed in your hands, but you may not cancel the throw.
|
||||
|
||||
|
||||
## 3. Dependencies
|
||||
|
||||
{% include dependencies_list.md component="advancedthrowing" %}
|
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
Loading…
Reference in New Issue
Block a user