Arsenal - Improved sorting, insignia detection, identity info verification and minor cleanup (#9795)

* Changed sorting + minor cleanup

* Update addons/arsenal/functions/fnc_fillLeftPanel.sqf

Co-authored-by: Grim <69561145+LinkIsGrim@users.noreply.github.com>

* check and log missing extended info

* fix extended loadout logging

* Made voice stuff config case

* use lowercase voice everywhere

* check loadout voice against configCase

* Update fnc_verifyLoadout.sqf

* Update addons/arsenal/functions/fnc_verifyLoadout.sqf

Co-authored-by: Grim <69561145+LinkIsGrim@users.noreply.github.com>

* Update addons/arsenal/functions/fnc_verifyLoadout.sqf

Co-authored-by: Grim <69561145+LinkIsGrim@users.noreply.github.com>

* Update addons/arsenal/functions/fnc_verifyLoadout.sqf

---------

Co-authored-by: LinkIsGrim <salluci.lovi@gmail.com>
Co-authored-by: Grim <69561145+LinkIsGrim@users.noreply.github.com>
This commit is contained in:
johnb432 2024-02-13 20:22:27 +01:00 committed by GitHub
parent a0f3933bf0
commit fd5e56ffbc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 102 additions and 73 deletions

View File

@ -108,24 +108,16 @@ GVAR(lastSortDirectionRight) = DESCENDING;
private _face = _extendedInfo getOrDefault [QGVAR(face), ""]; private _face = _extendedInfo getOrDefault [QGVAR(face), ""];
if (_face != "") then { if (_face != "") then {
if (isMultiplayer) then { private _id = [QGVAR(broadcastFace), [_unit, _face], QGVAR(centerFace_) + hashValue _unit] call CBA_fnc_globalEventJIP;
private _id = [QGVAR(broadcastFace), [_unit, _face], QGVAR(centerFace_) + netId _unit] call CBA_fnc_globalEventJIP; [_id, _unit] call CBA_fnc_removeGlobalEventJIP;
[_id, _unit] call CBA_fnc_removeGlobalEventJIP;
} else {
_unit setFace _face;
};
}; };
// Set voice // Set voice
private _voice = _extendedInfo getOrDefault [QGVAR(voice), ""]; private _voice = _extendedInfo getOrDefault [QGVAR(voice), ""];
if (_voice != "") then { if (_voice != "") then {
if (isMultiplayer) then { private _id = [QGVAR(broadcastVoice), [_unit, _voice], QGVAR(centerVoice_) + hashValue _unit] call CBA_fnc_globalEventJIP;
private _id = [QGVAR(broadcastVoice), [_unit, _voice], QGVAR(centerVoice_) + netId _unit] call CBA_fnc_globalEventJIP; [_id, _unit] call CBA_fnc_removeGlobalEventJIP;
[_id, _unit] call CBA_fnc_removeGlobalEventJIP;
} else {
_unit setSpeaker _voice;
};
}; };
// Set insignia // Set insignia
@ -147,7 +139,7 @@ GVAR(lastSortDirectionRight) = DESCENDING;
// Set voice if enabled // Set voice if enabled
if (GVAR(loadoutsSaveVoice)) then { if (GVAR(loadoutsSaveVoice)) then {
_extendedInfo set [QGVAR(voice), speaker _unit]; _extendedInfo set [QGVAR(voice), (speaker _unit) call EFUNC(common,getConfigName)];
}; };
// Set insignia if enabled // Set insignia if enabled

View File

@ -91,5 +91,4 @@ private _insigniaCondition = toString {
GVAR(insigniaCache) set [_x, 2]; GVAR(insigniaCache) set [_x, 2];
} forEach (_insigniaCondition configClasses (missionConfigFile >> "CfgUnitInsignia")); } forEach (_insigniaCondition configClasses (missionConfigFile >> "CfgUnitInsignia"));
ADDON = true; ADDON = true;

View File

@ -6,7 +6,7 @@
* *
* Arguments: * Arguments:
* 0: Config category, must be "CfgWeapons", "CfgVehicles", "CfgMagazines", "CfgVoice" or "CfgUnitInsignia" <STRING> * 0: Config category, must be "CfgWeapons", "CfgVehicles", "CfgMagazines", "CfgVoice" or "CfgUnitInsignia" <STRING>
* 1: Classname <STRING> * 1: Classname (must be in config case) <STRING>
* 2: Panel control <CONTROL> * 2: Panel control <CONTROL>
* 3: Name of the picture entry in that Cfg class <STRING> (default: "picture") * 3: Name of the picture entry in that Cfg class <STRING> (default: "picture")
* 4: Config root <NUMBER> (default: 0 -> configFile) * 4: Config root <NUMBER> (default: 0 -> configFile)
@ -42,7 +42,7 @@ if (_skip) then {
if (_skip) exitWith {}; if (_skip) exitWith {};
// Sanitise key, as it's public; If not in cache, find info and cache it for later use // If not in cache, find info and cache it for later use
((uiNamespace getVariable QGVAR(addListBoxItemCache)) getOrDefaultCall [_configCategory + _className + str _configRoot, { ((uiNamespace getVariable QGVAR(addListBoxItemCache)) getOrDefaultCall [_configCategory + _className + str _configRoot, {
// Get classname (config case), display name, picture and DLC // Get classname (config case), display name, picture and DLC
private _configPath = ([configFile, campaignConfigFile, missionConfigFile] select _configRoot) >> _configCategory >> _className; private _configPath = ([configFile, campaignConfigFile, missionConfigFile] select _configRoot) >> _configCategory >> _className;

View File

@ -71,15 +71,16 @@ if (GVAR(currentLoadoutsTab) != IDC_buttonSharedLoadouts) then {
_loadoutCachedInfo = [_loadoutData] call FUNC(verifyLoadout); _loadoutCachedInfo = [_loadoutData] call FUNC(verifyLoadout);
_contentPanelCtrl setVariable [_loadoutNameAndTab, _loadoutCachedInfo]; _contentPanelCtrl setVariable [_loadoutNameAndTab, _loadoutCachedInfo];
_loadoutCachedInfo params ["", "_nullItemsList", "_unavailableItemsList"]; _loadoutCachedInfo params ["", "_nullItemsList", "_unavailableItemsList", "_missingExtendedInfo"];
// Log missing / nil items to RPT (only once per arsenal session) // Log missing / nil items to RPT (only once per arsenal session)
if (GVAR(EnableRPTLog) && {(_nullItemsList isNotEqualTo []) || {_unavailableItemsList isNotEqualTo []}}) then { if (GVAR(EnableRPTLog) && {(_nullItemsList isNotEqualTo []) || {_unavailableItemsList isNotEqualTo [] || {_missingExtendedInfo isNotEqualTo []}}}) then {
private _printComponent = "ACE_Arsenal - Loadout:"; private _printComponent = "ACE_Arsenal - Loadout:";
private _printNullItemsList = ["Missing items:", str _nullItemsList] joinString " "; private _printNullItemsList = ["Missing items:", str _nullItemsList] joinString " ";
private _printUnavailableItemsList = ["Unavailable items:", str _unavailableItemsList] joinString " "; private _printUnavailableItemsList = ["Unavailable items:", str _unavailableItemsList] joinString " ";
private _printMissingExtendedInfo = ["Missing extended loadout:", str _missingExtendedInfo] joinString " ";
diag_log text (format ["%1%5 %2%5 %3%5 %4", _printComponent, "Name: " + _loadoutName, _printNullItemsList, _printUnavailableItemsList, endl]); diag_log text (format ["%1%6 %2%6 %3%6 %4%6 %5", _printComponent, "Name: " + _loadoutName, _printNullItemsList, _printUnavailableItemsList, _printMissingExtendedInfo, endl]);
}; };
}; };

View File

@ -68,7 +68,7 @@ if (isNil QGVAR(virtualItems)) then {
GVAR(virtualItemsFlatAll) = +GVAR(virtualItemsFlat); GVAR(virtualItemsFlatAll) = +GVAR(virtualItemsFlat);
GVAR(currentFace) = face GVAR(center); GVAR(currentFace) = face GVAR(center);
GVAR(currentVoice) = speaker GVAR(center); GVAR(currentVoice) = (speaker GVAR(center)) call EFUNC(common,getConfigName);
GVAR(currentInsignia) = GVAR(center) call BIS_fnc_getUnitInsignia; GVAR(currentInsignia) = GVAR(center) call BIS_fnc_getUnitInsignia;
GVAR(currentAction) = "Stand"; GVAR(currentAction) = "Stand";

View File

@ -683,14 +683,19 @@ switch (GVAR(currentLeftPanel)) do {
TOGGLE_RIGHT_PANEL_HIDE TOGGLE_RIGHT_PANEL_HIDE
private _unitInsigniaConfig = configFile >> "CfgUnitInsignia" >> _item; // Check for correct config: First mission, then campaign and finally regular config
private _itemCfg = missionConfigFile >> "CfgUnitInsignia" >> _item;
if (isNull _itemCfg) then {
_itemCfg = campaignConfigFile >> "CfgUnitInsignia" >> _item;
};
if (isNull _itemCfg) then {
_itemCfg = configFile >> "CfgUnitInsignia" >> _item;
};
// Display new items's info on the bottom right // Display new items's info on the bottom right
if (isNull _unitInsigniaConfig) then { [_display, _control, _curSel, _itemCfg] call FUNC(itemInfo);
[_display, _control, _curSel, missionConfigFile >> "CfgUnitInsignia" >> _item] call FUNC(itemInfo);
} else {
[_display, _control, _curSel, _unitInsigniaConfig] call FUNC(itemInfo);
};
}; };
}; };

View File

@ -15,6 +15,10 @@
params ["_control"]; params ["_control"];
// https://community.bistudio.com/wiki/toString, see comment
// However, using 55295 did not work as expected, 55291 was found by trial and error
#define HIGHEST_VALUE_CHAR 55291
// When filling the sorting panel, FUNC(sortPanel) is called twice, so ignore first call // When filling the sorting panel, FUNC(sortPanel) is called twice, so ignore first call
if (GVAR(ignoreFirstSortPanelCall)) exitWith { if (GVAR(ignoreFirstSortPanelCall)) exitWith {
GVAR(ignoreFirstSortPanelCall) = false; GVAR(ignoreFirstSortPanelCall) = false;
@ -29,6 +33,7 @@ private _sortDirectionCtrl = _display displayCtrl ([IDC_sortLeftTabDirection, ID
private _cfgMagazines = configFile >> "CfgMagazines"; private _cfgMagazines = configFile >> "CfgMagazines";
private _cfgFaces = configFile >> "CfgFaces"; private _cfgFaces = configFile >> "CfgFaces";
private _cfgUnitInsignia = configFile >> "CfgUnitInsignia"; private _cfgUnitInsignia = configFile >> "CfgUnitInsignia";
private _cfgUnitInsigniaCampaign = campaignConfigFile >> "CfgUnitInsignia";
private _cfgUnitInsigniaMission = missionConfigFile >> "CfgUnitInsignia"; private _cfgUnitInsigniaMission = missionConfigFile >> "CfgUnitInsignia";
if (_rightSort) then { if (_rightSort) then {
@ -126,7 +131,6 @@ private _selected = if (_right) then {
_panel lbData _curSel _panel lbData _curSel
}; };
private _originalNames = createHashMap;
private _item = ""; private _item = "";
private _quantity = ""; private _quantity = "";
private _itemCfg = configNull; private _itemCfg = configNull;
@ -136,12 +140,8 @@ private _fillerChar = toString [1];
private _magazineMiscItems = uiNamespace getVariable QGVAR(magazineMiscItems); private _magazineMiscItems = uiNamespace getVariable QGVAR(magazineMiscItems);
private _sortCache = uiNamespace getVariable QGVAR(sortCache); private _sortCache = uiNamespace getVariable QGVAR(sortCache);
private _faceCache = uiNamespace getVariable QGVAR(faceCache);
private _faceCache = if (_cfgClass == _cfgFaces) then { private _insigniaCache = uiNamespace getVariable QGVAR(insigniaCache);
uiNamespace getVariable [QGVAR(faceCache), createHashMap]
} else {
createHashMap
};
private _countColumns = if (_right) then { private _countColumns = if (_right) then {
count lnbGetColumnsPosition _panel count lnbGetColumnsPosition _panel
@ -150,9 +150,9 @@ private _countColumns = if (_right) then {
}; };
private _for = if (_right) then { private _for = if (_right) then {
for '_i' from 0 to (lnbSize _panel select 0) - 1 for "_i" from 0 to (lnbSize _panel select 0) - 1
} else { } else {
for '_i' from 0 to (lbSize _panel) - 1 for "_i" from 0 to (lbSize _panel) - 1
}; };
_for do { _for do {
@ -163,6 +163,14 @@ _for do {
_panel lbData _i _panel lbData _i
}; };
// Check if entry is "Empty"
if (!_right && {(_panel lbValue _i) == -1}) then {
// Set to lowest/highest lexicographical value, so that "Empty" is always at the top
_panel lbSetTextRight [_i, ["", toString [HIGHEST_VALUE_CHAR, HIGHEST_VALUE_CHAR, HIGHEST_VALUE_CHAR, HIGHEST_VALUE_CHAR, HIGHEST_VALUE_CHAR]] select (_sortDirection == ASCENDING)];
continue;
};
// Get item's count // Get item's count
_quantity = if (_right) then { _quantity = if (_right) then {
parseNumber (_panel lnbText [_i, 2]) parseNumber (_panel lnbText [_i, 2])
@ -179,18 +187,22 @@ _for do {
_itemCfg = if !(_cfgClass in [_cfgFaces, _cfgUnitInsignia]) then { _itemCfg = if !(_cfgClass in [_cfgFaces, _cfgUnitInsignia]) then {
_cfgClass >> _item _cfgClass >> _item
} else { } else {
// If insignia, check both config and mission file // If insignia, check for correct config: First mission, then campaign and finally regular config
if (_cfgClass == _cfgUnitInsignia) then { if (_cfgClass == _cfgUnitInsignia) then {
_itemCfg = _cfgClass >> _item; _itemCfg = _cfgUnitInsigniaMission >> _item;
if (isNull _itemCfg) then { if (isNull _itemCfg) then {
_itemCfg = _cfgUnitInsigniaMission >> _item; _itemCfg = _cfgUnitInsigniaCampaign >> _item;
};
if (isNull _itemCfg) then {
_itemCfg = _cfgUnitInsignia >> _item;
}; };
_itemCfg _itemCfg
} else { } else {
// If face, check correct category // If face, check correct category
_cfgClass >> (_faceCache get _item) param [2, "Man_A3"] >> _item _cfgClass >> (_faceCache getOrDefault [_item, []]) param [2, "Man_A3"] >> _item
}; };
}; };
@ -216,37 +228,29 @@ _for do {
_value _value
}, true]; }, true];
// Save the current row's item's name in a cache and set text to it's sorting value // Set the right text temporarily, so it can be used for sorting
if (_right) then { if (_right) then {
_name = _panel lnbText [_i, 1];
_originalNames set [_item, _name];
// Use value, display name and classname to sort, which means a fixed alphabetical order is guaranteed // Use value, display name and classname to sort, which means a fixed alphabetical order is guaranteed
// Filler char has lowest lexicographical order possible // Filler char has lowest lexicographical value possible
_panel lnbSetText [[_i, 1], format ["%1%2%4%3", _value, _name, _item, _fillerChar]]; _panel lnbSetTextRight [[_i, 1], format ["%1%2%4%3", _value, _panel lnbText [_i, 1], _item, _fillerChar]];
} else { } else {
if (_item != "") then { if (_item != "") then {
_name = _panel lbText _i;
_originalNames set [_item, _name];
// Use value, display name and classname to sort, which means a fixed alphabetical order is guaranteed // Use value, display name and classname to sort, which means a fixed alphabetical order is guaranteed
// Filler char has lowest lexicographical order possible // Filler char has lowest lexicographical value possible
_panel lbSetText [_i, format ["%1%2%4%3", _value, _name, _item, _fillerChar]]; _panel lbSetTextRight [_i, format ["%1%2%4%3", _value, _panel lbText _i, _item, _fillerChar]];
}; };
}; };
}; };
// Sort alphabetically, find the previously selected item, select it again and reset text to original text // Sort alphabetically, find the previously selected item and select it again
if (_right) then { if (_right) then {
_panel lnbSort [1, _sortDirection == ASCENDING]; [_panel, 1] lnbSortBy ["TEXT", _sortDirection == ASCENDING, false, true, false];
_for do { _for do {
_item = _panel lnbData [_i, 0]; // Remove sorting text, as it blocks the item name otherwise
_panel lnbSetTextRight [[_i, 1], ""];
_panel lnbSetText [[_i, 1], _originalNames get _item]; if (_curSel != -1 && {(_panel lnbData [_i, 0]) == _selected}) then {
// Set selection after text, otherwise item info box on the right side shows invalid name
if (_curSel != -1 && {_item == _selected}) then {
_panel lnbSetCurSelRow _i; _panel lnbSetCurSelRow _i;
// To avoid unnecessary checks after previsouly selected item was found // To avoid unnecessary checks after previsouly selected item was found
@ -254,17 +258,17 @@ if (_right) then {
}; };
}; };
} else { } else {
lbSort [_panel, ["DESC", "ASC"] select _sortDirection]; _panel lbSortBy ["TEXT", _sortDirection == ASCENDING, false, true, false];
_for do { _for do {
_item = _panel lbData _i; _item = _panel lbData _i;
// Check if valid item (problems can be caused when searching) // Check if valid item (problems can be caused when searching)
if (_item != "") then { if (_item != "") then {
_panel lbSetText [_i, _originalNames get _item]; // Remove sorting text, as it blocks the item name otherwise
_panel lbSetTextRight [_i, ""];
}; };
// Set selection after text, otherwise item info box on the right side shows invalid name
if (_curSel != -1 && {_item == _selected}) then { if (_curSel != -1 && {_item == _selected}) then {
_panel lbSetCurSel _i; _panel lbSetCurSel _i;

View File

@ -27,6 +27,7 @@ private _name = "";
private _itemArray = []; private _itemArray = [];
private _nullItemsList = []; private _nullItemsList = [];
private _unavailableItemsList = []; private _unavailableItemsList = [];
private _missingExtendedInfo = [];
// Search for all items and check their availability // Search for all items and check their availability
private _fnc_filterLoadout = { private _fnc_filterLoadout = {
@ -70,7 +71,22 @@ private _fnc_filterLoadout = {
// Loadout might come from a different modpack, which might have different config naming // Loadout might come from a different modpack, which might have different config naming
_loadout = _loadout call _fnc_filterLoadout; _loadout = _loadout call _fnc_filterLoadout;
// Raise event for 3rd party: mostly for handling extended info {
[QGVAR(loadoutVerified), [_loadout, _extendedInfo]] call CBA_fnc_localEvent; private _class = _extendedInfo getOrDefault [_x, ""];
private _cache = missionNamespace getVariable (_x + "Cache");
[[_loadout, _extendedInfo], _nullItemsList arrayIntersect _nullItemsList, _unavailableItemsList arrayIntersect _unavailableItemsList] // Previously voices were stored in lower case (speaker command returns lower case), so this is to make old loadouts compatible
if (_class != "" && {_x == QGVAR(voice)}) then {
_class = _class call EFUNC(common,getConfigName);
};
if (_class != "" && {!(_class in _cache)}) then {
_missingExtendedInfo pushBack [_x, _class];
_extendedInfo deleteAt _x;
};
} forEach [QGVAR(insignia), QGVAR(face), QGVAR(voice)];
// Raise event for 3rd party: mostly for handling extended info
// Pass all items, including duplicates
[QGVAR(loadoutVerified), [_loadout, _extendedInfo, _nullItemsList, _unavailableItemsList, _missingExtendedInfo]] call CBA_fnc_localEvent;
[[_loadout, _extendedInfo], _nullItemsList arrayIntersect _nullItemsList, _unavailableItemsList arrayIntersect _unavailableItemsList, _missingExtendedInfo]

View File

@ -564,6 +564,7 @@ class GVAR(display) {
colorSelect[] = {1,1,1,1}; colorSelect[] = {1,1,1,1};
colorSelect2[] = {1,1,1,1}; colorSelect2[] = {1,1,1,1};
colorPictureRightSelected[] = {1,1,1,1}; colorPictureRightSelected[] = {1,1,1,1};
colorTextRight[] = {0.5, 0.5, 0.5, 0};
onLBSelChanged = QUOTE(_this call FUNC(onSelChangedLeft)); onLBSelChanged = QUOTE(_this call FUNC(onSelChangedLeft));
onLBDblClick = QUOTE(_this call FUNC(onPanelDblClick)); onLBDblClick = QUOTE(_this call FUNC(onPanelDblClick));
onSetFocus = QUOTE(GVAR(leftTabFocus) = true); onSetFocus = QUOTE(GVAR(leftTabFocus) = true);
@ -594,6 +595,7 @@ class GVAR(display) {
colorSelect[] = {1,1,1,1}; colorSelect[] = {1,1,1,1};
colorSelect2[] = {1,1,1,1}; colorSelect2[] = {1,1,1,1};
colorPictureRightSelected[] = {1,1,1,1}; colorPictureRightSelected[] = {1,1,1,1};
colorTextRight[] = {0.5, 0.5, 0.5, 0};
columns[] = {0.07, 0.15, 0.75}; columns[] = {0.07, 0.15, 0.75};
idcLeft = IDC_arrowMinus; idcLeft = IDC_arrowMinus;
idcRIght = IDC_arrowPlus; idcRIght = IDC_arrowPlus;

View File

@ -1 +0,0 @@
#include "..\script_component.hpp"

View File

@ -17,7 +17,7 @@
params ["_className"]; params ["_className"];
(uiNamespace getVariable QGVAR(configNames)) getOrDefaultCall [toLower _className, { (uiNamespace getVariable QGVAR(configNames)) getOrDefaultCall [toLowerANSI _className, {
private _config = configNull; private _config = configNull;
{ {

View File

@ -27,23 +27,34 @@ PREP_RECOMPILE_END;
}] call CBA_fnc_addClassEventHandler; }] call CBA_fnc_addClassEventHandler;
[QEGVAR(arsenal,loadoutVerified), { [QEGVAR(arsenal,loadoutVerified), {
params ["_loadout", "_extendedInfo"]; params ["_loadout", "_extendedInfo", "", "", "_missingExtendedInfo"];
private _gunbagInfo = _extendedInfo getOrDefault [QGVAR(gunbagWeapon), []]; private _gunbagInfo = _extendedInfo getOrDefault [QGVAR(gunbagWeapon), []];
if (_gunbagInfo isEqualTo []) exitWith {}; if (_gunbagInfo isEqualTo []) exitWith {};
private _weapon = (_gunbagInfo select 0) call EFUNC(arsenal,baseWeapon); private _weapon = (_gunbagInfo select 0) call EFUNC(arsenal,baseWeapon);
if !(_weapon in EGVAR(arsenal,virtualItemsFlat)) exitWith { if !(_weapon in EGVAR(arsenal,virtualItemsFlat)) exitWith {
INFO_1("removing [%1] from loadout",_gunbagInfo); _missingExtendedInfo pushBack [QGVAR(gunbagWeapon), _weapon];
_extendedInfo deleteAt QGVAR(gunbagWeapon); _extendedInfo deleteAt QGVAR(gunbagWeapon);
}; };
private _missingItems = [];
private _attachments = _gunbagInfo select 1;
{
if (_x != "" && {!(_x call EFUNC(arsenal,baseWeapon) in EGVAR(arsenal,virtualItemsFlat))}) then {
_missingItems pushBack _x;
_attachments set [_forEachIndex, ""];
};
} forEach _attachments;
private _magazines = _gunbagInfo select 2;
{ {
private _class = _x param [0, ""]; private _class = _x param [0, ""];
private _defaultValue = ["", []] select {_x isEqualType []}; if !(_class != "" && {_class in EGVAR(arsenal,virtualItemsFlat)}) then {
if (_class != "" && {!(_class in EGVAR(arsenal,virtualItemsFlat))}) then { _missingItems pushBack _class;
INFO_1("removing [%1] from loadout",_x); _magazines set [_forEachIndex, ["", 0]];
_gunbagInfo set [_forEachIndex + 1, _defaultValue];
}; };
} forEach (_gunbagInfo select [1]); // weapon was verified above } forEach _magazines;
if (_missingItems isNotEqualTo []) then {
_missingExtendedInfo pushBack [QGVAR(gunbagWeapon), _missingItems];
};
}] call CBA_fnc_addEventHandler; }] call CBA_fnc_addEventHandler;
["CBA_loadoutSet", { ["CBA_loadoutSet", {

View File

@ -511,7 +511,7 @@ All are local.
| ace_arsenal_loadoutsDisplayClosed | None | 3.12.3 | | ace_arsenal_loadoutsDisplayClosed | None | 3.12.3 |
| ace_arsenal_loadoutsTabChanged | loadouts screen display (DISPLAY), tab control (CONTROL) | 3.12.3 | | ace_arsenal_loadoutsTabChanged | loadouts screen display (DISPLAY), tab control (CONTROL) | 3.12.3 |
| ace_arsenal_loadoutsListFilled | loadouts screen display (DISPLAY), tab control (CONTROL) | 3.12.3 | | ace_arsenal_loadoutsListFilled | loadouts screen display (DISPLAY), tab control (CONTROL) | 3.12.3 |
| ace_arsenal_loadoutVerified | loadout data (ARRAY), loadout CBA extended data (HASHMAP) | 3.17.0 | | ace_arsenal_loadoutVerified | loadout data (ARRAY), loadout CBA extended data (HASHMAP), null items (ARRAY), unavailable items (ARRAY), unavailable extended data (ARRAY) | 3.17.0 |
| ace_arsenal_weaponItemChanged | weapon classname (STRING), item classname (STRING), item index (NUMBER, 0-5: muzzle, side, optic, bipod, magazine, underbarrel) | 3.16.0 | | ace_arsenal_weaponItemChanged | weapon classname (STRING), item classname (STRING), item index (NUMBER, 0-5: muzzle, side, optic, bipod, magazine, underbarrel) | 3.16.0 |
## 9. Custom sub item categories ## 9. Custom sub item categories