Safemode - Refactor (#10111)

* Refactor safemode

* Further improvements and fixes

* Update XEH_postInit.sqf

* Don't allow binoculars to be set to safe

* Add API for getting weapon safety status

* Update fnc_jamWeapon.sqf

* Added doc

* Update fnc_playChangeFiremodeSound.sqf

* Update addons/overheating/functions/fnc_jamWeapon.sqf

Co-authored-by: PabstMirror <pabstmirror@gmail.com>

* Update addons/weaponselect/functions/fnc_selectWeaponMode.sqf

Co-authored-by: PabstMirror <pabstmirror@gmail.com>

---------

Co-authored-by: PabstMirror <pabstmirror@gmail.com>
This commit is contained in:
johnb432 2024-08-02 13:59:18 +02:00 committed by GitHub
parent 4e56d58210
commit 6a25e9365a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 257 additions and 140 deletions

View File

@ -1,6 +1,6 @@
#include "..\script_component.hpp"
/*
* Author: commy2
* Author: commy2, johnb43
* Get the muzzles of a weapon.
*
* Arguments:
@ -10,19 +10,30 @@
* All weapon muzzles <ARRAY>
*
* Example:
* ["gun"] call ace_common_fnc_getWeaponMuzzles
* "arifle_AK12_F" call ace_common_fnc_getWeaponMuzzles
*
* Public: Yes
*/
params [["_weapon", "", [""]]];
private _muzzles = getArray (configFile >> "CfgWeapons" >> _weapon >> "muzzles");
private _config = configFile >> "CfgWeapons" >> _weapon;
if (!isClass _config) exitWith {
[] // return
};
private _muzzles = [];
// Get config case muzzle names
{
if (_x == "this") then {
_muzzles set [_forEachIndex, configName (configFile >> "CfgWeapons" >> _weapon)];
_muzzles pushBack (configName _config);
} else {
if (isClass (_config >> _x)) then {
_muzzles pushBack (configName (_config >> _x));
};
} forEach _muzzles;
};
} forEach getArray (_config >> "muzzles");
_muzzles
_muzzles // return

View File

@ -80,9 +80,11 @@ if (_unit getVariable [QGVAR(JammingActionID), -1] == -1) then {
private _condition = {
private _unit = _this select 1;
[_unit] call CBA_fnc_canUseWeapon
&& {currentMuzzle _unit in (_unit getVariable [QGVAR(jammedWeapons), []])}
&& {!(currentMuzzle _unit in (_unit getVariable [QEGVAR(safemode,safedWeapons), []]))}
(weaponState _unit) params ["_currentWeapon", "_currentMuzzle"];
_unit call CBA_fnc_canUseWeapon
&& {_currentMuzzle in (_unit getVariable [QGVAR(jammedWeapons), []])}
&& {!(["ace_safemode"] call EFUNC(common,isModLoaded)) || {!([_unit, _currentWeapon, _currentMuzzle] call EFUNC(safemode,getWeaponSafety))}}
};
private _statement = {

View File

@ -1,4 +1,3 @@
class Extended_PreStart_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_SCRIPT(XEH_preStart));

View File

@ -1,6 +1,6 @@
PREP(getWeaponSafety);
PREP(lockSafety);
PREP(playChangeFiremodeSound);
PREP(setSafeModeVisual);
PREP(unlockSafety);
PREP(setWeaponSafety);
PREP(unlockSafety);

View File

@ -4,18 +4,26 @@
if (!hasInterface) exitWith {};
["ACE3 Weapons", QGVAR(safeMode), localize LSTRING(SafeMode), {
["ACE3 Weapons", QGVAR(safeMode), LLSTRING(SafeMode), {
// Conditions: canInteract
if !([ACE_player, objNull, ["isNotEscorting", "isNotInside", "isNotSwimming"]] call EFUNC(common,canInteractWith)) exitWith {false};
// Conditions: specific
if !([ACE_player] call CBA_fnc_canUseWeapon && {currentWeapon ACE_player != binocular ACE_player} && {currentWeapon ACE_player != ""}) exitWith {false};
// Statement
[ACE_player, currentWeapon ACE_player, currentMuzzle ACE_player] call FUNC(lockSafety);
(weaponState ACE_player) params ["_currentWeapon", "_currentMuzzle"];
// Conditions: specific
if !(ACE_player call CBA_fnc_canUseWeapon && {_currentWeapon != ""} && {_currentWeapon != binocular ACE_player}) exitWith {false};
// Statement: Toggle weapon safety
[ACE_player, _currentWeapon, _currentMuzzle] call FUNC(lockSafety);
true
}, {false}, [DIK_GRAVE, [false, true, false]], false] call CBA_fnc_addKeybind;
["unit", {
private _weaponSafe = currentWeapon ACE_player in (ACE_player getVariable [QGVAR(safedWeapons), []]);
[!_weaponSafe] call FUNC(setSafeModeVisual);
(weaponState ACE_player) params ["_currentWeapon", "_currentMuzzle"];
private _weaponSafe = [ACE_player, _currentWeapon, _currentMuzzle] call FUNC(getWeaponSafety);
// Player HUD
!_weaponSafe call FUNC(setSafeModeVisual);
}] call CBA_fnc_addPlayerEventHandler;

View File

@ -0,0 +1,45 @@
#include "..\script_component.hpp"
/*
* Author: johnb43
* Getter for weapon safety state.
*
* Arguments:
* 0: Unit <OBJECT>
* 1: Weapon <STRING>
* 2: Muzzle <STRING> (default: current muzzle of weapon)
*
* Return Value:
* Safety status <BOOL>
*
* Example:
* [ACE_player, currentWeapon ACE_player] call ace_safemode_fnc_getWeaponSafety
*
* Public: Yes
*/
params [
["_unit", objNull, [objNull]],
["_weapon", "", [""]],
["_muzzle", nil, [""]]
];
if (_weapon == "" || {!(_unit hasWeapon _weapon)}) exitWith {false};
// Check if weapon is a binocular
if ((_weapon call EFUNC(common,getItemType)) select 1 == "binocular") exitWith {false};
// Check for invalid muzzles
_muzzle = if (isNil "_muzzle") then {
// Get current weapon muzzle if not defined
(_unit weaponState _weapon) select 1
} else {
// Get config case muzzle names
private _muzzles = _weapon call EFUNC(common,getWeaponMuzzles);
_muzzles param [_muzzles findIf {_x == _muzzle}, ""]
};
// Weapon is not available
if (_muzzle == "") exitWith {false};
_muzzle in ((_unit getVariable [QGVAR(safedWeapons), createHashMap]) getOrDefault [_weapon, createHashMap]) // return

View File

@ -1,13 +1,13 @@
#include "..\script_component.hpp"
/*
* Author: commy2
* Put weapon on safety, or take it off safety if safety is already put on.
* Author: commy2, johnb43
* Puts weapon on safety, or take it off safety if safety is already put on.
*
* Arguments:
* 0: Unit <OBJECT>
* 1: Weapon <STRING>
* 2: Muzzle <STRING>
* 3: Show hint <BOOL>
* 3: Show hint <BOOL> (default: true)
*
* Return Value:
* None
@ -18,67 +18,74 @@
* Public: No
*/
params ["_unit", "_weapon", "_muzzle", ["_hint", true, [true]]];
params ["_unit", "_weapon", "_muzzle", ["_hint", true]];
private _safedWeapons = _unit getVariable [QGVAR(safedWeapons), []];
private _safedWeapons = _unit getVariable QGVAR(safedWeapons);
if (_weapon in _safedWeapons) exitWith {
_this call FUNC(unlockSafety);
};
_safedWeapons pushBack _weapon;
if (isNil "_safedWeapons") then {
_safedWeapons = createHashMap;
_unit setVariable [QGVAR(safedWeapons), _safedWeapons];
};
if (_unit getVariable [QGVAR(actionID), -1] == -1) then {
// See if the current weapon has locked muzzles
private _safedWeaponMuzzles = _safedWeapons getOrDefault [_weapon, createHashMap, true];
// If muzzle is locked, unlock it (toggle)
if (_muzzle in _safedWeaponMuzzles) exitWith {
[_unit, _weapon, _muzzle, _hint] call FUNC(unlockSafety);
};
private _firemode = (_unit weaponState _muzzle) select 2;
// This syntax of selectWeapon doesn't mess with gun lights and lasers
_unit selectWeapon [_weapon, _muzzle, _firemode];
// Store new muzzle & firemode
_safedWeaponMuzzles set [_muzzle, _firemode];
// Lock muzzle
if (isNil {_unit getVariable QGVAR(actionID)}) then {
_unit setVariable [QGVAR(actionID), [
_unit, "DefaultAction", {
params ["", "_unit"];
if (
[_this select 1] call CBA_fnc_canUseWeapon
&& {
if (currentMuzzle (_this select 1) in ((_this select 1) getVariable [QGVAR(safedWeapons), []])) then {
if (inputAction "nextWeapon" > 0) exitWith {
[_this select 1, currentWeapon (_this select 1), currentMuzzle (_this select 1)] call FUNC(unlockSafety);
_unit call CBA_fnc_canUseWeapon && {
(weaponState _unit) params ["_currentWeapon", "_currentMuzzle"];
// Block firing the muzzle in safe mode
if (_currentMuzzle in ((_unit getVariable [QGVAR(safedWeapons), createHashMap]) getOrDefault [_currentWeapon, createHashMap])) then {
if (inputAction "nextWeapon" > 0 || {inputAction "prevWeapon" > 0}) exitWith {
[_unit, _currentWeapon, _currentMuzzle] call FUNC(unlockSafety);
false
};
true
} else {false}
}
) then {
// player hud
[false] call FUNC(setSafeModeVisual);
true
} else {
// player hud
[true] call FUNC(setSafeModeVisual);
false
}
}
) then {
// Player HUD
false call FUNC(setSafeModeVisual);
true
} else {
// Player HUD
true call FUNC(setSafeModeVisual);
false
};
}, {}
] call EFUNC(common,addActionEventHandler)];
};
if (_muzzle isEqualType "") then {
private _laserEnabled = _unit isIRLaserOn _weapon || {_unit isFlashlightOn _weapon};
_unit selectWeapon _muzzle;
if (
_laserEnabled
&& {
_muzzle == primaryWeapon _unit // prevent UGL switch
|| {"" == primaryWeapon _unit} // Arma switches to primary weapon if exists
}
) then {
{_unit action [_x, _unit]} forEach ["GunLightOn", "IRLaserOn"];
};
};
// play fire mode selector sound
// Play fire mode selector sound
[_unit, _weapon, _muzzle] call FUNC(playChangeFiremodeSound);
// show info box unless disabled
// Show info box unless disabled
if (_hint) then {
private _picture = getText (configFile >> "CfgWeapons" >> _weapon >> "picture");
[localize LSTRING(PutOnSafety), _picture] call EFUNC(common,displayTextPicture);
[LLSTRING(PutOnSafety), getText (configFile >> "CfgWeapons" >> _weapon >> "picture")] call EFUNC(common,displayTextPicture);
};

View File

@ -1,7 +1,7 @@
#include "..\script_component.hpp"
/*
* Author: commy2
* Play weapon firemode change sound.
* Plays weapon firemode change sound.
*
* Arguments:
* 0: Unit <OBJECT>
@ -21,21 +21,23 @@ params ["_unit", "_weapon"];
private _sound = getArray (configFile >> "CfgWeapons" >> _weapon >> "changeFiremodeSound");
if (_sound isEqualTo []) exitWith {
playSound "ACE_Sound_Click";
playSoundUI ["ACE_Sound_Click"];
};
// get position where to play the sound (position of the weapon)
private _position = _unit modelToWorldVisualWorld (_unit selectionPosition "RightHand");
_sound params ["_filename", ["_volume", 1], ["_soundPitch", 1], ["_distance", 0]];
_sound params [["_filename", ""], ["_volume", 1], ["_soundPitch", 1], ["_distance", 0]];
if (_filename == "") exitWith {
playSound "ACE_Sound_Click";
playSoundUI ["ACE_Sound_Click"];
};
// add file extension .wss as default
// Add file extension .wss as default
if !(toLowerANSI (_filename select [count _filename - 4]) in [".wav", ".ogg", ".wss"]) then {
_filename = format ["%1.wss", _filename];
};
playSound3D [_filename, objNull, false, _position, _volume, _soundPitch, _distance];
// Get position where to play the sound (position of the weapon)
private _position = _unit modelToWorldVisualWorld (_unit selectionPosition "RightHand");
playSound3D [_filename, objNull, insideBuilding _unit >= 0.5, _position, _volume, _soundPitch, _distance];
nil // return

View File

@ -1,7 +1,7 @@
#include "..\script_component.hpp"
/*
* Author: commy2
* Show firemode indicator, representing safety lock
* Shows firemode indicator, representing safety lock.
*
* Arguments:
* 0: Show firemode <BOOL>
@ -10,7 +10,7 @@
* None
*
* Example:
* [true] call ace_safemode_fnc_setSafeModeVisual
* true call ace_safemode_fnc_setSafeModeVisual
*
* Public: No
*/
@ -27,8 +27,8 @@ if (_show) then {
private _config = configFile >> "RscInGameUI" >> "RscUnitInfoSoldier" >> "WeaponInfoControlsGroupLeft" >> "controls" >> "CA_ModeTexture";
_control ctrlSetPosition [getNumber (_config >> "x"), getNumber (_config >> "y"), getNumber (_config >> "w"), getNumber (_config >> "h")];
_control ctrlCommit 0;
} else {
_control ctrlSetPosition [0, 0, 0, 0];
_control ctrlCommit 0;
};
_control ctrlCommit 0;

View File

@ -1,13 +1,14 @@
#include "..\script_component.hpp"
/*
* Author: Brostrom.A
* Safe or unsafe the given weapon based on weapon state; locked or unlocked.
* Author: Brostrom.A, johnb43
* Lock or unlock the given weapon based on weapon state.
*
* Arguments:
* 0: Unit <OBJECT>
* 1: Weapon <STRING>
* 2: State <BOOL>
* 3: Show hint <BOOL> (default: true)
* 4: Muzzle <STRING> (default: current muzzle of weapon)
*
* Return Value:
* None
@ -22,17 +23,31 @@ params [
["_unit", objNull, [objNull]],
["_weapon", "", [""]],
["_state", true, [true]],
["_hint", true, [true]]
["_hint", true, [true]],
["_muzzle", nil, [""]]
];
if (_weapon == "") exitWith {};
// Don't allow to set weapon safety if unit doesn't have one (but allow removing safety, in case unit doesn't have weapon anymore)
if (_weapon == "" || {_state && {!(_unit hasWeapon _weapon)}}) exitWith {};
private _safedWeapons = _unit getVariable [QGVAR(safedWeapons), []];
// Check if weapon is a binocular
if ((_weapon call EFUNC(common,getItemType)) select 1 == "binocular") exitWith {};
_weapon = configName (configFile >> "CfgWeapons" >> _weapon);
// Check for invalid muzzles
_muzzle = if (isNil "_muzzle") then {
// Get current weapon muzzle if not defined
(_unit weaponState _weapon) select 1
} else {
// Get config case muzzle names
private _muzzles = _weapon call EFUNC(common,getWeaponMuzzles);
private _muzzle = currentMuzzle _unit;
if (_state isNotEqualTo (_weapon in _safedWeapons)) then {
[_unit, _weapon, _muzzle, _hint] call FUNC(lockSafety);
_muzzles param [_muzzles findIf {_x == _muzzle}, ""]
};
// Weapon is not available
if (_muzzle == "") exitWith {};
// If the weapon is already in the desired state, don't do anything
if (_state == (_muzzle in ((_unit getVariable [QGVAR(safedWeapons), createHashMap]) getOrDefault [_weapon, createHashMap]))) exitWith {};
[_unit, _weapon, _muzzle, _hint] call FUNC(lockSafety);

View File

@ -1,13 +1,13 @@
#include "..\script_component.hpp"
/*
* Author: commy2
* Take weapon of safety lock.
* Author: commy2, johnb43
* Takes the weapon safety lock off.
*
* Arguments:
* 0: Unit <OBJECT>
* 1: Weapon <STRING>
* 2: Muzzle <STRING>
* 3: Show hint <BOOL>
* 3: Show hint <BOOL> (default: true)
*
* Return Value:
* None
@ -18,67 +18,37 @@
* Public: No
*/
params ["_unit", "_weapon", "_muzzle", ["_hint", true, [true]]];
params ["_unit", "_weapon", "_muzzle", ["_hint", true]];
private _safedWeapons = _unit getVariable [QGVAR(safedWeapons), []];
_safedWeapons deleteAt (_safedWeapons find _weapon);
private _safedWeaponMuzzles = (_unit getVariable QGVAR(safedWeapons)) get _weapon;
private _firemode = _safedWeaponMuzzles deleteAt _muzzle;
_unit setVariable [QGVAR(safedWeapons), _safedWeapons];
// Remove action if all weapons have removed their safeties
if (_safedWeaponMuzzles isEqualTo createHashMap) then {
(_unit getVariable QGVAR(safedWeapons)) deleteAt _weapon;
// remove action if all weapons have put their safety on
if (_safedWeapons isEqualTo []) then {
[_unit, "DefaultAction", _unit getVariable [QGVAR(actionID), -1]] call EFUNC(common,removeActionEventHandler);
_unit setVariable [QGVAR(actionID), -1];
private _ehID = _unit getVariable QGVAR(actionID);
if (!isNil "_ehID" && {(_unit getVariable QGVAR(safedWeapons)) isEqualTo createHashMap}) then {
[_unit, "DefaultAction", _ehID] call EFUNC(common,removeActionEventHandler);
_unit setVariable [QGVAR(actionID), nil];
};
};
private _laserEnabled = _unit isIRLaserOn _weapon || {_unit isFlashlightOn _weapon};
// Let engine handle switching to next firemode/muzzle
if (inputAction "nextWeapon" == 0 && {inputAction "prevWeapon" == 0}) then {
// This syntax of selectWeapon doesn't mess with gun lights and lasers
_unit selectWeapon [_weapon, _muzzle, _firemode];
_unit selectWeapon _muzzle;
if (
_laserEnabled
&& {
_muzzle == primaryWeapon _unit // prevent UGL switch
|| {"" == primaryWeapon _unit} // Arma switches to primary weapon if exists
}
) then {
{_unit action [_x, _unit]} forEach ["GunLightOn", "IRLaserOn"];
};
if (inputAction "nextWeapon" > 0) then {
// switch to the last mode to roll over to first after the default nextWeapon action
// get weapon modes
private _modes = [];
{
if (getNumber (configFile >> "CfgWeapons" >> _weapon >> _x >> "showToPlayer") == 1) then {
_modes pushBack _x;
};
if (_x == "this") then {
_modes pushBack _weapon;
};
} forEach getArray (configFile >> "CfgWeapons" >> _weapon >> "modes");
// select last mode
private _mode = _modes select (count _modes - 1);
// switch to last mode
private _index = 0;
while {
_index < 299 && {currentMuzzle _unit != _weapon || {currentWeaponMode _unit != _mode}}
} do {
_unit action ["SwitchWeapon", _unit, _unit, _index];
_index = _index + 1;
};
} else {
// play fire mode selector sound
// Play fire mode selector sound
[_unit, _weapon, _muzzle] call FUNC(playChangeFiremodeSound);
};
// player hud
[true] call FUNC(setSafeModeVisual);
// Player HUD
true call FUNC(setSafeModeVisual);
// show info box unless disabled
// Show info box unless disabled
if (_hint) then {
private _picture = getText (configFile >> "CfgWeapons" >> _weapon >> "picture");
[localize LSTRING(TookOffSafety), _picture] call EFUNC(common,displayTextPicture);
[LLSTRING(TookOffSafety), getText (configFile >> "CfgWeapons" >> _weapon >> "picture")] call EFUNC(common,displayTextPicture);
};

View File

@ -28,8 +28,8 @@ if (currentWeapon _unit != _weapon) exitWith {
};
// Unlock safety
if (_weapon in (_unit getVariable [QEGVAR(safemode,safedWeapons), []])) exitWith {
[_unit, _weapon, _weapon] call EFUNC(safemode,unlockSafety);
if ((["ace_safemode"] call EFUNC(common,isModLoaded)) && {[_unit, _weapon] call EFUNC(safemode,getWeaponSafety)}) exitWith {
[_unit, _weapon, false] call EFUNC(safemode,setWeaponSafety);
};
private _modes = _weapon call EFUNC(common,getWeaponModes);

View File

@ -0,0 +1,58 @@
---
layout: wiki
title: Safemode Framework
description: Explains how to use the weapon safety API.
group: framework
order: 5
parent: wiki
mod: ace
version:
major: 3
minor: 0
patch: 0
---
## 1. Scripting
### 1.1 Setting weapon safety status
`ace_safemode_fnc_setWeaponSafety`
If you want the state of the currently selected muzzle, either pass the muzzle by name or leave it blank (= `nil`).
If the unit doesn't have a weapon, its safety can't be locked, but it can be unlocked.
```sqf
* Lock or unlock the given weapon based on weapon state.
*
* Arguments:
* 0: Unit <OBJECT>
* 1: Weapon <STRING>
* 2: State <BOOL>
* 3: Show hint <BOOL> (default: true)
* 4: Muzzle <STRING> (default: current muzzle of weapon)
*
* Return Value:
* None
*
* Example:
* [ACE_player, currentWeapon ACE_player, true] call ace_safemode_fnc_setWeaponSafety
```
### 1.2 Getting weapon safety status
`ace_safemode_fnc_getWeaponSafety`
If you want the state of the currently selected muzzle, either pass the muzzle by name or leave it blank (= `nil`).
```sqf
* Getter for weapon safety state.
*
* Arguments:
* 0: Unit <OBJECT>
* 1: Weapon <STRING>
* 2: Muzzle <STRING> (default: current muzzle of weapon)
*
* Return Value:
* Safety status <BOOL>
*
* Example:
* [ACE_player, currentWeapon ACE_player] call ace_safemode_fnc_getWeaponSafety
```