diff --git a/addons/weapononback/$PBOPREFIX$ b/addons/weapononback/$PBOPREFIX$ new file mode 100644 index 00000000000..6c80c84ab44 --- /dev/null +++ b/addons/weapononback/$PBOPREFIX$ @@ -0,0 +1 @@ +z\ace\addons\weapononback diff --git a/addons/weapononback/CfgEventHandlers.hpp b/addons/weapononback/CfgEventHandlers.hpp new file mode 100644 index 00000000000..f5826ce696e --- /dev/null +++ b/addons/weapononback/CfgEventHandlers.hpp @@ -0,0 +1,31 @@ +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 { + init = QUOTE(call COMPILE_FILE(XEH_postInit)); + }; +}; + +class Extended_DisplayLoad_EventHandlers { + class RscDisplayInventory { + ADDON = QUOTE(call FUNC(onInventoryOpened)); + }; +}; + +class Extended_Init_EventHandlers { + class WeaponHolderSimulated { + class ADDON { + init = QUOTE(_this call FUNC(onWHSInit)); + }; + }; +}; diff --git a/addons/weapononback/CfgVehicles.hpp b/addons/weapononback/CfgVehicles.hpp new file mode 100644 index 00000000000..ff831c13dab --- /dev/null +++ b/addons/weapononback/CfgVehicles.hpp @@ -0,0 +1,14 @@ +class CfgVehicles { + class ReammoBox; + class GVAR(weaponHolder): ReammoBox { + author = ECSTRING(common,ACETeam); + model = QPATHTOF(data\holder.p3d); + showWeaponCargo = 1; + scope = 1; + transportMaxWeapons = 1; + transportMaxMagazines = 0; + transportMaxItems = 0; + transportMaxBackpacks = 0; + destrType = "DestructNo"; + }; +}; diff --git a/addons/weapononback/CfgWeapons.hpp b/addons/weapononback/CfgWeapons.hpp new file mode 100644 index 00000000000..df009bf059b --- /dev/null +++ b/addons/weapononback/CfgWeapons.hpp @@ -0,0 +1,16 @@ +class CfgWeapons { + class Launcher_Base_F; + class GVAR(weapon): Launcher_Base_F { + scope = 1; + scopeCurator = 1; + scopeArsenal = 1; + model = "\A3\Weapons_f\empty"; + picture = "\A3\Weapons_F\Data\clear_empty.paa"; + magazines[] = {"ACE_FakeMagazine"}; + displayName = CSTRING(FakeWeaponDisplayName); + type = TYPE_WEAPON_SECONDARY; + class WeaponSlotsInfo { + mass = 0; + }; + }; +}; diff --git a/addons/weapononback/README.md b/addons/weapononback/README.md new file mode 100644 index 00000000000..d6d7fc484bc --- /dev/null +++ b/addons/weapononback/README.md @@ -0,0 +1,11 @@ +ace_weapononback +=================== + +Adds the possibility to have a second primary weapon. + + +## Maintainers + +The people responsible for merging changes to this component or answering potential questions. + +- [BaerMitUmlaut](https://github.com/BaerMitUmlaut) diff --git a/addons/weapononback/RscDisplayInventory.hpp b/addons/weapononback/RscDisplayInventory.hpp new file mode 100644 index 00000000000..044aaab0d53 --- /dev/null +++ b/addons/weapononback/RscDisplayInventory.hpp @@ -0,0 +1,16 @@ +class RscActiveText; + +class RscDisplayInventory { + class controls { + class GVAR(weaponImage): RscActiveText { + idc = IDC_WEAPON_IMAGE; + style = "0x30 + 0x800"; + color[] = {1, 1, 1, 1}; + colorFocused[] = {1, 1, 1, 1}; + colorText[] = {1, 1, 1, 1}; + colorBackground[] = {1, 1, 1, 0.1}; + colorBackgroundSelected[] = {1, 1, 1, 0.1}; + onLoad = "_this#0 ctrlEnable false"; + }; + }; +}; diff --git a/addons/weapononback/XEH_PREP.hpp b/addons/weapononback/XEH_PREP.hpp new file mode 100644 index 00000000000..995d5686e9b --- /dev/null +++ b/addons/weapononback/XEH_PREP.hpp @@ -0,0 +1,14 @@ +PREP(add); +PREP(get); +PREP(getIDCContainer); +PREP(onDrag); +PREP(onDragWOB); +PREP(onDrop); +PREP(onDropWOB); +PREP(onInventoryOpened); +PREP(onWeaponChange); +PREP(onWHSInit); +PREP(remove); +PREP(renderPFH); +PREP(swap); +PREP(updateInventory); diff --git a/addons/weapononback/XEH_postInit.sqf b/addons/weapononback/XEH_postInit.sqf new file mode 100644 index 00000000000..31e7de7404d --- /dev/null +++ b/addons/weapononback/XEH_postInit.sqf @@ -0,0 +1,43 @@ +#include "script_component.hpp" + +["weapon", FUNC(onWeaponChange)] call CBA_fnc_addPlayerEventHandler; + +["unit", { + params ["_newUnit", "_oldUnit"]; + + private _inventoryEHs = _oldUnit getVariable [QGVAR(inventoryEHs), [-1, -1]]; + _oldUnit removeEventHandler ["InventoryOpened", _inventoryEHs#0]; + _oldUnit removeEventHandler ["InventoryClosed", _inventoryEHs#1]; + + _inventoryEHs = [ + _newUnit addEventHandler ["InventoryOpened", { + params ["", "_firstContainer", "_secondContainer"]; + + // If only one container was opened, pretend it's both box and ground + // WOB holder sometimes gets opened for a frame, but is immediately + // closed and should never be accessed anyways + if (isNull _secondContainer || {typeOf _secondContainer == QGVAR(weaponHolder)}) then { + _secondContainer = _firstContainer; + }; + GVAR(openedContainers) = [_firstContainer, _secondContainer]; + }], + _newUnit addEventHandler ["InventoryClosed", { + GVAR(openedContainers) = [objNull, objNull]; + }] + ]; + + _newUnit setVariable [QGVAR(inventoryEHs), _inventoryEHs]; +}, true] call CBA_fnc_addPlayerEventHandler; + +["loadout", { + // One frame delay, because dropping from inventory isn't instant but still handled by us + // See also fnc_onDragWOB + [{ + private _hasWeaponHolder = !isNull (ACE_player getVariable [QGVAR(weaponHolder), objNull]); + private _hasFakeWeapon = secondaryWeapon ACE_player == QGVAR(weapon); + + if !(_hasWeaponHolder isEqualTo _hasFakeWeapon) then { + [ACE_player] call FUNC(remove); + }; + }] call CBA_fnc_execNextFrame; +}] call CBA_fnc_addPlayerEventHandler; diff --git a/addons/weapononback/XEH_preInit.sqf b/addons/weapononback/XEH_preInit.sqf new file mode 100644 index 00000000000..43d3c140991 --- /dev/null +++ b/addons/weapononback/XEH_preInit.sqf @@ -0,0 +1,28 @@ +#include "script_component.hpp" + +ADDON = false; + +PREP_RECOMPILE_START; +#include "XEH_PREP.hpp" +PREP_RECOMPILE_END; + +#include "initSettings.sqf" + + +GVAR(units) = []; +GVAR(openedContainers) = [objNull, objNull]; + +// Add "add" event handler in preInit to catch JIP events +[QGVAR(add), { + params ["_unit", "_weaponsItems"]; + + [_unit, _weaponsItems, true] call FUNC(add); +}] call CBA_fnc_addEventHandler; + +[QGVAR(remove), { + params ["_unit"]; + + [_unit, true] call FUNC(remove); +}] call CBA_fnc_addEventHandler; + +ADDON = true; diff --git a/addons/weapononback/XEH_preStart.sqf b/addons/weapononback/XEH_preStart.sqf new file mode 100644 index 00000000000..022888575ed --- /dev/null +++ b/addons/weapononback/XEH_preStart.sqf @@ -0,0 +1,3 @@ +#include "script_component.hpp" + +#include "XEH_PREP.hpp" diff --git a/addons/weapononback/config.cpp b/addons/weapononback/config.cpp new file mode 100644 index 00000000000..7ecf732af86 --- /dev/null +++ b/addons/weapononback/config.cpp @@ -0,0 +1,20 @@ +#include "script_component.hpp" + +class CfgPatches { + class ADDON { + name = COMPONENT_NAME; + units[] = {}; + weapons[] = {}; + requiredVersion = REQUIRED_VERSION; + requiredAddons[] = {"ace_common"}; + author = ECSTRING(common,ACETeam); + authors[] = {"BaerMitUmlaut"}; + url = ECSTRING(main,URL); + VERSION_CONFIG; + }; +}; + +#include "CfgEventHandlers.hpp" +#include "CfgVehicles.hpp" +#include "CfgWeapons.hpp" +#include "RscDisplayInventory.hpp" diff --git a/addons/weapononback/data/holder.p3d b/addons/weapononback/data/holder.p3d new file mode 100644 index 00000000000..f222852897f Binary files /dev/null and b/addons/weapononback/data/holder.p3d differ diff --git a/addons/weapononback/data/model.cfg b/addons/weapononback/data/model.cfg new file mode 100644 index 00000000000..a17a0063010 --- /dev/null +++ b/addons/weapononback/data/model.cfg @@ -0,0 +1,27 @@ +class CfgSkeletons +{ + class Skeleton + { + isDiscrete=0; + skeletonInherit=""; + skeletonBones[]={}; + }; +}; +class CfgModels +{ + class holder + { + htMin=0; + htMax=0; + afMax=0; + mfMax=0; + mFact=0; + tBody=0; + skeletonName="Skeleton"; + sectionsInherit=""; + sections[]={}; + class Animations + { + }; + }; +}; diff --git a/addons/weapononback/data/weapononback_ca.paa b/addons/weapononback/data/weapononback_ca.paa new file mode 100644 index 00000000000..621fcfa132f Binary files /dev/null and b/addons/weapononback/data/weapononback_ca.paa differ diff --git a/addons/weapononback/functions/fnc_add.sqf b/addons/weapononback/functions/fnc_add.sqf new file mode 100644 index 00000000000..8204ffe9de0 --- /dev/null +++ b/addons/weapononback/functions/fnc_add.sqf @@ -0,0 +1,79 @@ +#include "script_component.hpp" +/* + * Author: BaerMitUmlaut + * Adds a weapon to the units back (global effect). + * + * Arguments: + * 0: Unit + * 1: Weapons items (weaponsItems format) + * + * Return Value: + * None + * + * Example: + * [player, weaponsItems player select 1] call ace_weapononback_fnc_add + * + * Public: Yes + */ +params ["_unit", "_weaponsItems", ["_calledFromEvent", false]]; + +if (!_calledFromEvent) exitWith { + [QGVAR(add), [_unit, _weaponsItems], EVENT_ID(_unit)] call CBA_fnc_globalEventJIP; +}; + +// Replace old weapon holder +private _weaponHolder = _unit getVariable [QGVAR(weaponHolder), objNull]; +private _oldWeapon = ""; +if (!isNull _weaponHolder) then { + deleteVehicle _weaponHolder; +}; + +_weaponHolder = QGVAR(weaponHolder) createVehicleLocal [0, 0, 0]; +_weaponHolder attachTo [_unit, [0, 0, 0], "proxy:\a3\characters_f\proxies\launcher.001"]; + +// Add weapon to weapon holder +_weaponHolder addWeaponWithAttachmentsCargo [_weaponsItems, 1]; + +// Disable simulation to lock +_weaponHolder enableSimulation false; + +_unit setVariable [QGVAR(weaponHolder), _weaponHolder]; +GVAR(units) pushBack _unit; + +if (local _unit) then { + private _oldMass = _unit getVariable [QGVAR(weaponMass), 0]; + private _newMass = 0; + + // Calculate weapon weight with all items + _weaponsItems params ["_weapon", "_muzzle", "_side", "_optic", "_magPrimary", "_magSecondary", "_bipod"]; + + // Weapon itself + _newMass = _newMass + getNumber (configFile >> "CfgWeapons" >> _weapon >> "WeaponSlotsInfo" >> "mass"); + + // Attachments + { + if (_x != "") then { + _newMass = _newMass + getNumber (configFile >> "CfgWeapons" >> _x >> "ItemInfo" >> "mass"); + }; + } forEach [_muzzle, _side, _optic, _bipod]; + + // Magazines + { + if !(_x isEqualTo []) then { + _newMass = _newMass + getNumber (configFile >> "CfgMagazines" >> _x#0 >> "mass"); + }; + } forEach [_magPrimary, _magSecondary]; + + [_unit, _unit, _newMass - _oldMass] call EFUNC(movement,addLoadToUnitContainer); + _unit setVariable [QGVAR(weaponMass), _newMass, true]; + + // Reset saved zeroing, if this weapon was swapped it will be restored in FUNC(swap) + ACE_player setVariable [QGVAR(scopeAdjustment), [0, 0, 0]]; + + _unit addWeapon QGVAR(weapon); + [] call FUNC(updateInventory); +}; + +if (isNil QGVAR(renderPFH)) then { + GVAR(renderPFH) = [FUNC(renderPFH), 0, []] call CBA_fnc_addPerFrameHandler; +}; diff --git a/addons/weapononback/functions/fnc_get.sqf b/addons/weapononback/functions/fnc_get.sqf new file mode 100644 index 00000000000..a6c15c22e9f --- /dev/null +++ b/addons/weapononback/functions/fnc_get.sqf @@ -0,0 +1,25 @@ +#include "script_component.hpp" +/* + * Author: BaerMitUmlaut + * Returns the weapon on a units back. + * + * Arguments: + * 0: Unit + * + * Return Value: + * Weapon, attachments and magazines (weaponsItems format) or empty array + * + * Example: + * [player] call ace_weapononback_fnc_get + * + * Public: Yes + */ +params ["_unit"]; + +private _weaponHolder = _unit getVariable [QGVAR(weaponHolder), objNull]; + +if (isNull _weaponHolder) then { + [] +} else { + (weaponsItemsCargo _weaponHolder)#0 +}; diff --git a/addons/weapononback/functions/fnc_getIDCContainer.sqf b/addons/weapononback/functions/fnc_getIDCContainer.sqf new file mode 100644 index 00000000000..7ebd47d10b6 --- /dev/null +++ b/addons/weapononback/functions/fnc_getIDCContainer.sqf @@ -0,0 +1,35 @@ +#include "script_component.hpp" +/* + * Author: BaerMitUmlaut + * Gets the container associated with the given inventory display IDC. + * + * Arguments: + * 0: Control IDC + * + * Return Value: + * Container + * + * Public: No + */ +params ["_ctrlIDC"]; + +switch (_ctrlIDC) do { + case IDC_GROUND_CONTAINER: { + GVAR(openedContainers)#1 + }; + case IDC_SOLDIER_CONTAINER: { + GVAR(openedContainers)#0 + }; + case IDC_UNIFORM_CONTAINER: { + uniformContainer ACE_player + }; + case IDC_VEST_CONTAINER: { + vestContainer ACE_player + }; + case IDC_BACKPACK_CONTAINER: { + backpackContainer ACE_player + }; + default { + objNull + }; +}; diff --git a/addons/weapononback/functions/fnc_onDrag.sqf b/addons/weapononback/functions/fnc_onDrag.sqf new file mode 100644 index 00000000000..01baf4b0b86 --- /dev/null +++ b/addons/weapononback/functions/fnc_onDrag.sqf @@ -0,0 +1,94 @@ +#include "script_component.hpp" +/* + * Author: BaerMitUmlaut + * Handles dragging weapons onto the secondary slot. + * + * Arguments: + * 0: Drag origin + * 1: Information about the dragged item + * + * Return Value: + * None + * + * Public: No + */ +params ["_ctrl", "_info"]; + +if !(secondaryWeapon ACE_player in ["", QGVAR(weapon)]) exitWith {}; + +// Check if dragged item is a primary weapon +private _ctrlIDC = ctrlIDC _ctrl; +private _isPrimary = if (_ctrlIDC == IDC_PRIMARY_SLOT) then { + (primaryWeapon ACE_player != "") && {_info == 0} +} else { + // Dragged from container + _info#0 params ["_displayName"]; + + private _container = [_ctrlIDC] call FUNC(getIDCContainer); + if (_container isKindOf "CAManBase") then { + _container = _container getVariable [QGVAR(droppedWeaponHolder), _container]; + }; + + private _index = (weaponCargo _container) findIf { + private _cfg = configFile >> "CfgWeapons" >> _x; + (getText (_cfg >> "displayName") == _displayName) + && {getNumber (_cfg >> "type") == TYPE_WEAPON_PRIMARY} + }; + + _index != -1 +}; + +if (!_isPrimary) exitWith { + TRACE_1("Dragged item is not a primary",_info); +}; + +// Set up drag and drop catcher +private _display = ctrlParent _ctrl; +private _slotSecondary = _display displayCtrl IDC_SECONDARY_SLOT; + +// RscPicture does not trigger LBDrop +private _dropCatcher = _display ctrlCreate ["ctrlActivePictureKeepAspect", IDC_DROP_CATCHER]; +if (secondaryWeapon ACE_player != QGVAR(weapon)) then { + _dropCatcher ctrlSetText QPATHTOF(data\weapononback_ca.paa); +} else { + _dropCatcher ctrlSetText "#(rgb,8,8,3)color(1,1,1,0)"; +}; +_dropCatcher ctrlSetPosition ctrlPosition _slotSecondary; +_dropCatcher ctrlSetTextColor [1, 1, 1, 1]; +_dropCatcher ctrlSetBackgroundColor [1, 1, 1, 0]; +_dropCatcher ctrlCommit 0; +_dropCatcher ctrlAddEventHandler ["LBDrop", FUNC(onDrop)]; + +// Hide red "you can't drop this here" +_slotSecondary ctrlSetFade 1; +_slotSecondary ctrlCommit 0; + +// Create white "you can drop this here" +private _secondaryBG = _display displayCtrl IDC_SECONDARY_BG; +_secondaryBG ctrlSetText "#(rgb,8,8,3)color(1,1,1,0.25)"; + +// Remove drop catcher when drag and drop action was completed +private _eventHandler = _display displayAddEventHandler ["MouseButtonUp", { + params ["_display"]; + + // Reset everything to its original state + private _eventHandler = _display getVariable QGVAR(mouseUpEH); + _display displayRemoveEventHandler ["MouseButtonUp", _eventHandler]; + + private _slotSecondary = _display displayCtrl IDC_SECONDARY_SLOT; + _slotSecondary ctrlSetFade 0; + _slotSecondary ctrlCommit 0; + + private _secondaryBG = _display displayCtrl IDC_SECONDARY_BG; + _secondaryBG ctrlSetText ""; + + // Delay drop catcher destruction by one frame or drop event doesn't trigger + private _dropCatcher = _display displayCtrl IDC_DROP_CATCHER; + _dropCatcher ctrlSetText "#(rgb,8,8,3)color(1,1,1,0)"; + + [{ + params ["_display"]; + ctrlDelete (_display displayCtrl IDC_DROP_CATCHER); + }, _this] call CBA_fnc_execNextFrame; +}]; +_display setVariable [QGVAR(mouseUpEH), _eventHandler]; diff --git a/addons/weapononback/functions/fnc_onDragWOB.sqf b/addons/weapononback/functions/fnc_onDragWOB.sqf new file mode 100644 index 00000000000..2effcb285d7 --- /dev/null +++ b/addons/weapononback/functions/fnc_onDragWOB.sqf @@ -0,0 +1,100 @@ +#include "script_component.hpp" +/* + * Author: BaerMitUmlaut + * Handles dragging the weapon on the back onto a container. + * + * Arguments: + * 0: Secondary slot control + * 1: Mouse button ID + * + * Return Value: + * None + * + * Public: No + */ +params ["_ctrl", "_mouseButton"]; + +if (_mouseButton != 0 || {secondaryWeapon ACE_player != QGVAR(weapon)}) exitWith {}; + +// Replace primary slot with drop catcher, make background white +private _display = ctrlParent _ctrl; +private _primarySlot = _display displayCtrl IDC_PRIMARY_SLOT; +_primarySlot ctrlSetFade 1; +_primarySlot ctrlCommit 0; +private _primaryBG = _display displayCtrl IDC_PRIMARY_BG; +_primaryBG ctrlSetText "#(rgb,8,8,3)color(1,1,1,0.25)"; + +// RscPicture does not trigger LBDrop +private _dropCatcher = _display ctrlCreate ["ctrlActivePictureKeepAspect", IDC_DROP_CATCHER]; +if (primaryWeapon ACE_player == "") then { + _dropCatcher ctrlSetText "a3\ui_f\data\GUI\Rsc\RscDisplayGear\ui_gear_primary_gs.paa"; +} else { + _dropCatcher ctrlSetText getText (configFile >> "CfgWeapons" >> primaryWeapon ACE_player >> "picture"); +}; +_dropCatcher ctrlSetPosition ctrlPosition _primarySlot; +_dropCatcher ctrlSetTextColor [1, 1, 1, 1]; +_dropCatcher ctrlSetBackgroundColor [1, 1, 1, 1]; +_dropCatcher ctrlCommit 0; +_dropCatcher ctrlAddEventHandler ["LBDrop", {[true] call FUNC(onDropWOB)}]; + +// Create image for the currently dragged weapon under the cursor +private _imgContainer = _display displayCtrl IDC_WEAPON_IMAGE; +private _dragImage = _display ctrlCreate ["RscPictureKeepAspect", -1]; + +_dragImage ctrlSetPosition ctrlPosition _imgContainer; +_dragImage ctrlSetText ctrlText _imgContainer; +_dragImage ctrlEnable false; +_dragImage ctrlCommit 0; + +(ctrlPosition _dragImage) params ["", "", "_w", "_h"]; +private _widthOffset = _w / 2; +private _heightOffset = _h / 2; +_dragImage ctrlSetPosition [getMousePosition#0 - _widthOffset, getMousePosition#1 - _heightOffset]; +_dragImage ctrlCommit 0; + +private _eventHandlers = [ + _display displayAddEventHandler ["MouseMoving", { + params ["_display"]; + + private _dragImage = _display getVariable [QGVAR(dragImage), ctrlNull]; + (ctrlPosition _dragImage) params ["", "", "_w", "_h"]; + private _widthOffset = _w / 2; + private _heightOffset = _h / 2; + _dragImage ctrlSetPosition [getMousePosition#0 - _widthOffset, getMousePosition#1 - _heightOffset]; + _dragImage ctrlCommit 0; + }], + _display displayAddEventHandler ["MouseButtonUp", { + params ["_display"]; + + private _dragImage = _display getVariable [QGVAR(dragImage), ctrlNull]; + ctrlDelete _dragImage; + + // Clean up event handlers + private _eventHandlers = _display getVariable [QGVAR(dragWOBEHs), []]; + _eventHandlers params ["_mouseMoving", "_mouseButtonUp"]; + _display displayRemoveEventHandler ["MouseMoving", _mouseMoving]; + _display displayRemoveEventHandler ["MouseButtonUp", _mouseButtonUp]; + + // LBDrop triggers after MouseButtonUp, so delay by one frame + [{ + params ["_display"]; + ctrlDelete (_display displayCtrl IDC_DROP_CATCHER); + }, _this] call CBA_fnc_execNextFrame; + + private _primarySlot = _display displayCtrl IDC_PRIMARY_SLOT; + _primarySlot ctrlSetFade 0; + _primarySlot ctrlCommit 0; + private _primaryBG = _display displayCtrl IDC_PRIMARY_BG; + _primaryBG ctrlSetText ""; + + // Inventory has some delay, timeout in case player didn't actually drop weapon somewhere + [{ + secondaryWeapon ACE_player != QGVAR(weapon) + }, { + [false] call FUNC(onDropWOB); + }, [], 5] call CBA_fnc_waitUntilAndExecute; + }] +]; + +_display setVariable [QGVAR(dragImage), _dragImage]; +_display setVariable [QGVAR(dragWOBEHs), _eventHandlers]; diff --git a/addons/weapononback/functions/fnc_onDrop.sqf b/addons/weapononback/functions/fnc_onDrop.sqf new file mode 100644 index 00000000000..530405b7c7f --- /dev/null +++ b/addons/weapononback/functions/fnc_onDrop.sqf @@ -0,0 +1,84 @@ +#include "script_component.hpp" +/* + * Author: BaerMitUmlaut + * Handles dropping a weapon onto the secondary slot. + * + * Arguments: + * 0: Drop catcher control + * 1: Ignored + * 2: Ignored + * 3: Control that the dragging operation started from + * 4: Information about the dragged item + * + * Return Value: + * None + * + * Public: No + */ +params ["_dropCatcher", "", "", "_originIDC", "_info"]; + +private _weaponsItems = []; + +if (_originIDC == IDC_PRIMARY_SLOT) then { + [] call FUNC(swap); +} else { + _info#0 params ["_displayName", "_index"]; + + private _display = ctrlParent _dropCatcher; + private _listBox = _display displayCtrl _originIDC; + + // First index is 127, followed by 1. + // Of course, the 128th item is then index -1, followed by 128. + // Thanks BI! + if (_index == 127) then { + _index = 0; + }; + if (_index == -1) then { + _index = 127; + }; + + private _weaponIndex = -1; + for "_i" from 0 to _index do { + if (_listBox lbText _i == _displayName) then { + _weaponIndex = _weaponIndex + 1; + }; + }; + + private _container = [_originIDC] call FUNC(getIDCContainer); + if (_container isKindOf "CAManBase") then { + _container = _container getVariable [QGVAR(droppedWeaponHolder), _container]; + }; + + private _allWeaponsItems = weaponsItemsCargo _container; + + // Get all weapons in container that have the name of the weapon that was taken + private _weaponsItemsWithName = _allWeaponsItems select { + getText (configFile >> "CfgWeapons" >> _x#0 >> "displayName") == _displayName + }; + + // Get unique weapon attachment setups because identical ones stack + private _allWeaponsItemsNoMags = _weaponsItemsWithName apply {_x select {!(_x isEqualType [])}}; + private _allWeaponsItemsUnique = _allWeaponsItemsNoMags arrayIntersect _allWeaponsItemsNoMags; + private _weaponAttachmentSetup = _allWeaponsItemsUnique#_weaponIndex; + private _weaponsItemsIndex = _allWeaponsItems findIf { + (_x select {!(_x isEqualType [])}) isEqualTo _weaponAttachmentSetup + }; + + // Choose first occurance of this weapon setup from container + private _weaponsItems = _allWeaponsItems#_weaponsItemsIndex; + _allWeaponsItems deleteAt _weaponsItemsIndex; + + // Add weapon on back to container if there is one + if (secondaryWeapon ACE_player == QGVAR(weapon)) then { + private _weaponsItems = [ACE_player] call FUNC(get); + _allWeaponsItems pushBack _weaponsItems; + }; + + // Remove setup from container + clearWeaponCargoGlobal _container; + { + _container addWeaponWithAttachmentsCargoGlobal [_x, 1]; + } forEach _allWeaponsItems; + + [ACE_player, _weaponsItems] call FUNC(add); +}; diff --git a/addons/weapononback/functions/fnc_onDropWOB.sqf b/addons/weapononback/functions/fnc_onDropWOB.sqf new file mode 100644 index 00000000000..f426b5a93be --- /dev/null +++ b/addons/weapononback/functions/fnc_onDropWOB.sqf @@ -0,0 +1,86 @@ +#include "script_component.hpp" +/* + * Author: BaerMitUmlaut + * Handles dropping the weapon on the back onto a container. + * + * Arguments: + * 0: Dropped onto primary weapon slot + * + * Return Value: + * None + * + * Public: No + */ +params [["_droppedOntoPrimary", false]]; + +if (_droppedOntoPrimary) then { + [] call FUNC(swap); +} else { + // Find container the weapon was dropped into + private _possibleContainers = if (QGVAR(weapon) in weapons ACE_player) then { + [ + // Ordered by likelyhood + backpackContainer ACE_player, + vestContainer ACE_player, + uniformContainer ACE_player + ] + } else { + if (GVAR(openedContainers)#0 isKindOf "CAManBase") then { + // Weapons cannot be dropped onto dead bodies + [ + // If the dropped primary of the unit wasn't taken yet, that holder is used + GVAR(openedContainers)#0 getVariable [QGVAR(droppedWeaponHolder), objNull], + // Otherwise a new one is created dynamically + nearestObject [ACE_player, "GroundWeaponHolder"] + ] + } else { + GVAR(openedContainers) + } + }; + + private _container = { + if (QGVAR(weapon) in weaponCargo _x) exitWith { _x }; + } forEach _possibleContainers; + + // Remove and replace fake weapon with actual weapon + private _weaponsItems = [ACE_player] call FUNC(get); + private _allWeaponsItems = weaponsItemsCargo _container; + _allWeaponsItems deleteAt (_allWeaponsItems findIf { _x#0 == QGVAR(weapon) }); + + if (_container canAdd _weaponsItems#0) then { + _allWeaponsItems pushBack _weaponsItems; + [ACE_player] call FUNC(remove); + } else { + // If container cannot fit weapon on back, readd fake weapon to player, show hint + ACE_player addWeapon QGVAR(weapon); + + private _containerCfg = switch (_container) do { + case (backpackContainer ACE_player): { + configFile >> "CfgWeapons" >> backpack ACE_player + }; + case (vestContainer ACE_player): { + configFile >> "CfgWeapons" >> vest ACE_player + }; + case (uniformContainer ACE_player): { + configFile >> "CfgWeapons" >> uniform ACE_player + }; + default { + configFile >> "CfgVehicles" >> typeOf _container + }; + }; + + [ + format [ + LLSTRING(CouldNotFit), + getText (configFile >> "CfgWeapons" >> _weaponsItems#0 >> "displayName"), + getText (_containerCfg >> "displayName") + ], + true, 5 + ] call EFUNC(common,displayText); + }; + + clearWeaponCargoGlobal _container; + { + _container addWeaponWithAttachmentsCargoGlobal [_x, 1]; + } forEach _allWeaponsItems; +}; diff --git a/addons/weapononback/functions/fnc_onInventoryOpened.sqf b/addons/weapononback/functions/fnc_onInventoryOpened.sqf new file mode 100644 index 00000000000..6e29083d706 --- /dev/null +++ b/addons/weapononback/functions/fnc_onInventoryOpened.sqf @@ -0,0 +1,48 @@ +#include "script_component.hpp" +/* + * Author: BaerMitUmlaut + * Adds event handlers when opening the inventory display. + * + * Arguments: + * 0: Inventory display + * + * Return Value: + * None + * + * Public: No + */ +params ["_display"]; + +[_display] call FUNC(updateInventory); + +// Add event handlers for dragging something onto the secondary slot +private _primarySlot = _display displayCtrl IDC_PRIMARY_SLOT; +_primarySlot ctrlAddEventHandler ["MouseButtonDown", FUNC(onDrag)]; + +{ + private _ctrl = _display displayCtrl _x; + _ctrl ctrlAddEventHandler ["LBDrag", FUNC(onDrag)]; +} forEach [ + IDC_GROUND_CONTAINER, + IDC_SOLDIER_CONTAINER, + IDC_UNIFORM_CONTAINER, + IDC_VEST_CONTAINER, + IDC_BACKPACK_CONTAINER +]; + +// Add event handler for dragging the weapon on back to a container +private _secondarySlot = _display displayCtrl IDC_SECONDARY_SLOT; +_secondarySlot ctrlAddEventHandler ["MouseButtonDown", FUNC(onDragWOB)]; + +// Add event handler for right click dropping a weapon into a container +_secondarySlot ctrlAddEventHandler ["MouseButtonClick", { + params ["", "_mouseButton"]; + + if (_mouseButton == 1 && {ACE_player in GVAR(units)}) then { + [{ + secondaryWeapon ACE_player != QGVAR(weapon) + }, { + [] call FUNC(onDropWOB); + }, [], 5] call CBA_fnc_waitUntilAndExecute; + }; +}]; diff --git a/addons/weapononback/functions/fnc_onWHSInit.sqf b/addons/weapononback/functions/fnc_onWHSInit.sqf new file mode 100644 index 00000000000..69b2a2b14a6 --- /dev/null +++ b/addons/weapononback/functions/fnc_onWHSInit.sqf @@ -0,0 +1,27 @@ +#include "script_component.hpp" +/* + * Author: BaerMitUmlaut + * Associates unit and weapon holder on death and moves weapon on back into + * the dropped weapon holder. + * + * Arguments: + * 0: Weapon holder + * + * Return Value: + * None + * + * Public: No + */ +params ["_weaponHolder"]; + +private _unit = nearestObject [_weaponHolder, "CAManBase"]; +if (isNull _unit || {alive _unit}) exitWith {}; + +_unit setVariable [QGVAR(droppedWeaponHolder), _weaponHolder]; + +if (local _unit && {_unit in GVAR(units)}) then { + private _weaponsItems = [_unit] call FUNC(get); + _weaponHolder addWeaponWithAttachmentsCargoGlobal [_weaponsItems, 1]; + + [_unit] call FUNC(remove); +}; diff --git a/addons/weapononback/functions/fnc_onWeaponChange.sqf b/addons/weapononback/functions/fnc_onWeaponChange.sqf new file mode 100644 index 00000000000..1abf7f5a6e6 --- /dev/null +++ b/addons/weapononback/functions/fnc_onWeaponChange.sqf @@ -0,0 +1,19 @@ +#include "script_component.hpp" +/* + * Author: BaerMitUmlaut + * Handles switching to the weapon on the back. + * + * Arguments: + * 0: Player + * 1: Weapon that was swapped to + * + * Return Value: + * None + * + * Public: No + */ +params ["", "_weapon"]; + +if (_weapon != QGVAR(weapon)) exitWith {}; + +[FUNC(swap), [], 2] call CBA_fnc_waitAndExecute; diff --git a/addons/weapononback/functions/fnc_remove.sqf b/addons/weapononback/functions/fnc_remove.sqf new file mode 100644 index 00000000000..30589f53219 --- /dev/null +++ b/addons/weapononback/functions/fnc_remove.sqf @@ -0,0 +1,40 @@ +#include "script_component.hpp" +/* + * Author: BaerMitUmlaut + * Removes the weapon on the back of a unit (global effect). + * + * Arguments: + * 0: Unit + * + * Return Value: + * None + * + * Example: + * [player] call ace_weapononback_fnc_remove + * + * Public: Yes + */ +params ["_unit", ["_calledFromEvent", false]]; + +if !(_unit in GVAR(units)) exitWith {}; + +if (!_calledFromEvent) exitWith { + [EVENT_ID(_unit)] call CBA_fnc_removeGlobalEventJIP; + [QGVAR(remove), [_unit]] call CBA_fnc_globalEvent; +}; + +private _weaponHolder = _unit getVariable [QGVAR(weaponHolder), objNull]; +deleteVehicle _weaponHolder; + +// There is a delay when deleting the weapon holder, so ensure the variable is null +_unit setVariable [QGVAR(weaponHolder), objNull]; +GVAR(units) deleteAt (GVAR(units) find _unit); + +if (local _unit) then { + private _oldMass = _unit getVariable [QGVAR(weaponMass), 0]; + [_unit, _unit, -_oldMass] call EFUNC(movement,addLoadToUnitContainer); + _unit setVariable [QGVAR(weaponMass), 0, true]; + + _unit removeWeapon QGVAR(weapon); + [] call FUNC(updateInventory); +}; diff --git a/addons/weapononback/functions/fnc_renderPFH.sqf b/addons/weapononback/functions/fnc_renderPFH.sqf new file mode 100644 index 00000000000..c27c722e1fe --- /dev/null +++ b/addons/weapononback/functions/fnc_renderPFH.sqf @@ -0,0 +1,51 @@ +#include "script_component.hpp" +/* + * Author: BaerMitUmlaut + * Renders weapons on the backs of players. + * + * Arguments: + * None + * + * Return Value: + * None + * + * Public: No + */ + +private _units = GVAR(units); + +if (_units isEqualTo []) exitWith {}; + +// Sort units by distance if there is a render limit greater than 0 +if (GVAR(renderLimit) > 0) then { + private _unitsWithDistance = GVAR(units) apply {[ACE_player distance _x, _x]}; + _unitsWithDistance sort true; + _units = _unitsWithDistance apply {_x#1}; +}; + +private _renderedUnits = 0; +{ + private _weaponHolder = _x getVariable [QGVAR(weaponHolder), objNull]; + + if (_renderedUnits != GVAR(renderLimit) && {isNull objectParent _x}) then { + _weaponHolder hideObject false; + + // Don't update orientation if not on screen, always update for player + if (_x == player || {!(worldToScreen ASLToAGL getPosWorld _weaponHolder isEqualTo [])}) then { + private _lShoulder = _x selectionPosition "leftshoulder"; + private _rShoulder = _x selectionPosition "rightshoulder"; + private _lUpLeg = _x selectionPosition "leftupleg"; + + private _ab = _lShoulder vectorFromTo _rShoulder; + private _bc = _lShoulder vectorFromTo _lUpLeg; + private _vectorDir = _ab vectorCrossProduct _bc; + private _vectorUp = _ab; + + _weaponHolder setVectorDirAndUp [_vectorUp, _vectorDir]; + + _renderedUnits = _renderedUnits + 1; + }; + } else { + _weaponHolder hideObject true; + }; +} forEach _units; diff --git a/addons/weapononback/functions/fnc_swap.sqf b/addons/weapononback/functions/fnc_swap.sqf new file mode 100644 index 00000000000..454cd775dee --- /dev/null +++ b/addons/weapononback/functions/fnc_swap.sqf @@ -0,0 +1,85 @@ +#include "script_component.hpp" +/* + * Author: BaerMitUmlaut + * Swaps primary and weapon on back. Can handle no primary or no weapon on back. + * + * Arguments: + * None + * + * Return Value: + * None + * + * Public: No + */ + +private _oldPrimary = (getUnitLoadout ACE_player)#0; +private _newPrimary = [ACE_player] call FUNC(get); + +// Save scope adjustment +private _adjustments = ACE_player getVariable [QEGVAR(scopes,adjustment), [[0, 0, 0], [0, 0, 0], [0, 0, 0]]]; +private _oldAdjustment = _adjustments#0; +private _newAdjustement = ACE_player getVariable [QGVAR(scopeAdjustment), [0, 0, 0]]; +EGVAR(scopes,guns) set [0, ""]; + +ACE_player removeWeapon (_oldPrimary param [0, ""]); + +if !(_newPrimary isEqualTo []) then { + _newPrimary params ["_weapon"]; + + // addWeapon is going to eat a mag for each muzzle before we can fill the weapon with addWeaponItem + // Because of that, we need to save where that mag was taken from and readd it + private _containerMags = [ + uniformContainer ACE_player, + vestContainer ACE_player, + backpackContainer ACE_player + ] apply {magazinesAmmo _x}; + + ACE_player addWeapon _weapon; + + // Readd lost magazines + { + private _container = _x; + private _before = _containerMags#_forEachIndex; + private _after = magazinesAmmo _container; + + { + _before deleteAt (_before find _x); + } forEach _after; + + { + _container addMagazineAmmoCargo [_x#0, 1, _x#1]; + } forEach _before; + } forEach [ + uniformContainer ACE_player, + vestContainer ACE_player, + backpackContainer ACE_player + ]; + + removeAllPrimaryWeaponItems ACE_player; + { + ACE_player addWeaponItem [_weapon, _x, true]; + } forEach (_newPrimary select [1, 6]); + + ACE_player selectWeapon _weapon; + + // Restore scope adjustment + [{ + EGVAR(scopes,guns)#0 != "" + }, { + params ["_oldAdjustment", "_newAdjustement"]; + + if !(_newAdjustement isEqualTo [0, 0, 0]) then { + private _scopeAdjustmentParams = [ACE_player]; + _scopeAdjustmentParams append _newAdjustement; + _scopeAdjustmentParams call EFUNC(scopes,applyScopeAdjustment); + }; + + ACE_player setVariable [QGVAR(scopeAdjustment), _oldAdjustment]; + }, [_oldAdjustment, _newAdjustement]] call CBA_fnc_waitUntilAndExecute; +}; + +if (_oldPrimary isEqualTo []) then { + [ACE_player] call FUNC(remove); +} else { + [ACE_player, _oldPrimary] call FUNC(add); +}; diff --git a/addons/weapononback/functions/fnc_updateInventory.sqf b/addons/weapononback/functions/fnc_updateInventory.sqf new file mode 100644 index 00000000000..ada7746a836 --- /dev/null +++ b/addons/weapononback/functions/fnc_updateInventory.sqf @@ -0,0 +1,39 @@ +#include "script_component.hpp" +/* + * Author: BaerMitUmlaut + * Updates the inventory display by hiding / showing the weapon on the back. + * + * Arguments: + * 0: Inventory display (optional, for UI EH) + * + * Return Value: + * None + * + * Public: No + */ +params [["_display", findDisplay IDD_INVENTORY]]; +if (isNull _display) exitWith {}; + +private _weaponHolder = ACE_player getVariable [QGVAR(weaponHolder), objNull]; +private _hasWeaponOnBack = !isNull _weaponHolder; + +// Hide attachment slots if player has weapon on their back +{ + private _ctrl = _display displayCtrl _x; + _ctrl ctrlSetFade ([0, 1] select _hasWeaponOnBack); + _ctrl ctrlCommit 0; +} forEach IDCS_SECONDARY_ATTACHMENTS; + +// Show/hide image of weapon on back +private _imgContainer = _display displayCtrl IDC_WEAPON_IMAGE; +private _secondarySlot = _display displayCtrl IDC_SECONDARY_SLOT; + +_imgContainer ctrlSetPosition ctrlPosition _secondarySlot; +_imgContainer ctrlSetBackgroundColor [1, 1, 1, 0]; +if (_hasWeaponOnBack) then { + private _weapon = (weaponCargo _weaponHolder)#0; + _imgContainer ctrlSetText getText (configFile >> "CfgWeapons" >> _weapon >> "picture"); +} else { + _imgContainer ctrlSetText ""; +}; +_imgContainer ctrlCommit 0; diff --git a/addons/weapononback/functions/script_component.hpp b/addons/weapononback/functions/script_component.hpp new file mode 100644 index 00000000000..07c8cab8367 --- /dev/null +++ b/addons/weapononback/functions/script_component.hpp @@ -0,0 +1 @@ +#include "\z\ace\addons\weapononback\script_component.hpp" diff --git a/addons/weapononback/initSettings.sqf b/addons/weapononback/initSettings.sqf new file mode 100644 index 00000000000..1b2409c0780 --- /dev/null +++ b/addons/weapononback/initSettings.sqf @@ -0,0 +1,13 @@ +[ + QGVAR(enabled), "CHECKBOX", + LELSTRING(common,Enabled), + LELSTRING(common,ACEKeybindCategoryWeapons), + true, false +] call CBA_settings_fnc_init; + +[ + QGVAR(renderLimit), "SLIDER", + [LSTRING(RenderLimit), LSTRING(RenderLimitDescription)], + LELSTRING(common,ACEKeybindCategoryWeapons), + [-1, 100, -1, -1], false +] call CBA_settings_fnc_init; diff --git a/addons/weapononback/script_component.hpp b/addons/weapononback/script_component.hpp new file mode 100644 index 00000000000..886a1ee66ef --- /dev/null +++ b/addons/weapononback/script_component.hpp @@ -0,0 +1,46 @@ +#define COMPONENT weapononback +#define COMPONENT_BEAUTIFIED Weapon on Back +#include "\z\ace\addons\main\script_mod.hpp" + +// #define DEBUG_MODE_FULL +// #define DISABLE_COMPILE_CACHE +// #define ENABLE_PERFORMANCE_COUNTERS + +#ifdef DEBUG_ENABLED_WEAPONONBACK + #define DEBUG_MODE_FULL +#endif + +#ifdef DEBUG_SETTINGS_WEAPONONBACK + #define DEBUG_SETTINGS DEBUG_SETTINGS_WEAPONONBACK +#endif + +#include "\z\ace\addons\main\script_macros.hpp" + +// Inventory IDD +#define IDD_INVENTORY 602 + +// Relevant slot IDCs +#define IDC_PRIMARY_SLOT 610 +#define IDC_SECONDARY_SLOT 611 + +// Custom controls +#define IDC_WEAPON_IMAGE 135001 +#define IDC_DROP_CATCHER 135002 +#define IDC_DROP_CATCHER_BG 135003 + +// Container IDCs +#define IDC_GROUND_CONTAINER 632 +#define IDC_SOLDIER_CONTAINER 640 +#define IDC_UNIFORM_CONTAINER 633 +#define IDC_VEST_CONTAINER 638 +#define IDC_BACKPACK_CONTAINER 619 + +// Attachement IDCs +#define IDCS_SECONDARY_ATTACHMENTS [1248, 1266, 1249, 1250, 1251, 624, 642, 626, 625, 627] + +// Background IDCs +#define IDC_PRIMARY_BG 1242 +#define IDC_SECONDARY_BG 1247 + +// ID generator for JIP events +#define EVENT_ID(obj) (QGVAR(eventID_) + netId obj) diff --git a/addons/weapononback/stringtable.xml b/addons/weapononback/stringtable.xml new file mode 100644 index 00000000000..f528290adbd --- /dev/null +++ b/addons/weapononback/stringtable.xml @@ -0,0 +1,21 @@ + + + + + Weapon on back + Waffe auf dem Rücken + + + Render limit + Renderlimit + + + Limit the amount of weapons on backs to be rendered (-1 for no limit). + Limitiert die Anzahl der Waffen auf dem Rücken die gerendert werden (-1 bedeutet kein Limit). + + + Could not fit %1 into %2. + %1 passt nicht in %2. + + +