Skip to content
Open
5 changes: 5 additions & 0 deletions addons/csw/XEH_PREP.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,19 @@ PREP(assemble_startDeployTripod);
PREP(canGetIn);
PREP(deployCancel);
PREP(deployConfirm);
PREP(compatibleMagazines);
PREP(getCarryMagazine);
PREP(getNearbySources);
PREP(getSourceCompatibleMagazines);
PREP(handleProxyWeaponChanged);
PREP(handleScrollWheel);
PREP(proxyWeapon);

PREP(getLoadActions);
PREP(getUnloadActions);
PREP(reload_canLoadMagazine);
PREP(reload_canUnloadMagazine);
PREP(reload_canUseSource);
PREP(reload_getLoadableMagazines);
PREP(reload_getVehicleMagazine);
PREP(reload_handleAddTurretMag);
Expand Down
2 changes: 1 addition & 1 deletion addons/csw/XEH_postInit.sqf
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include "script_component.hpp"

GVAR(vehicleMagCache) = createHashMap;
GVAR(deployPFH) = -1;

["CBA_settingsInitialized", {
Expand Down Expand Up @@ -41,6 +40,7 @@ GVAR(deployPFH) = -1;
[QGVAR(addTurretMag), LINKFUNC(reload_handleAddTurretMag)] call CBA_fnc_addEventHandler;
[QGVAR(removeTurretMag), LINKFUNC(reload_handleRemoveTurretMag)] call CBA_fnc_addEventHandler;
[QGVAR(returnAmmo), LINKFUNC(reload_handleReturnAmmo)] call CBA_fnc_addEventHandler;
[QGVAR(proxyWeaponChanged), LINKFUNC(handleProxyWeaponChanged)] call CBA_fnc_addEventHandler;
[QGVAR(autofire_fire), LINKFUNC(autofire_fire)] call CBA_fnc_addEventHandler;

// Cancel placement if interact menu open
Expand Down
6 changes: 6 additions & 0 deletions addons/csw/XEH_preInit.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,11 @@ PREP_RECOMPILE_END;

GVAR(initializedStaticTypes) = [];

// Config-derived, so they hold for the whole session. Filled lazily by FUNC(compatibleMagazines) and
// FUNC(getCarryMagazine) rather than on weapon swap, a CSW without a proxy weapon needs them too
GVAR(vehicleMagCache) = createHashMap;
GVAR(compatibleCarryMagsCache) = createHashMap;
GVAR(compatibleVehicleMagsCache) = createHashMap;

ADDON = true;

6 changes: 3 additions & 3 deletions addons/csw/functions/fnc_ai_handleFired.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@

if (GVAR(ammoHandling) != 2) exitWith {};

params ["_vehicle", "_weapon", "", "", "", "_magazine", "", "_gunner"];
TRACE_4("firedEH:",_vehicle,_weapon,_magazine,_gunner);
params ["_vehicle", "", "", "", "", "", "", "_gunner"];
TRACE_2("firedEH:",_vehicle,_gunner);

if (someAmmo _vehicle) exitWith {};
if ((!local _gunner) || {[_gunner] call EFUNC(common,isPlayer)}) exitWith {};

TRACE_1("need ammo",magazinesAllTurrets _vehicle);

[_vehicle, _gunner, _weapon, _magazine] call FUNC(ai_reload);
[_vehicle, _gunner] call FUNC(ai_reload);
2 changes: 1 addition & 1 deletion addons/csw/functions/fnc_ai_handleGetIn.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ if ((!local _gunner) || {[_gunner] call EFUNC(common,isPlayer)}) exitWith {};

TRACE_1("need ammo",magazinesAllTurrets _vehicle);

[_vehicle, _gunner, currentWeapon _vehicle] call FUNC(ai_reload);
[_vehicle, _gunner] call FUNC(ai_reload);
100 changes: 29 additions & 71 deletions addons/csw/functions/fnc_ai_reload.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -6,95 +6,53 @@
* Arguments:
* 0: CSW <OBJECT>
* 1: Gunner <OBJECT>
* 2: Weapon <STRING>
* 3: Magazine <STRING> (default: "")
*
* Return Value:
* None
*
* Example:
* [cursorObject, gunner cursorObject] call ace_csw_fnc_ai_reload
*
* Public: No
*/

params ["_vehicle", "_gunner", "_weapon", ["_magazine", ""]];

private _isProxy = _gunner getVariable [QGVAR(autofire_isProxy), false];
TRACE_2("checking for proxy",_gunner,_isProxy);
if (_isProxy) exitWith {};
params ["_vehicle", "_gunner"];
TRACE_2("AI reload",_vehicle,_gunner);

private _turretPath = [_gunner] call EFUNC(common,getTurretIndex);
private _reloadSource = objNull;
private _reloadMag = "";
private _reloadNeededAmmo = -1;
// Proxy gunners are scripted, they do not carry or load anything
if (_gunner getVariable [QGVAR(autofire_isProxy), false]) exitWith {TRACE_1("proxy gunner",_gunner)};

private _cfgMagGroups = configFile >> QGVAR(groups);
// The turret is derived from the gunner rather than from a weapon argument. What weapon the CSW has
// selected is not an answerable question on GetIn: nothing has been fired yet, and FUNC(proxyWeapon)
// has just swapped the turret's weapon out from under it
private _loadableMagazines = [_vehicle, _gunner, true] call FUNC(reload_getLoadableMagazines);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This uses cached stuff if I've understood correctly. If the best source were to be deleted before the cache expires (5 seconds), line 44 would fail, yet the weapon would be reloaded - right? Or am I missing something?

if (_loadableMagazines isEqualTo []) exitWith {TRACE_1("no loadable mag",_vehicle)};

private _nearSupplies = [_gunner] + ((_vehicle nearSupplies 10) select {
isNull (group _x) ||
{!([_x] call EFUNC(common,isPlayer)) && {[side group _gunner, side group _x] call BIS_fnc_sideIsFriendly}}
});
private _bestAmmo = -1;
private _magazineInfo = [];

// Find if there is anything we can reload with
{
scopeName "findSource";
private _xSource = _x;

private _cswMagazines = [];
{
_cswMagazines pushBackUnique _x;
} forEach ((magazineCargo _xSource) select {isClass (_cfgMagGroups >> _x)});
TRACE_2("",_xSource,_cswMagazines);

private _compatibleMags = compatibleMagazines _weapon;
if (_magazine != "") then {
_compatibleMags insert [0, [_magazine]];
if ((_x select 4) > _bestAmmo) then {
_bestAmmo = _x select 4;
_magazineInfo = _x;
};
} forEach _loadableMagazines;

{
private _xWeaponMag = _x;
{
if ((getNumber (_cfgMagGroups >> _x >> _xWeaponMag)) == 1) then {
private _loadInfo = [_vehicle, _turretPath, _x, _xSource] call FUNC(reload_canLoadMagazine);
if (_loadInfo select 0) then {
_reloadMag = _x;
_reloadSource = _xSource;
_reloadNeededAmmo = _loadInfo select 2;
TRACE_3("found mag",_reloadMag,_reloadSource,_x);
breakOut "findSource";
};
};
} forEach _cswMagazines;
} forEach _compatibleMags;
} forEach _nearSupplies;
if (_reloadMag == "") exitWith {TRACE_1("could not find mag",_reloadMag);};

// Figure out what we can add from the magazines we have
private _bestAmmoToSend = -1;
{
_x params ["_xMag", "_xAmmo"];
TRACE_2("",_xMag,_xAmmo);
if (_xMag == _reloadMag) then {
if ((_bestAmmoToSend == -1) || {(_xAmmo > _bestAmmoToSend) && {_xAmmo <= _reloadNeededAmmo}}) then {
_bestAmmoToSend = _xAmmo;
};
};
} forEach (if (_reloadSource isKindOf "CAManBase") then {magazinesAmmo _reloadSource} else {magazinesAmmoCargo _reloadSource});
TRACE_4("",_reloadSource,_reloadMag,_reloadNeededAmmo,_bestAmmoToSend);
if (_bestAmmoToSend == -1) exitWith {ERROR("No ammo");};
_magazineInfo params ["_carryMag", "_turretPath", "", "_magSource", "_ammo"];

// Remove the mag from the source
[_reloadSource, _reloadMag, _bestAmmoToSend] call EFUNC(common,removeSpecificMagazine);
[_magSource, _carryMag, _ammo] call EFUNC(common,removeSpecificMagazine);

private _timeToLoad = 1;
if (!isNull(configOf _vehicle >> QUOTE(ADDON) >> "ammoLoadTime")) then {
_timeToLoad = getNumber(configOf _vehicle >> QUOTE(ADDON) >> "ammoLoadTime");
};
// Leftover ammo goes back to the gunner
private _eventParams = [_vehicle, _turretPath, _carryMag, _ammo, _gunner];

private _timeToLoad = GET_NUMBER(configOf _vehicle >> QUOTE(ADDON) >> "ammoLoadTime",1);
TRACE_1("Reloading in progress",_timeToLoad);

[{
params ["_vehicle", "_turretPath", "_gunner", "_reloadMag", "_bestAmmoToSend"];
if ((!alive _vehicle) || {!alive _gunner} || {(_vehicle distance _gunner) > 10}) exitWith {TRACE_1("invalid state",_this);};
params ["_vehicle", "_turretPath", "", "", "_gunner"];
if !(alive _vehicle && {alive _gunner}) exitWith {TRACE_2("invalid state",alive _vehicle,alive _gunner)};

// Reload the static weapon
TRACE_5("calling addTurretMag event",_vehicle,_turretPath,_gunner,_reloadMag,_bestAmmoToSend);
[QGVAR(addTurretMag), _this] call CBA_fnc_globalEvent;
}, [_vehicle, _turretPath, _gunner, _reloadMag, _bestAmmoToSend], _timeToLoad] call CBA_fnc_waitAndExecute;
TRACE_1("calling addTurretMag event",_this);
[QGVAR(addTurretMag), _this, _vehicle, _turretPath] call CBA_fnc_turretEvent;
}, _eventParams, _timeToLoad] call CBA_fnc_waitAndExecute;
60 changes: 60 additions & 0 deletions addons/csw/functions/fnc_compatibleMagazines.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#include "..\script_component.hpp"
/*
* Author: LinkIsGrim
* Gets all carry magazines that can be loaded into a CSW, includes weapons added by script.
*
* Arguments:
* 0: CSW <OBJECT>
*
* Return Value:
* Compatible Carry Magazines <HASHMAP>
* Magazine classname <STRING>
* true <BOOL>
*
* Example:
* [cursorObject] call ace_csw_fnc_compatibleMagazines
*
* Public: Yes
*/

params [["_csw", objNull, [objNull]]];

// Read from config rather than GVAR(initializedStaticTypes), which is only filled where there is an
// interface and so is always empty on a dedicated server
if ((getNumber (configOf _csw >> QUOTE(ADDON) >> "enabled")) != 1) exitWith {createHashMap};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this blocks all loading on non-enabled static weapons
e.g. HMG_01_A_base_F (auto turrets)

before you could unload and load them when empty
now you can unload but never load


// Caches are filled here rather than on weapon swap, a CSW without a proxy weapon needs them too
private _fnc_cacheWeapon = {
private _weapon = _this;

GVAR(compatibleCarryMagsCache) getOrDefaultCall [_weapon, {
// Engine command, not this function
private _vehicleMags = compatibleMagazines _weapon;
GVAR(compatibleVehicleMagsCache) set [_weapon, _vehicleMags];

// Vehicle magazines without a carry equivalent come back as "", they can't be loaded by hand
private _carryMags = (_vehicleMags apply {_x call FUNC(getCarryMagazine)}) select {_x != ""};

_carryMags createHashMapFromArray (_carryMags apply {true})
}, true]
};

private _weapons = [];
{
private _turret = _x;
{
_weapons pushBackUnique _x;
} forEach (_csw weaponsTurret _turret);
} forEach (allTurrets _csw);

if (_weapons isEqualTo []) exitWith {createHashMap};

// Copies, so callers can't edit the cache. Single weapon is by far the most common CSW, skip the merge
if (count _weapons isEqualTo 1) exitWith {+((_weapons select 0) call _fnc_cacheWeapon)};

private _carryMagazines = createHashMap;
{
_carryMagazines merge [_x call _fnc_cacheWeapon, true];
} forEach _weapons;

_carryMagazines // return
16 changes: 9 additions & 7 deletions addons/csw/functions/fnc_getCarryMagazine.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@
* Public: No
*/

params ["_vehicleMag"];
params [["_vehicleMag", "", [""]]];

private _carryMag = GVAR(vehicleMagCache) get _vehicleMag;
if (isNil "_carryMag") then {
if (_vehicleMag == "") exitWith {""};

// Config lookups are case-insensitive but hashmap keys are not, so normalise before caching.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, but as far as I can tell, all instances of FUNC(getCarryMagazine) are called with engine commands, so there are only config-sensitive entries that are passed.

// A magazine with no carry equivalent caches "" on purpose, so the miss costs nothing to look up again
GVAR(vehicleMagCache) getOrDefaultCall [toLowerANSI _vehicleMag, {
private _groups = "getNumber (_x >> _vehicleMag) == 1 && {isClass (configFile >> 'CfgMagazines' >> configName _x)}" configClasses (configFile >> QGVAR(groups));
_carryMag = configName (_groups param [0, configNull]);
GVAR(vehicleMagCache) set [_vehicleMag, _carryMag];
private _carryMag = configName (_groups param [0, configNull]);
TRACE_2("setting cache",_vehicleMag,_carryMag);
};

_carryMag
_carryMag
}, true] // return
2 changes: 1 addition & 1 deletion addons/csw/functions/fnc_getLoadActions.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ private _actions = [];
private _text = if (_isBeltLinking) then {
format [LLSTRING(actionLink), _displayName];
} else {
format [LLSTRING(loadX), _displayName];
format [LLSTRING(actionLoad), _displayName];
};

private _action = [format ["load_%1", _forEachIndex], _text, _picture, _statement, _condition, {}, _x] call EFUNC(interact_menu,createAction);
Expand Down
81 changes: 81 additions & 0 deletions addons/csw/functions/fnc_getNearbySources.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include "..\script_component.hpp"
/*
* Author: LinkIsGrim
* Gets available ammo sources for loading a CSW. Units are replaced by their containers, since
* magazineCargo does not read a unit's inventory.
*
* Arguments:
* 0: Unit or vehicle attempting to load <OBJECT>
* 1: Skip vehicle sources <BOOL> (default: false)
* 2: Include crew of the source <BOOL> (default: false)
*
* Return Value:
* Ammo sources <ARRAY of OBJECT>
*
* Example:
* [player] call ace_csw_fnc_getNearbySources
*
* Public: No
*/

params ["_unit", ["_skipVehicles", false], ["_includeCrew", false]];

// Normalised so an omitted default and an explicit one hit the same cache entry
private _params = [_unit, _skipVehicles, _includeCrew];

[
_params,
{
params ["_unit", "_skipVehicles", "_includeCrew"];

// group is grpNull on a vehicle, so a CSW passed straight in has to be asked directly
private _side = if (_unit isKindOf "CAManBase") then {side group _unit} else {side _unit};

// group is also grpNull on crates and weapon holders, which is what lets them through
private _nearSupplies = (_unit nearSupplies DISTANCE_SEARCH_RADIUS) select {
isNull (group _x) ||
{!([_x] call EFUNC(common,isPlayer)) && {[_side, side group _x] call BIS_fnc_sideIsFriendly}}
};

if (_includeCrew) then {
_nearSupplies append (crew _unit);
};

if (_skipVehicles) then {
_nearSupplies = _nearSupplies select {
private _source = _x;
(["Ship", "Car", "Air", "Tank"] findIf {_source isKindOf _x}) == -1
};
};

_nearSupplies pushBackUnique _unit;

// Built into a second array rather than appended in place, forEach over an array that is
// growing underneath it has no defined behaviour
private _sources = [];
{
// magazineCargo does not read a unit's inventory, only their containers do
if (_x isKindOf "CAManBase") then {
{
_sources pushBackUnique _x;
} forEach [uniformContainer _x, vestContainer _x, backpackContainer _x];
continue;
};

_sources pushBackUnique _x;

{
_x params ["", "_container"];
_sources pushBackUnique _container;
} forEach (everyContainer _x);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Doesn't handle recursive containers - tbf, I don't know how necessary that is though.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

gotta stop the rabbit hole somewhere IMO

besides being able to load from a vehicle is believe being able to load from a backpack inside one, ehhhhh

} forEach _nearSupplies;

// A unit with no vest or backpack gives objNull back
_sources select {!isNull _x} // return
},
_unit,
// The uid has to cover every argument, cachedCall hands it to setVariable so it must be a string
format [QGVAR(nearbySourcesCache_%1), hashValue _params],
NEARBY_SOURCES_CACHE_EXPIRY,
QGVAR(clearNearbySourcesCache)
] call EFUNC(common,cachedCall)
Loading