diff --git a/addons/casings/XEH_PREP.hpp b/addons/casings/XEH_PREP.hpp index 8a0738b2724..1dab059008f 100644 --- a/addons/casings/XEH_PREP.hpp +++ b/addons/casings/XEH_PREP.hpp @@ -1 +1,3 @@ PREP(createCasing); +PREP(createLitter); +PREP(createMagazine); diff --git a/addons/casings/XEH_postInit.sqf b/addons/casings/XEH_postInit.sqf index 47737c4e04a..622dbc96db2 100644 --- a/addons/casings/XEH_postInit.sqf +++ b/addons/casings/XEH_postInit.sqf @@ -6,7 +6,9 @@ if (!hasInterface) exitWith {}; if (!GVAR(enabled)) exitWith {}; GVAR(cachedCasings) = createHashMap; + GVAR(cachedMagazines) = createHashMap; GVAR(casings) = []; ["CAManBase", "FiredMan", LINKFUNC(createCasing)] call CBA_fnc_addClassEventHandler; + [QGVAR(reloaded), "Reloaded", LINKFUNC(createMagazine)] call CBA_fnc_addBISPlayerEventHandler; }] call CBA_fnc_addEventHandler; diff --git a/addons/casings/functions/fnc_createCasing.sqf b/addons/casings/functions/fnc_createCasing.sqf index 990bef16b2e..c1c246a08c4 100644 --- a/addons/casings/functions/fnc_createCasing.sqf +++ b/addons/casings/functions/fnc_createCasing.sqf @@ -22,55 +22,26 @@ if (!isNull objectParent _unit) exitWith {}; private _modelPath = GVAR(cachedCasings) getOrDefaultCall [_ammo, { - private _cartridge = getText (configFile >> "CfgAmmo" >> _ammo >> "cartridge"); - if (_cartridge == "") then { // return (note: can't use exitWith) - "" - } else { - private _cartridgeConfig = configFile >> "CfgVehicles" >> _cartridge; + private _cartridgeConfig = configNull; // private var in switch condition scope isn't available in the result block. + private _modelOverride = ""; // very annoying. - // if explicitly defined, use ACE's config - if (isText (_cartridgeConfig >> QGVAR(model))) exitWith { - getText (_cartridgeConfig >> QGVAR(model)) - }; - // use casing's default model - private _model = getText (_cartridgeConfig >> "model"); - if ("a3\weapons_f\empty" in toLowerANSI _model) exitWith { "" }; + private _cartridge = getText (configFile >> "CfgAmmo" >> _ammo >> "cartridge"); + private _model = switch (true) do { + case (_cartridge == ""): { "" }; - // Add file extension if missing (fileExists needs file extension) - if ((_model select [count _model - 4]) != ".p3d") then { - _model = _model + ".p3d"; - }; + _cartridgeConfig = configFile >> "CfgVehicles" >> _cartridge; + _modelOverride = getText (_cartridgeConfig >> QGVAR(model)); - ["", _model] select (fileExists _model) + case (_modelOverride != ""): { _modelOverride }; // Use the override if non-empty + default { getText (_cartridgeConfig >> "model") } // Use the casing's default model }; -}, true]; - -if (_modelPath isEqualTo "") exitWith {}; -private _unitPos = getPosASL _unit; -// Distant shooters don't produce as many cases -if ((AGLToASL positionCameraToWorld [0,0,0]) vectorDistance _unitPos > 100 && {random 1 < 0.9}) exitWith {}; - -private _weapDir = _unit weaponDirection currentWeapon _unit; -private _ejectDir = _weapDir vectorCrossProduct [0, 0, 1]; -private _pos = _unitPos - vectorAdd (_weapDir vectorMultiply (-0.5 + random 2)) - vectorAdd (_ejectDir vectorMultiply (0.2 + random 2)); - -[ - { - params ["_modelPath", "_pos"]; + // Add file extension if missing (fileExists needs file extension) + if ((_model select [count _model - 4]) != ".p3d") then { + _model = _model + ".p3d"; + }; - private _lisPos = (lineIntersectsSurfaces [_pos, _pos vectorAdd [0,0,-1e11], objNull, objNull, true, 1, "ROADWAY", "FIRE"]) #0; - private _casing = createSimpleObject [_modelPath, (_lisPos #0 vectorAdd [0,0,0.005]), true]; - _casing setDir (random 360); - _casing setVectorUp _lisPos #1; - private _idx = GVAR(casings) pushBack _casing; + ["", _model] select (!("a3\weapons_f\empty" in toLowerANSI _model) && {fileExists _model}) +}, true]; - for "_" from 0 to (_idx - GVAR(maxCasings)) do { - deleteVehicle (GVAR(casings) deleteAt 0); - }; - }, - [_modelPath,_pos], - 0.4 -] call CBA_fnc_waitAndExecute; +[_unit, _modelPath] call FUNC(createLitter); diff --git a/addons/casings/functions/fnc_createLitter.sqf b/addons/casings/functions/fnc_createLitter.sqf new file mode 100644 index 00000000000..f9a26c68418 --- /dev/null +++ b/addons/casings/functions/fnc_createLitter.sqf @@ -0,0 +1,51 @@ +#include "..\script_component.hpp" +/* + * Author: esteldunedain / Cyruz / diwako + * Handles casing/dropped mag creation. + * + * Arguments: + * 0: Unit - Unit to create litter for + * 1: Model - Path to litter model + * 2: Force creation - Skip the distance + RNG check (default: false) + * + * Return Value: + * None + * + * Example: + * [player, "\a3\weapons_f\mag_univ.p3d"] call ace_casings_fnc_createLitter + * + * Public: No + */ + +params ["_unit", "_modelPath", ["_force", false]]; + +if (_modelPath == "") exitWith {}; + +private _unitPos = getPosASL _unit; +// Distant shooters don't produce as many cases +if (!_force && {(AGLToASL positionCameraToWorld [0,0,0]) vectorDistance _unitPos > 100 && {random 1 < 0.9}}) exitWith {}; + +private _weapDir = _unit weaponDirection currentWeapon _unit; +private _ejectDir = _weapDir vectorCrossProduct [0, 0, 1]; +private _pos = _unitPos + vectorAdd (_weapDir vectorMultiply (-0.5 + random 2)) + vectorAdd (_ejectDir vectorMultiply (0.2 + random 2)); + +[ + { + params ["_modelPath", "_pos"]; + TRACE_2("creating litter",_modelPath,_pos); + + private _lisPos = (lineIntersectsSurfaces [_pos, _pos vectorAdd [0,0,-1e11], objNull, objNull, true, 1, "ROADWAY", "FIRE"]) #0; + private _casing = createSimpleObject [_modelPath, (_lisPos #0 vectorAdd [0,0,0.010]), false]; // global + _casing setDir (random 360); + _casing setVectorUp _lisPos #1; + private _idx = GVAR(casings) pushBack _casing; + + for "_" from 0 to (_idx - GVAR(maxCasings)) do { + deleteVehicle (GVAR(casings) deleteAt 0); + }; + }, + [_modelPath,_pos], + 0.4 +] call CBA_fnc_waitAndExecute; diff --git a/addons/casings/functions/fnc_createMagazine.sqf b/addons/casings/functions/fnc_createMagazine.sqf new file mode 100644 index 00000000000..0d917fe5c20 --- /dev/null +++ b/addons/casings/functions/fnc_createMagazine.sqf @@ -0,0 +1,51 @@ +#include "..\script_component.hpp" +/* + * Author: GabrielPearce / esteldunedain / Cyruz / diwako / PabstMirror + * Produces a casing matching the reloaded and dropped magazine + * + * Arguments: + * 0: unit - Object the reloaded event handler is assigned to + * 4: Old magazine (can be nil) - + * + * Return Value: + * None + * + * Example: + * [player, "", "","", ["1Rnd_HE_Grenade_shell", 0]] call ace_casings_fnc_createMagazine + * + * Public: No + */ + +params ["_unit", "", "", "", "_oldMagazine"]; +TRACE_2("createMagazine",_unit,_oldMagazine); + +if (isNil "_oldMagazine") exitWith {}; +_oldMagazine params ["_mag", "_ammo"]; +if (_ammo != 0) exitWith {}; + +private _modelPath = GVAR(cachedMagazines) getOrDefaultCall [_mag, { + private _magConfig = configNull; // private var in switch condition scope isn't available in the result block. + private _modelOverride = ""; // very annoying. + + private _model = switch true do { + // Should cover most 40x36 + case (_mag in compatibleMagazines ["arifle_Mk20_GL_F", "EGLM"]): { "A3\Weapons_F\MagazineProxies\mag_40x36_HE_1rnd.p3d" }; + + _magConfig = configFile >> "CfgMagazines" >> _mag; + _modelOverride = getText (_magConfig >> QGVAR(model)); + + case (_modelOverride != ""): { _modelOverride }; // Use the override if non-empty + case (getNumber (_magConfig >> "modelSpecialIsProxy") == 1): {getText (_magConfig >> "modelSpecial")}; // Use the magazine's proxy + + default { getText (_magConfig >> "model") }; // Use the magazine's dropped model + }; + + // Add file extension if missing (fileExists needs file extension) + if ((_model select [count _model - 4]) != ".p3d") then { + _model = _model + ".p3d"; + }; + + ["", _model] select (!(_model regexMatch "(?:\\)?a3\\weapons_f\\(?:empty|ammo\\mag_univ).p3d") && {fileExists _model}) +}, true]; + +[_unit, _modelPath, true] call FUNC(createLitter); diff --git a/docs/wiki/feature/casings.md b/docs/wiki/feature/casings.md index 8bcf24a6969..e1543f92dc0 100644 --- a/docs/wiki/feature/casings.md +++ b/docs/wiki/feature/casings.md @@ -2,7 +2,7 @@ layout: wiki title: Casings component: casings -description: Adds infantry bullet casings on the ground when weapons are fired. +description: Adds infantry bullet casings on the ground when weapons are fired and drops empty magazines when reloading. group: feature category: realism parent: wiki @@ -14,4 +14,4 @@ version: --- ## 1. Overview -Spits out casings from infantry weapons when fired. +Spits out casings from infantry weapons when fired and drops empty magazines when reloading. diff --git a/docs/wiki/framework/casings-framework.md b/docs/wiki/framework/casings-framework.md new file mode 100644 index 00000000000..df4ee6a9e4d --- /dev/null +++ b/docs/wiki/framework/casings-framework.md @@ -0,0 +1,42 @@ +--- +layout: wiki +title: Casings Framework +description: Explains how to set-up dropped magazine models with ACE3 casings system. +group: framework +order: 5 +parent: wiki +mod: ace +version: + major: 3 + minor: 0 + patch: 0 +--- + +## 1. Config Values + +```cpp +class CfgAmmo { // In order of priority + class MyAmmo { + cartridge = "CartridgeClassnameInCfgVehicles"; // if empty (""), no casing will be created + ace_casings_model = "path\to\casing\model.p3d"; // Dropped casing will have this model if not an empty string + }; +}; + +class CfgVehicles { + class MyCartridge { + model = "path\to\cartridge\model.p3d"; // If empty string or model, no casing will be created + }; +}; + +class CfgMagazines { + class MyMagazine { // In order of priority + // If magazine is compatible with vanilla Mk20's EGLM, model will always be "A3\Weapons_F\MagazineProxies\mag_40x36_HE_1rnd.p3d" + ace_casings_model = "path\to\magazine\model.p3d"; //Dropped magazine will have this model if not an empty string + modelSpecialIsProxy = 1; + modelSpecial = "path\to\magazine\proxy\model.p3d"; // Proxy model + model = "path\to\magazine\dropped\model.p3d"; // Dropped model + + // If model doesn't exist, is the default dropped magazine model (pouch), or is empty, no dropped magazine will be created. + }; +}; +```