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

Improve Rebel Weapon and Other Items Selection #427

Open
wants to merge 16 commits into
base: unstable
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions A3A/addons/core/CfgFunctions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class CfgFunctions
class generateRebelGear {};
class getRadio {};
class hasARadio {};
class itemArrayWeight {};
class itemConfig {};
class itemConfigMass {};
class itemSort {};
Expand Down
268 changes: 110 additions & 158 deletions A3A/addons/core/functions/Ammunition/fn_generateRebelGear.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ Info("Started updating A3A_rebelGear");
#define ITEM_MAX 50

private _fnc_addItemNoUnlocks = {
params ["_array", "_class", "_amount"];
if (_amount < 0) exitWith { _array append [_class, 1] };
params ["_array", "_class", "_amount", ["_arrayWeight", 1]];
if (_amount < 0) exitWith { _array append [_class, _arrayWeight] };
if (_amount <= ITEM_MIN) exitWith {};
_array pushBack _class;
_array pushBack linearConversion [ITEM_MIN, ITEM_MAX, _amount, 0, 1, true];
_array pushBack (linearConversion [ITEM_MIN, ITEM_MAX, _amount, 0, 1, true] * _arrayWeight); // multiply weight (preference) by ratio of amount of item to max amount of that item such that items rebels have more of are more likely to be selected
};

private _fnc_addItemUnlocks = {
params ["_array", "_class", "_amount"];
if (_amount < 0) exitWith { _array append [_class, 1] };
params ["_array", "_class", "_amount", ["_arrayWeight", 1]];
if (_amount < 0) exitWith { _array append [_class, _arrayWeight] };
};

private _fnc_addItem = [_fnc_addItemUnlocks, _fnc_addItemNoUnlocks] select (minWeaps < 0);
Expand All @@ -37,155 +37,113 @@ private _fnc_addItem = [_fnc_addItemUnlocks, _fnc_addItemNoUnlocks] select (minW
// Work with temporary array so that we're not transferring partials
private _rebelGear = createHashMap;

// Primary weapon filtering
private _rifle = [];
private _smg = [];
private _shotgun = [];
private _sniper = [];
private _mg = [];
private _gl = [];
{
_x params ["_class", "_amount"];
private _categories = _class call A3A_fnc_equipmentClassToCategories;

call {
if ("GrenadeLaunchers" in _categories) exitWith { [_gl, _class, _amount] call _fnc_addItem }; // call before rifles
if ("Rifles" in _categories) exitWith { [_rifle, _class, _amount/2] call _fnc_addItem };
if ("SniperRifles" in _categories) exitWith { [_sniper, _class, _amount] call _fnc_addItem };
if ("MachineGuns" in _categories) exitWith { [_mg, _class, _amount] call _fnc_addItem };
if ("SMGs" in _categories) exitWith { [_smg, _class, _amount] call _fnc_addItem };
if ("Shotguns" in _categories) exitWith { [_shotgun, _class, _amount] call _fnc_addItem };
};
} forEach (jna_dataList select IDC_RSCDISPLAYARSENAL_TAB_PRIMARYWEAPON);

_rebelGear set ["Rifles", _rifle];
_rebelGear set ["SMGs", _smg];
_rebelGear set ["Shotguns", _shotgun];
_rebelGear set ["MachineGuns", _mg];
_rebelGear set ["SniperRifles", _sniper];
_rebelGear set ["GrenadeLaunchers", _gl];

// Secondary weapon filtering
private _rlaunchers = [];
private _mlaunchersAT = [];
private _mlaunchersAA = [];
{
_x params ["_class", "_amount"];
private _categories = _class call A3A_fnc_equipmentClassToCategories;
/* if !("Disposable" in _categories) then {
private _magcount = _class call _fnc_magCount;
_amount = _amount min (_magcount/2);
};*/

if ("RocketLaunchers" in _categories) then { [_rlaunchers, _class, _amount] call _fnc_addItem; continue };
if ("MissileLaunchers" in _categories) then {
if ("AA" in _categories) exitWith { [_mlaunchersAA, _class, _amount] call _fnc_addItemNoUnlocks };
if ("AT" in _categories) exitWith { [_mlaunchersAT, _class, _amount] call _fnc_addItemNoUnlocks };
};
} forEach (jna_datalist select IDC_RSCDISPLAYARSENAL_TAB_SECONDARYWEAPON);

_rebelGear set ["RocketLaunchers", _rlaunchers];
_rebelGear set ["MissileLaunchersAT", _mlaunchersAT];
_rebelGear set ["MissileLaunchersAA", _mlaunchersAA];

// Vest filtering
private _avests = ["", [1.5,0.5] select (minWeaps < 0)]; // blank entry to phase in armour use gradually
private _uvests = [];
{
_x params ["_class", "_amount"];
private _categories = _class call A3A_fnc_equipmentClassToCategories;
private _array = [_uvests, _avests] select ("ArmoredVests" in _categories);
[_array, _class, _amount] call _fnc_addItem;
} forEach (jna_datalist select IDC_RSCDISPLAYARSENAL_TAB_VEST);

_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 _uheadgear = [];
{
_x params ["_class", "_amount"];
private _categories = _class call A3A_fnc_equipmentClassToCategories;
private _array = [_uheadgear, _aheadgear] select ("ArmoredHeadgear" in _categories);
[_array, _class, _amount] call _fnc_addItem;
} forEach (jna_datalist select IDC_RSCDISPLAYARSENAL_TAB_HEADGEAR);

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

// Backpack filtering
private _backpacks = [];
{
_x params ["_class", "_amount"];
private _categories = _class call A3A_fnc_equipmentClassToCategories;
if ("BackpacksCargo" in _categories) then { [_backpacks, _class, _amount] call _fnc_addItem };
} forEach (jna_datalist select IDC_RSCDISPLAYARSENAL_TAB_BACKPACK);

_rebelGear set ["BackpacksCargo", _backpacks];

// NVG filtering
private _nvgs = ["", 0.5]; // blank entry for phase-in
{
_x params ["_class", "_amount"];
private _categories = _class call A3A_fnc_equipmentClassToCategories;
if !("NVGThermal" in _categories) then { [_nvgs, _class, _amount] call _fnc_addItem };
} forEach (jna_datalist select IDC_RSCDISPLAYARSENAL_TAB_NVGS);

_rebelGear set ["NVGs", _nvgs];

// Unfiltered stuff (just radios atm? could add GPS)
private _radios = [];
{ [_radios, _x#0, _x#1] call _fnc_addItem } forEach (jna_dataList select IDC_RSCDISPLAYARSENAL_TAB_RADIO);
_rebelGear set ["Radios", _radios];

// Misc items. Don't really need weighting but whatever
private _minedetectors = [];
private _toolkits = [];
private _medikits = [];
{
_x params ["_class", "_amount"];
private _categories = _class call A3A_fnc_equipmentClassToCategories;
call {
if ("MineDetectors" in _categories) exitWith { [_minedetectors, _class, _amount] call _fnc_addItem };
if ("Toolkits" in _categories) exitWith { [_toolkits, _class, _amount] call _fnc_addItem };
if ("Medikits" in _categories) exitWith { [_medikits, _class, _amount] call _fnc_addItem };
};
} forEach (jna_dataList select IDC_RSCDISPLAYARSENAL_TAB_CARGOMISC);

_rebelGear set ["MineDetectors", _minedetectors];
_rebelGear set ["Toolkits", _toolkits];
_rebelGear set ["Medikits", _medikits];

// Hand grenades
private _smokes = [];
private _nades = [];
{
_x params ["_class", "_amount"];
private _categories = _class call A3A_fnc_equipmentClassToCategories;
call {
if ("SmokeGrenades" in _categories) exitWith { [_smokes, _class, _amount] call _fnc_addItem };
if ("Grenades" in _categories) exitWith { [_nades, _class, _amount] call _fnc_addItem };
};
} forEach (jna_datalist select IDC_RSCDISPLAYARSENAL_TAB_CARGOTHROW);

_rebelGear set ["SmokeGrenades", _smokes];
_rebelGear set ["Grenades", _nades];

// Explosives. Could add mines but don't want them atm.
private _charges = [];
{
_x params ["_class", "_amount"];
private _categories = _class call A3A_fnc_equipmentClassToCategories;
if ("ExplosiveCharges" in _categories) then { [_charges, _class, _amount] call _fnc_addItemNoUnlocks };
} forEach (jna_datalist select IDC_RSCDISPLAYARSENAL_TAB_CARGOPUT);

_rebelGear set ["ExplosiveCharges", _charges];
{
_x params ["_class", "_amount"];
private _categories = _class call A3A_fnc_equipmentClassToCategories;
private _mainCategory = _categories select 0;

switch (_mainCategory) do {
// Primary Weapons
case "Rifles";
case "SniperRifles";
case "GrenadeLaunchers";
case "MachineGuns";
case "SMGs";
case "Shotguns";
case "PrimaryWeaponsCatchAll" : {
_arrayWeight = [_class, _categories] call A3A_fnc_itemArrayWeight;
_array = _rebelGear getOrDefault [[_mainCategory, "GrenadeLaunchers"] select ("GrenadeLaunchers" in _categories), [], true];
[_array, _class, _amount, _arrayWeight] call _fnc_addItem;
};

// Secondary Weapons
case "RocketLaunchers": {
_arrayWeight = [_class, _categories] call A3A_fnc_itemArrayWeight;
_array = _rebelGear getOrDefault ["RocketLaunchers", [], true];
[_array, _class, _amount, _arrayWeight] call _fnc_addItem;
};
case "MissileLaunchers": {
if ("AA" in _categories) exitWith {
_array = _rebelGear getOrDefault ["MissileLaunchersAA", [], true];
[_array, _class, _amount] call _fnc_addItemNoUnlocks
};
if ("AT" in _categories) exitWith {
_array = _rebelGear getOrDefault ["MissileLaunchersAT", [], true];
[_array, _class, _amount] call _fnc_addItemNoUnlocks
};
};

// Handguns
case "Handguns": {
_arrayWeight = [_class, _categories] call A3A_fnc_itemArrayWeight;
_array = _rebelGear getOrDefault ["Handguns", [], true];
[_array, _class, _amount, _arrayWeight] call _fnc_addItem;
};

// Gear
case "Vests": {
_array = _rebelGear getOrDefault [["CivilianVests", "ArmoredVests"] select ("ArmoredVests" in _categories), ["", [1.5,0.5] select (minWeaps < 0)], true];
[_array, _class, _amount] call _fnc_addItem;
};
case "Headgear": {
//_array = _rebelGear getOrDefault [["CosmeticHeadgear", "ArmoredHeadgear"] select ("ArmoredHeadgear" in _categories), ["", [1.5,0.5] select (minWeaps < 0)], true]; // not used, rebels have template-defined basic headgear
_array = _rebelGear getOrDefault ["ArmoredHeadgear", [], true];
if ("ArmoredHeadgear" in _categories) then { [_array, _class, _amount] call _fnc_addItem };
};
case "Backpacks": {
_array = _rebelGear getOrDefault ["BackpacksCargo", [], true];
if ("BackpacksCargo" in _categories) then { [_array, _class, _amount] call _fnc_addItem };
};

// Items
case "NVGs": {
_array = _rebelGear getOrDefault ["NVGs", ["", 0.5], true];
if !("NVGThermal" in _categories) then { [_array, _class, _amount] call _fnc_addItem };
};
case "Radios";
case "MineDetectors";
case "Toolkits";
case "Medikits";
case "CargoMiscCatchAll": {
_array = _rebelGear getOrDefault [_mainCategory, [], true];
[_array, _class, _amount] call _fnc_addItem;
};

default {
// Grenades / Explosives
if ("SmokeGrenades" in _categories) exitWith {
_array = _rebelGear getOrDefault ["SmokeGrenades", [], true];
[_array, _class, _amount] call _fnc_addItem
};
if ("Grenades" in _categories) exitWith {
_array = _rebelGear getOrDefault ["Grenades", [], true];
[_array, _class, _amount] call _fnc_addItem
};
if ("ExplosiveCharges" in _categories) exitWith {
_array = _rebelGear getOrDefault ["ExplosiveCharges", [], true];
[_array, _class, _amount] call _fnc_addItemNoUnlocks
};
};
};
} forEach (jna_datalist select _x);
} forEach [
IDC_RSCDISPLAYARSENAL_TAB_PRIMARYWEAPON,
IDC_RSCDISPLAYARSENAL_TAB_SECONDARYWEAPON,
IDC_RSCDISPLAYARSENAL_TAB_VEST,
IDC_RSCDISPLAYARSENAL_TAB_HEADGEAR,
IDC_RSCDISPLAYARSENAL_TAB_BACKPACK,
IDC_RSCDISPLAYARSENAL_TAB_NVGS,
IDC_RSCDISPLAYARSENAL_TAB_RADIO,
IDC_RSCDISPLAYARSENAL_TAB_CARGOMISC,
IDC_RSCDISPLAYARSENAL_TAB_CARGOTHROW,
IDC_RSCDISPLAYARSENAL_TAB_CARGOPUT
];

// Optic filtering. No weighting because of weapon compatibilty complexity
private _opticClose = [];
private _opticMid = [];
private _opticLong = [];
private _opticClose = _rebelGear getOrDefault ["OpticsClose", [], true];
private _opticMid = _rebelGear getOrDefault ["OpticsMid", [], true];
private _opticLong = _rebelGear getOrDefault ["OpticsLong", [], true];
private _midCount = 0;
{
_x params ["_class", "_amount"];
Expand Down Expand Up @@ -215,22 +173,16 @@ if (_midCount < ITEM_MAX*2) then {
};
};

_rebelGear set ["OpticsClose", _opticClose];
_rebelGear set ["OpticsMid", _opticMid];
_rebelGear set ["OpticsLong", _opticLong];
_rebelGear set ["OpticsAll", _opticClose + _opticMid + _opticLong]; // for launchers

// Light attachments, also no weights because of weapon compat
private _lights = [];
{
_x params ["_class", "_amount"];
if (_amount > 0 and {minWeaps > 0 or _amount < ITEM_MIN}) then { continue };
private _categories = _class call A3A_fnc_equipmentClassToCategories;
if ("LightAttachments" in _categories) then { _lights pushBack _class };
if ("LightAttachments" in _categories) then { (_rebelGear getOrDefault ["LightAttachments", [], true]) pushBack _class };
} forEach (jna_dataList select IDC_RSCDISPLAYARSENAL_TAB_ITEMACC);

_rebelGear set ["LightAttachments", _lights];

// Update everything while unscheduled so that version numbers match
isNil {
A3A_rebelGearVersion = time;
Expand Down
79 changes: 79 additions & 0 deletions A3A/addons/core/functions/Ammunition/fn_itemArrayWeight.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
Calculates weight (preference - for use in weighted list(s)) of given item based on attributes important to that item type.
Currently supports weapon item types, but is structured to calculate additional types in the future.

Author: jwoodruff40

Uses some concepts (and particularly _impact calculation) from ACE.
Thanks ACE!
(https://github.com/acemod/ACE3)

Params:
_class - ( String ) - Class of the item to calculate weight for.
_categories - ( Array (of Strings) ) - Categories the item fits in to, generated by _fn_equipmentClassToCategories

Returns:
_arrayWeight - ( Number ) - weight for use in weighted list / selection by selectRandomWeighted.
**/

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

params ["_class", ["_categories", []]];

private _config = _class call A3A_fnc_itemConfig;

// Total "score" (array weight) of the item based on calculated properties
private _arrayWeight = 1; // in case this function is called with an itemType without custom weighting setup or without an item type, just use default weight (but this shouldn't be called unless you're attempting to change this behavior)

if ("Weapons" in _categories) then {
private _magcfg = getArray (_config >> "Magazines") # 0 call A3A_fnc_itemConfig;
private _ammocfg = getText (_magcfg >> "ammo") call A3A_fnc_itemConfig;
private _firemode = getArray (_config >> "modes") # 0; // primary firemode ("SINGLE", "FULLAUTO", etc)
private _modecfg = [_config >> _firemode, _config] select (_firemode == "this");

private _weight = (_config call A3A_fnc_itemConfigMass); // Mass / Weight
private _accuracy = getNumber (_modecfg >> "dispersion"); // Dispersion / Accuracy
private _reloadtime = getNumber (_modecfg >> "reloadTime");
private _rof = if (_reloadTime == 0) then {0} else {1 / _reloadTime};
private _magcap = getNumber (_magcfg >> "count"); // Mag Capacity
private _hit = getNumber ( _ammocfg >> "hit");
private _basevel = getNumber (_config >> "initSpeed");
private _magvel = getNumber (_magcfg >> "initSpeed");
private _muzvel = if (_basevel > 0) then { _basevel } else { if (_basevel == 0) then { _magvel } else { abs _basevel * _magvel } };
private _impact = sqrt (_hit ^ 2 * _muzvel); // impact based on ACE3 concept combining hit / damage and velocity of projectile; modified from ACE3\addons\ACE_ARSENAL\functions\fnc_statBarStatement_impact.sqf
private _caliber = getNumber (_ammocfg >> "caliber"); // Unintuitively by name, this is a penetration multiplier. More useful for rockets / missiles than bullets.
private _effrange = 0; // not always reliable (hence not used in most weapons). Iterates through all firemodes, so probably doesn't need to be run for every weapon type.
if (_firemode == "this") then {
_effrange = getNumber (_config >> "MidRange") * getNumber (_config >> "MidRangeProbab");
} else {
{
private _range = getNumber (_config >> _x >> "MidRange") * getNumber (_config >> _x >> "MidRangeProbab");
if (_range > _effrange) then { _effrange = _range };
} forEach getArray (_config >> "modes");
};
private _isDisposable = [0, 20] select ("Disposable" in _categories);

switch (_categories select 0) do {
// Calculate _arrayWeight based on item attributes above, and scaling them as needed (which attributes and how they're scaled dependent on item type)
case "Rifles": { _arrayWeight = round ((_accuracy * 10000) + _rof + _magcap + (_impact / 30) - (_weight / 5)) }; // Rifles. Blend of most attributes.
case "SniperRifles" : { _arrayWeight = round ((_accuracy * 50000) + (_effrange / 15) + (_impact / 30) + _rof - (_weight/3)) }; // Favor accuracy and range more. RoF still factored due to generally shorter range / more frenetic AI engagements.
case "GrenadeLaunchers";
case "MachineGuns";
case "SMGs";
case "Shotguns";
case "PrimaryWeaponsCatchAll" : { _arrayWeight = round ((_accuracy * 10000) + _rof + _magcap + (_impact / 30) - (_weight / 5)) }; // Primary weapons catchall. Same as Rifles.
case "RocketLaunchers" : { _arrayWeight = round ((_impact * _caliber / 20) + (_effrange / 4) - (_weight / 2) - _isDisposable) }; // Multiplies impact by caliber (penetration) to favor launchers better against vehicles.
case "MissileLaunchersAT";
case "MissileLaunchersAA";
case "SecondaryWeaponsCatchAll" : { _arrayWeight = 1 }; // placeholder catchall for launchers
case "Handguns" : { _arrayWeight = round (_rof + _magcap + (_impact * 20000) - (_weight * 2.5)) }; // Handguns. Array weight favors weapons with high RoF, Mag capacity, and impact force while heavily discriminating against weight, since it's a backup weapon.
case "ArmoredVests";
case "CivilianVests";
case "ArmoredHeadgear";
case "Backpacks";
case "GearCatchAll" : { _arrayWeight = 1 }; // placeholder catchall for gear (vests, helmets, backpacks)
};
};

[1, _arrayWeight] select (_arrayWeight > 0);
Loading