From 0deada7a3dcdd3fd28a0d69f9601126a0500c523 Mon Sep 17 00:00:00 2001 From: johnb432 <58661205+johnb432@users.noreply.github.com> Date: Wed, 7 Feb 2024 17:58:59 +0100 Subject: [PATCH] Prevent double ammo detonation --- addons/common/XEH_postInit.sqf | 1 + addons/cookoff/XEH_PREP.hpp | 1 + .../functions/fnc_detonateAmmunition.sqf | 180 ++---------------- .../fnc_detonateAmmunitionServer.sqf | 171 +++++++++++++++++ 4 files changed, 187 insertions(+), 166 deletions(-) create mode 100644 addons/cookoff/functions/fnc_detonateAmmunitionServer.sqf diff --git a/addons/common/XEH_postInit.sqf b/addons/common/XEH_postInit.sqf index af4b33d49b..d31ad12991 100644 --- a/addons/common/XEH_postInit.sqf +++ b/addons/common/XEH_postInit.sqf @@ -192,6 +192,7 @@ if (isServer) then { [QGVAR(switchMove), {(_this select 0) switchMove (_this select 1)}] call CBA_fnc_addEventHandler; [QGVAR(setVectorDirAndUp), {(_this select 0) setVectorDirAndUp (_this select 1)}] call CBA_fnc_addEventHandler; [QGVAR(addWeaponItem), {(_this select 0) addWeaponItem [(_this select 1), (_this select 2)]}] call CBA_fnc_addEventHandler; +[QGVAR(removeMagazinesTurret), {(_this select 0) removeMagazinesTurret [_this select 1, _this select 2]}] call CBA_fnc_addEventHandler; [QGVAR(setVanillaHitPointDamage), { params ["_object", "_hitPointAnddamage"]; diff --git a/addons/cookoff/XEH_PREP.hpp b/addons/cookoff/XEH_PREP.hpp index 3dc602be70..dfc3b7b130 100644 --- a/addons/cookoff/XEH_PREP.hpp +++ b/addons/cookoff/XEH_PREP.hpp @@ -3,6 +3,7 @@ PREP(cookOffBox); PREP(cookOffBoxLocal); PREP(cookOffEffect); PREP(detonateAmmunition); +PREP(detonateAmmunitionServer); PREP(engineFire); PREP(engineFireLocal); PREP(getVehicleAmmo); diff --git a/addons/cookoff/functions/fnc_detonateAmmunition.sqf b/addons/cookoff/functions/fnc_detonateAmmunition.sqf index 6ef74e277f..6bc3744f66 100644 --- a/addons/cookoff/functions/fnc_detonateAmmunition.sqf +++ b/addons/cookoff/functions/fnc_detonateAmmunition.sqf @@ -1,7 +1,7 @@ #include "..\script_component.hpp" /* - * Author: Glowbal, johnb43 - * Detonates ammunition from an object (e.g. vehicle or crate) until no ammo is left. + * Author: johnb43 + * Starts ammunition detonating from an object. * * Arguments: * 0: Object @@ -11,7 +11,7 @@ * 4: Initial delay (default: 0) * * Return Value: - * Nothing Useful + * None * * Example: * [cursorObject] call ace_cookoff_fnc_detonateAmmunition @@ -25,171 +25,19 @@ params ["_object", ["_destroyWhenFinished", false], ["_source", objNull], ["_ins if (isNull _object) exitWith {}; -private _objectAmmo = _object getVariable QGVAR(cookoffMagazines); +// Don't have an object detonate its ammo twice +if (!isNil {_object getVariable QGVAR(cookoffMagazines)}) exitWith {}; -if (isNil "_objectAmmo") then { - _objectAmmo = _object call FUNC(getVehicleAmmo); +_object setVariable [QGVAR(cookoffMagazines), _object call FUNC(getVehicleAmmo)]; - _object setVariable [QGVAR(cookoffMagazines), _objectAmmo]; +// TODO: When setMagazineTurretAmmo and magazineTurretAmmo are fixed (https://feedback.bistudio.com/T79689), +// we can add gradual ammo removal during cook-off +if (GVAR(removeAmmoDuringCookoff)) then { + clearMagazineCargoGlobal _object; - // TODO: When setMagazineTurretAmmo and magazineTurretAmmo are fixed (https://feedback.bistudio.com/T79689), - // we can add gradual ammo removal during cook-off - if (GVAR(removeAmmoDuringCookoff)) then { - clearMagazineCargoGlobal _object; - - { - _object removeMagazinesTurret [_x select 0, _x select 1]; - } forEach (magazinesAllTurrets _object); - }; + { + [QEGVAR(common,removeMagazinesTurret), [_object, _x select 0, _x select 1], _object, _x select 1] call CBA_fnc_turretEvent; + } forEach (magazinesAllTurrets _object); }; -_objectAmmo params ["_magazines", "_totalAmmo"]; - -// If the cook-off has finished, clean up the effects and destroy the object -if (_magazines isEqualTo [] || {_totalAmmo <= 0}) exitWith { - [QGVAR(cleanupBoxEffects), _object] call CBA_fnc_globalEvent; - - _object setVariable [QGVAR(cookoffMagazines), nil]; - - if (_destroyWhenFinished) then { - _object setDamage [1, true, _source, _instigator]; - }; -}; - -// If the cook-off is interrupted or disabled, clean up the effects -if (underwater _object || { - if (GVAR(ammoCookoffDuration) == 0) exitWith {true}; - - if (_object isKindOf "ReammoBox_F") exitWith { - !(GVAR(enableAmmobox) && {_object getVariable [QGVAR(enableAmmoCookoff), true]}) - }; - - !(GVAR(enableAmmoCookoff) && {_object getVariable [QGVAR(enableAmmoCookoff), true]}) -}) exitWith { - [QGVAR(cleanupBoxEffects), _object] call CBA_fnc_globalEvent; - - _object setVariable [QGVAR(cookoffMagazines), nil]; -}; - -// Initial delay allows for a delay for the first time this function runs in its cycle -if (_initialDelay > 0) exitWith { - [FUNC(detonateAmmunition), [_object, _destroyWhenFinished, _source, _instigator], _initialDelay] call CBA_fnc_waitAndExecute; -}; - -private _magazineIndex = floor random (count _magazines); -private _magazine = _magazines select _magazineIndex; -_magazine params ["_magazineClassname", "_ammoCount", "_spawnProjectile"]; - -// Make sure ammo is at least 0 -_ammoCount = _ammoCount max 0; - -// Remove some ammo, which will be detonated -private _removed = _ammoCount min floor (1 + random (6 / GVAR(ammoCookoffDuration))); - -_ammoCount = _ammoCount - _removed; - -if (_ammoCount <= 0) then { - _magazines deleteAt _magazineIndex; -} else { - _magazine set [1, _ammoCount]; // remove ammo that was detonated -}; - -private _timeBetweenAmmoDetonation = ((random 10 / sqrt _totalAmmo) min MAX_TIME_BETWEEN_AMMO_DET) max 0.1; -TRACE_2("",_totalAmmo,_timeBetweenAmmoDetonation); -_totalAmmo = _totalAmmo - _removed; - -_object setVariable [QGVAR(cookoffMagazines), [_magazines, _totalAmmo]]; - -// Detonate the remaining ammo after a delay -[FUNC(detonateAmmunition), [_object, _destroyWhenFinished, _source, _instigator], _timeBetweenAmmoDetonation] call CBA_fnc_waitAndExecute; - -// Get magazine info, which is used to spawn projectiles -private _configMagazine = configFile >> "CfgMagazines" >> _magazineClassname; -private _ammo = getText (_configMagazine >> "ammo"); -private _configAmmo = configFile >> "CfgAmmo" >> _ammo; - -private _simType = toLower getText (_configAmmo >> "simulation"); -private _speed = linearConversion [0, 1, random 1, 1, 20, true]; -private _effect2pos = _object selectionPosition "destructionEffect2"; - -// Spawns the projectiles, making them either fly in random directions or explode -private _fnc_spawnProjectile = { - // If the magazines are inside of the cargo (inventory), don't let their projectiles escape the interior of the vehicle - if (!_spawnProjectile) exitWith {}; - - params ["_object", "_ammo", "_speed", "_flyAway"]; - - private _spawnPos = _object modelToWorld [-0.2 + random 0.4, -0.2 + random 0.4, random 3]; - - if (_spawnPos select 2 < 0) then { - _spawnPos set [2, 0]; - }; - - private _projectile = createVehicle [_ammo, _spawnPos, [], 0, "CAN_COLLIDE"]; - - if (_flyAway) then { - private _vectorAmmo = [-1 + random 2, -1 + random 2, -0.2 + random 1]; - private _vectorVelocity = _vectorAmmo vectorMultiply _speed; - - _projectile setVectorDir _vectorVelocity; - _projectile setVelocity _vectorVelocity; - } else { - _projectile setDamage 1; - }; -}; - -switch (_simType) do { - case "shotbullet": { - [QGVAR(playCookoffSound), [_object, _simType]] call CBA_fnc_globalEvent; - - if (random 1 < 0.6) then { - [_object, _ammo, _speed, true] call _fnc_spawnProjectile; - }; - }; - case "shotshell": { - [QGVAR(playCookoffSound), [_object, _simType]] call CBA_fnc_globalEvent; - - if (random 1 < 0.15) then { - [_object, _ammo, _speed, true] call _fnc_spawnProjectile; - }; - }; - case "shotgrenade": { - if (random 1 < 0.9) then { - _speed = 0; - }; - - [_object, _ammo, _speed, random 1 < 0.5] call _fnc_spawnProjectile; - }; - case "shotrocket"; - case "shotmissile"; - case "shotsubmunitions": { - if (random 1 < 0.1) then { - [QGVAR(playCookoffSound), [_object, _simType]] call CBA_fnc_globalEvent; - - [_object, _ammo, _speed, random 1 < 0.3] call _fnc_spawnProjectile; - } else { - createVehicle ["ACE_ammoExplosionLarge", _object modelToWorld _effect2pos, [], 0 , "CAN_COLLIDE"]; - }; - }; - case "shotdirectionalbomb"; - case "shotmine": { - if (random 1 < 0.5) then { - // Not all explosives detonate on destruction, some have scripted alternatives - if (getNumber (_configAmmo >> "triggerWhenDestroyed") != 1) then { - _ammo = getText (_configAmmo >> QEGVAR(explosives,explosive)); - }; - - // If a scripted alternative doesn't exist use generic explosion - if (_ammo != "") then { - [_object, _ammo, 0, false] call _fnc_spawnProjectile; - } else { - createVehicle ["SmallSecondary", _object modelToWorld _effect2pos, [], 0 , "CAN_COLLIDE"]; - }; - }; - }; - case "shotilluminating": { - if (random 1 < 0.15) then { - [_object, _ammo, _speed, random 1 < 0.3] call _fnc_spawnProjectile; - }; - }; -}; +[FUNC(detonateAmmunitionServer), [_object, _destroyWhenFinished, _source, _instigator], _initialDelay] call CBA_fnc_waitAndExecute; diff --git a/addons/cookoff/functions/fnc_detonateAmmunitionServer.sqf b/addons/cookoff/functions/fnc_detonateAmmunitionServer.sqf new file mode 100644 index 0000000000..25bb4e43a5 --- /dev/null +++ b/addons/cookoff/functions/fnc_detonateAmmunitionServer.sqf @@ -0,0 +1,171 @@ +#include "..\script_component.hpp" +/* + * Author: Glowbal, johnb43 + * Detonates ammunition from an object (e.g. vehicle or crate) until no ammo is left. + * + * Arguments: + * 0: Object + * 1: Destroy when finished + * 2: Source + * 3: Instigator + * + * Return Value: + * Nothing Useful + * + * Example: + * [cursorObject, true, player, player] call ace_cookoff_fnc_detonateAmmunitionServer + * + * Public: No + */ + +if (!isServer) exitWith {}; + +params ["_object", "_destroyWhenFinished", "_source", "_instigator"]; + +if (isNull _object) exitWith {}; + +(_object getVariable QGVAR(cookoffMagazines)) params ["_magazines", "_totalAmmo"]; + +// If the cook-off has finished, clean up the effects and destroy the object +if (_magazines isEqualTo [] || {_totalAmmo <= 0}) exitWith { + [QGVAR(cleanupBoxEffects), _object] call CBA_fnc_globalEvent; + + _object setVariable [QGVAR(cookoffMagazines), nil]; + + if (_destroyWhenFinished) then { + _object setDamage [1, true, _source, _instigator]; + }; +}; + +// If the cook-off is interrupted or disabled, clean up the effects +if (underwater _object || { + if (GVAR(ammoCookoffDuration) == 0) exitWith {true}; + + if (_object isKindOf "ReammoBox_F") exitWith { + !(GVAR(enableAmmobox) && {_object getVariable [QGVAR(enableAmmoCookoff), true]}) + }; + + !(GVAR(enableAmmoCookoff) && {_object getVariable [QGVAR(enableAmmoCookoff), true]}) +}) exitWith { + [QGVAR(cleanupBoxEffects), _object] call CBA_fnc_globalEvent; + + _object setVariable [QGVAR(cookoffMagazines), nil]; +}; + +private _magazineIndex = floor random (count _magazines); +private _magazine = _magazines select _magazineIndex; +_magazine params ["_magazineClassname", "_ammoCount", "_spawnProjectile"]; + +// Make sure ammo is at least 0 +_ammoCount = _ammoCount max 0; + +// Remove some ammo, which will be detonated +private _removed = _ammoCount min floor (1 + random (6 / GVAR(ammoCookoffDuration))); + +_ammoCount = _ammoCount - _removed; + +if (_ammoCount <= 0) then { + _magazines deleteAt _magazineIndex; +} else { + _magazine set [1, _ammoCount]; // remove ammo that was detonated +}; + +private _timeBetweenAmmoDetonation = ((random 10 / sqrt _totalAmmo) min MAX_TIME_BETWEEN_AMMO_DET) max 0.1; +TRACE_2("",_totalAmmo,_timeBetweenAmmoDetonation); +_totalAmmo = _totalAmmo - _removed; + +_object setVariable [QGVAR(cookoffMagazines), [_magazines, _totalAmmo]]; + +// Detonate the remaining ammo after a delay +[FUNC(detonateAmmunitionServer), [_object, _destroyWhenFinished, _source, _instigator], _timeBetweenAmmoDetonation] call CBA_fnc_waitAndExecute; + +// Get magazine info, which is used to spawn projectiles +private _configMagazine = configFile >> "CfgMagazines" >> _magazineClassname; +private _ammo = getText (_configMagazine >> "ammo"); +private _configAmmo = configFile >> "CfgAmmo" >> _ammo; + +private _simType = toLower getText (_configAmmo >> "simulation"); +private _speed = linearConversion [0, 1, random 1, 1, 20, true]; +private _effect2pos = _object selectionPosition "destructionEffect2"; + +// Spawns the projectiles, making them either fly in random directions or explode +private _fnc_spawnProjectile = { + // If the magazines are inside of the cargo (inventory), don't let their projectiles escape the interior of the vehicle + if (!_spawnProjectile) exitWith {}; + + params ["_object", "_ammo", "_speed", "_flyAway"]; + + private _spawnPos = _object modelToWorld [-0.2 + random 0.4, -0.2 + random 0.4, random 3]; + + if (_spawnPos select 2 < 0) then { + _spawnPos set [2, 0]; + }; + + private _projectile = createVehicle [_ammo, _spawnPos, [], 0, "CAN_COLLIDE"]; + + if (_flyAway) then { + private _vectorAmmo = [-1 + random 2, -1 + random 2, -0.2 + random 1]; + private _vectorVelocity = _vectorAmmo vectorMultiply _speed; + + _projectile setVectorDir _vectorVelocity; + _projectile setVelocity _vectorVelocity; + } else { + _projectile setDamage 1; + }; +}; + +switch (_simType) do { + case "shotbullet": { + [QGVAR(playCookoffSound), [_object, _simType]] call CBA_fnc_globalEvent; + + if (random 1 < 0.6) then { + [_object, _ammo, _speed, true] call _fnc_spawnProjectile; + }; + }; + case "shotshell": { + [QGVAR(playCookoffSound), [_object, _simType]] call CBA_fnc_globalEvent; + + if (random 1 < 0.15) then { + [_object, _ammo, _speed, true] call _fnc_spawnProjectile; + }; + }; + case "shotgrenade": { + if (random 1 < 0.9) then { + _speed = 0; + }; + + [_object, _ammo, _speed, random 1 < 0.5] call _fnc_spawnProjectile; + }; + case "shotrocket"; + case "shotmissile"; + case "shotsubmunitions": { + if (random 1 < 0.1) then { + [QGVAR(playCookoffSound), [_object, _simType]] call CBA_fnc_globalEvent; + + [_object, _ammo, _speed, random 1 < 0.3] call _fnc_spawnProjectile; + } else { + createVehicle ["ACE_ammoExplosionLarge", _object modelToWorld _effect2pos, [], 0 , "CAN_COLLIDE"]; + }; + }; + case "shotdirectionalbomb"; + case "shotmine": { + if (random 1 < 0.5) then { + // Not all explosives detonate on destruction, some have scripted alternatives + if (getNumber (_configAmmo >> "triggerWhenDestroyed") != 1) then { + _ammo = getText (_configAmmo >> QEGVAR(explosives,explosive)); + }; + + // If a scripted alternative doesn't exist use generic explosion + if (_ammo != "") then { + [_object, _ammo, 0, false] call _fnc_spawnProjectile; + } else { + createVehicle ["SmallSecondary", _object modelToWorld _effect2pos, [], 0 , "CAN_COLLIDE"]; + }; + }; + }; + case "shotilluminating": { + if (random 1 < 0.15) then { + [_object, _ammo, _speed, random 1 < 0.3] call _fnc_spawnProjectile; + }; + }; +};