Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UI and supporting code to set custom loadouts for rebel troops #3392

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions A3A/addons/core/CfgFunctions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class CfgFunctions
class Ammunition {
file = QPATHTOFOLDER(functions\Ammunition);
class ACEpvpReDress {};
class addPrimaryAndMags {};
class allMagazines {};
class ammunitionTransfer {};
class arsenalManage {};
Expand All @@ -92,6 +93,7 @@ class CfgFunctions
class launcherInfo {};
class loot {};
class randomRifle {};
class setRebelLoadouts {};
class transfer {};
class unlockEquipment {};
class vehicleSort {};
Expand Down
6 changes: 3 additions & 3 deletions A3A/addons/core/dialogs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1774,13 +1774,13 @@ class commander_comm {
class 10slots_R2: A3A_core_BattleMenuRedButton
{
idc = -1;
text = "";
text = "Customize loadouts"; //$STR_antistasi_dialogs_commander_comm_customLoadouts;
x = 0.482498 * safezoneW + safezoneX;
y = 0.365981 * safezoneH + safezoneY;
w = 0.175015 * safezoneW;
h = 0.0560125 * safezoneH;
tooltip = "";
action = "";
tooltip = "Customize loadouts for rebel AI troops"; //$STR_antistasi_dialogs_commander_comm_customLoadouts_tooltip;
action = "if (player == theBoss) then {closeDialog 0; createDialog ""A3A_customLoadoutsDialog""} else {[""Custom Loadouts"", ""Only commanders have access to this function""] call A3A_fnc_customHint}";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// TODO: Localize
Or just localize it yourself.

};
class 10slots_L3: A3A_core_BattleMenuRedButton
{
Expand Down
68 changes: 68 additions & 0 deletions A3A/addons/core/functions/Ammunition/fn_addPrimaryAndMags.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
Equip rebel unit with primary weapon or handgun
Adds magazines by mass. Uses default magazine of selected weapon

Parameters:
0. <OBJECT> Rebel unit to equip with primary weapon.
1. <STRING> Weapon classname
2. <STRING> Optic type preference ("OpticsClose", "OpticsMid", "OpticsLong")
3. <NUMBER> Total mass of primary magazines to add to inventory.
4. <NUMBER> Optional: Number of GL mags to add if secondary.

Returns:
Nothing

Environment:
Scheduled, any machine
*/

#include "..\..\script_component.hpp"
FIX_LINE_NUMBERS()

params ["_unit", "_weapon", "_opticType", "_totalMagWeight", ["_glMags", 5]];

call A3A_fnc_fetchRebelGear; // Send current version of rebelGear from server if we're out of date

// Probably shouldn't ever be executed
if !(primaryWeapon _unit isEqualTo "") then {
if (_weapon == primaryWeapon _unit) exitWith {};
private _magazines = getArray (configFile / "CfgWeapons" / (primaryWeapon _unit) / "magazines");
{_unit removeMagazines _x} forEach _magazines; // Broken, doesn't remove mags globally. Pain to fix.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{_unit removeMagazines _x} forEach _magazines; // Broken, doesn't remove mags globally. Pain to fix.
{
_unit removePrimaryWeaponItem _x;
_unit removeMagazineGlobal _x;
} forEach _magazines;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy-paste of an inactive code path (the function is never called on units with weapons at the moment), so I won't be touching it for now. The code's quite wrong for other reasons because it doesn't consider magazine wells. Not sure how I missed removeMagazineGlobal though.

_unit removeWeapon (primaryWeapon _unit);
};

private _categories = _weapon call A3A_fnc_equipmentClassToCategories;

if ("GrenadeLaunchers" in _categories && {"Rifles" in _categories} ) then {
Copy link

@rautamiekka rautamiekka Dec 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ("GrenadeLaunchers" in _categories && {"Rifles" in _categories} ) then {
if (_categories findAny ["GrenadeLaunchers", "Rifles"] isNotEqualTo -1) then {

In my testing, the old seemed to average 20ms, while the new 15. Also the new one is way cleaner.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's wrong though. Should be comparing to -1

Copy link

@rautamiekka rautamiekka Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I only just now realized that mistake, which you beat me to. I keep forgetting this thing doesn't return boolean. Fixed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still wrong. It's an AND, while findAny can only do an OR.

// lookup real underbarrel GL magazine, because not everything is 40mm
private _config = configFile >> "CfgWeapons" >> _weapon;
private _glmuzzle = getArray (_config >> "muzzles") select 1; // guaranteed by category
_glmuzzle = configName (_config >> _glmuzzle); // bad-case fix. compatibleMagazines is case-sensitive as of 2.12
private _glmag = compatibleMagazines [_weapon, _glmuzzle] select 0;
_unit addMagazines [_glmag, 5];
};

private _magazine = compatibleMagazines _weapon select 0;
private _magweight = 5 max getNumber (configFile >> "CfgMagazines" >> _magazine >> "mass");

_unit addWeapon _weapon;
if ("Handguns" in _categories) then {
_unit addHandgunItem _magazine;
} else {
_unit addPrimaryWeaponItem _magazine;
};
_unit addMagazines [_magazine, round (random 0.5 + _totalMagWeight / _magWeight)];


private _compatOptics = A3A_rebelOpticsCache get _weapon;
if (isNil "_compatOptics") then {
private _compatItems = compatibleItems _weapon;

_compatOptics = _compatItems arrayIntersect (A3A_rebelGear get _opticType);
if (_compatOptics isEqualTo [] and _opticType != "OpticsClose") then {
private _fallbackType = ["OpticsClose", "OpticsMid"] select (_opticType == "OpticsLong");
_compatOptics = _compatItems arrayIntersect (A3A_rebelGear get _fallbackType);
};
A3A_rebelOpticsCache set [_weapon, _compatOptics];
};
if (_compatOptics isNotEqualTo []) then { _unit addPrimaryWeaponItem (selectRandom _compatOptics) };
31 changes: 21 additions & 10 deletions A3A/addons/core/functions/Ammunition/fn_fetchRebelGear.sqf
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
/*
Ensures A3A_rebelGear and related caches are valid and updated for equipping rebel AIs
Ensures A3A_rebelGear, A3A_rebelLoadouts and related caches are locally valid and updated for equipping rebel AIs

Parameters:
None, returns nothing

Environment:
Scheduled, executed anywhere
Scheduled, executed locally
*/

#include "..\..\script_component.hpp"
FIX_LINE_NUMBERS()

// Send current version of rebelGear from server if we're out of date
if (!isNil "A3A_rebelGear" and { A3A_rebelGear get "Version" == A3A_rebelGearVersion}) exitWith {};
private _updateGear = isNil "A3A_rebelGear" or { A3A_rebelGear get "Version" != A3A_rebelGearVersion };
private _updateLoadouts = isNil "A3A_rebelLoadouts" or { A3A_rebelLoadouts get "Version" != A3A_rebelLoadoutsVersion };
if !(_updateGear or _updateLoadouts) exitWith {};

Info("Fetching new version of rebelGear data...");
[clientOwner, "A3A_rebelGear"] remoteExecCall ["publicVariableClient", 2];
waitUntil { sleep 1; !isNil "A3A_rebelGear" and { A3A_rebelGear get "Version" == A3A_rebelGearVersion } };
Info("New version of rebelGear data received");
if (_updateGear) then {
// Create/clear local accessory-compatibility caches
A3A_rebelOpticsCache = createHashMap;
A3A_rebelFlashlightsCache = createHashMap;

// Create/clear local accessory-compatibility caches
A3A_rebelOpticsCache = createHashMap;
A3A_rebelFlashlightsCache = createHashMap;
Info("Fetching new version of rebelGear data...");
[clientOwner, "A3A_rebelGear"] remoteExecCall ["publicVariableClient", 2];
};
if (_updateLoadouts) then {
Info("Fetching new version of rebelLoadouts data...");
[clientOwner, "A3A_rebelLoadouts"] remoteExecCall ["publicVariableClient", 2];
};
waitUntil { sleep 0.5;
!isNil "A3A_rebelGear" and { A3A_rebelGear get "Version" == A3A_rebelGearVersion }
and !isNil "A3A_rebelLoadouts" and { A3A_rebelLoadouts get "Version" == A3A_rebelLoadoutsVersion };
};
Info("New version of rebelGear data received");
18 changes: 15 additions & 3 deletions A3A/addons/core/functions/Ammunition/fn_generateRebelGear.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Info("Started updating A3A_rebelGear");

// Base weight mappings, MIN->0, MAX->1
#define ITEM_MIN 10
#define ITEM_MAX 50
#define ITEM_MAX 40

private _fnc_addItemNoUnlocks = {
params ["_array", "_class", "_amount"];
Expand Down Expand Up @@ -108,8 +108,18 @@ _rebelGear set ["RocketLaunchers", _rlaunchers];
_rebelGear set ["MissileLaunchersAT", _mlaunchersAT];
_rebelGear set ["MissileLaunchersAA", _mlaunchersAA];

// Function to phase in armour & helmets gradually
private _fnc_addEmptyEntry = {
params ["_list", "_targWeight"];
private _weight = 0;
{ if (_x isEqualType 0) then { _weight = _weight + _x } } forEach _list;
if (_weight >= _targWeight) exitWith {};
_list pushBack "";
_list pushBack (_targWeight - _weight);
};

// Vest filtering
private _avests = ["", [1.5,0.5] select (minWeaps < 0)]; // blank entry to phase in armour use gradually
private _avests = [];
private _uvests = [];
{
_x params ["_class", "_amount"];
Expand All @@ -118,11 +128,12 @@ private _uvests = [];
[_array, _class, _amount] call _fnc_addItem;
} forEach (jna_datalist select IDC_RSCDISPLAYARSENAL_TAB_VEST);

[_avests, 1] call _fnc_addEmptyEntry;
_rebelGear set ["ArmoredVests", _avests];
_rebelGear set ["CivilianVests", _uvests];

// Helmet filtering
private _aheadgear = ["", [1.5,0.5] select (minWeaps < 0)]; // blank entry to phase in armour use gradually
private _aheadgear = []; //"", [1.5,0.5] select (minWeaps < 0)]; // blank entry to phase in armour use gradually
private _uheadgear = [];
{
_x params ["_class", "_amount"];
Expand All @@ -131,6 +142,7 @@ private _uheadgear = [];
[_array, _class, _amount] call _fnc_addItem;
} forEach (jna_datalist select IDC_RSCDISPLAYARSENAL_TAB_HEADGEAR);

[_aheadgear, 1] call _fnc_addEmptyEntry;
_rebelGear set ["ArmoredHeadgear", _aheadgear];
//_rebelGear set ["CosmeticHeadgear", _uheadgear]; // not used, rebels have template-defined basic headgear

Expand Down
61 changes: 5 additions & 56 deletions A3A/addons/core/functions/Ammunition/fn_randomRifle.sqf
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
/*
Equip unit with random weapon of preferred type using A3A_rebelGear
Adds magazines by mass. Uses default magazine of selected weapon
Select random rebel weapon of preferred type using A3A_rebelGear

Parameters:
0. <OBJECT> Rebel unit to equip with primary weapon.
1. <STRING> Preferred weapon type ("Rifles", "MachineGuns" etc).
2. <NUMBER> Optional, total mass of carried magazines to add.
0. <STRING> Preferred weapon type ("Rifles", "MachineGuns" etc).

Returns:
Nothing
<STRING> Weapon classname selected

Environment:
Scheduled, any machine
Expand All @@ -17,7 +14,7 @@ Environment:
#include "..\..\script_component.hpp"
FIX_LINE_NUMBERS()

params ["_unit", "_weaponType", ["_totalMagWeight", 50]];
params ["_weaponType"];

call A3A_fnc_fetchRebelGear; // Send current version of rebelGear from server if we're out of date

Expand All @@ -36,52 +33,4 @@ if (_pool isEqualTo []) then {
};
};
private _weapon = selectRandomWeighted _pool;

// Probably shouldn't ever be executed
if !(primaryWeapon _unit isEqualTo "") then {
if (_weapon == primaryWeapon _unit) exitWith {};
private _magazines = getArray (configFile / "CfgWeapons" / (primaryWeapon _unit) / "magazines");
{_unit removeMagazines _x} forEach _magazines; // Broken, doesn't remove mags globally. Pain to fix.
_unit removeWeapon (primaryWeapon _unit);
};

private _categories = _weapon call A3A_fnc_equipmentClassToCategories;

if ("GrenadeLaunchers" in _categories && {"Rifles" in _categories} ) then {
// lookup real underbarrel GL magazine, because not everything is 40mm
private _config = configFile >> "CfgWeapons" >> _weapon;
private _glmuzzle = getArray (_config >> "muzzles") select 1; // guaranteed by category
_glmuzzle = configName (_config >> _glmuzzle); // bad-case fix. compatibleMagazines is case-sensitive as of 2.12
private _glmag = compatibleMagazines [_weapon, _glmuzzle] select 0;
_unit addMagazines [_glmag, 5];
};

private _magazine = compatibleMagazines _weapon select 0;
private _magweight = 5 max getNumber (configFile >> "CfgMagazines" >> _magazine >> "mass");

_unit addWeapon _weapon;
if ("Handguns" in _categories) then {
_unit addHandgunItem _magazine;
} else {
_unit addPrimaryWeaponItem _magazine;
};
_unit addMagazines [_magazine, round (random 0.5 + _totalMagWeight / _magWeight)];


private _compatOptics = A3A_rebelOpticsCache get _weapon;
if (isNil "_compatOptics") then {
private _compatItems = compatibleItems _weapon; // cached, should be fast
_compatOptics = _compatItems arrayIntersect call {
if (_weaponType in ["Rifles", "MachineGuns"]) exitWith { A3A_rebelGear get "OpticsMid" };
if (_weaponType == "SniperRifles") exitWith { A3A_rebelGear get "OpticsLong" };
A3A_rebelGear get "OpticsClose";
};
if (_compatOptics isEqualTo []) then {
_compatOptics = _compatItems arrayIntersect call {
if (_weaponType in ["Rifles", "MachineGuns"]) exitWith { A3A_rebelGear get "OpticsClose" };
A3A_rebelGear get "OpticsMid";
};
};
A3A_rebelOpticsCache set [_weapon, _compatOptics];
};
if (_compatOptics isNotEqualTo []) then { _unit addPrimaryWeaponItem (selectRandom _compatOptics) };
_weapon;
17 changes: 17 additions & 0 deletions A3A/addons/core/functions/Ammunition/fn_setRebelLoadouts.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
Writes custom rebel loadouts structure & updates version number

Parameters:
<HASHMAP> Custom rebel loadouts structure

Environment:
Unscheduled, server
*/

params ["_newLoadouts"];

A3A_rebelLoadoutsVersion = time;
_newLoadouts set ["Version", A3A_rebelLoadoutsVersion];
A3A_rebelLoadouts = _newLoadouts;

publicVariable "A3A_rebelLoadoutsVersion";
3 changes: 2 additions & 1 deletion A3A/addons/core/functions/Base/fn_initPetros.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ removeGoggles petros;
private _vest = selectRandomWeighted (A3A_rebelGear get "ArmoredVests");
if (_vest == "") then { _vest = selectRandomWeighted (A3A_rebelGear get "CivilianVests") };
petros addVest _vest;
[petros, "Rifles"] call A3A_fnc_randomRifle;
private _weapon = ["Rifles"] call A3A_fnc_randomRifle;
[petros, _weapon, "OpticsMid", 50] call A3A_fnc_addPrimaryAndMags;
petros selectWeapon (primaryWeapon petros);

if (petros == leader group petros) then {
Expand Down
Loading