diff --git a/addons/overthrow_main/CfgFunctions.hpp b/addons/overthrow_main/CfgFunctions.hpp index 6fe1d2ec..2c65640f 100644 --- a/addons/overthrow_main/CfgFunctions.hpp +++ b/addons/overthrow_main/CfgFunctions.hpp @@ -429,6 +429,12 @@ class CfgFunctions class vehicleGetName {}; class vehicleGetPic {}; class getSearchStock {}; + class dumpContainer {}; + class dumpUnitLoadout {}; + class dumpWeapon {}; + class dumpItem {}; + class canDumpUnitLoadout {}; + class canDumpContainer {}; }; /* diff --git a/addons/overthrow_main/functions/AI/orders/fn_orderLoot.sqf b/addons/overthrow_main/functions/AI/orders/fn_orderLoot.sqf index d7e3e518..6dd1b8f1 100644 --- a/addons/overthrow_main/functions/AI/orders/fn_orderLoot.sqf +++ b/addons/overthrow_main/functions/AI/orders/fn_orderLoot.sqf @@ -1,131 +1,199 @@ -private _sorted = []; -private _myunits = groupSelectedUnits player; - +private _selectedUnits = groupSelectedUnits player; { player groupSelectUnit [_x, false]; -} forEach (groupSelectedUnits player); - -_myunits params ["_tt"]; -if(!isNull objectParent _tt) then { - _sorted = [objectParent _tt]; -}else{ - private _objects = _tt nearEntities [["Car","ReammoBox_F","Air","Ship"],20]; - if(count _objects isEqualTo 0) exitWith { - "Cannot find any containers or vehicles within 20m of first selected unit" call OT_fnc_notifyMinor; - }; - _sorted = [_objects,[],{_x distance _tt},"ASCEND"] call BIS_fnc_SortBy; +} forEach (_selectedUnits); + +// If at least one selected unit is a driver of a recovery truck, do truck recovery instead +private _unitInRecoveryTruck = _selectedUnits findIf {objectParent _x isKindOf "OT_I_Truck_recovery" && driver objectParent _x isEqualTo _x}; +if (_unitInRecoveryTruck > -1) exitWith { + [_selectedUnits # _unitInRecoveryTruck] spawn OT_fnc_recover; }; -if(count _sorted isEqualTo 0) exitWith {}; -private _target = _sorted select 0; +private _sortedTargets = nearestObjects [_selectedUnits # 0, ["Car", "ReammoBox_F", "Air", "Ship"], 20] select {alive _x}; +if (count _sortedTargets isEqualTo 0) exitWith { + "Cannot find any containers or vehicles within 20m of first selected unit" call OT_fnc_notifyMinor; +}; +private _target = _sortedTargets # 0; { - if ((typeOf vehicle _x) == "OT_I_Truck_recovery" && (driver vehicle _x) == _x) exitWith { - [_x] spawn OT_fnc_recover; - }; - [_x,_target] spawn { - private _active = true; - private _wasincar = false; - private _car = objNull; + [_x, _target] spawn { + params ["_looter", "_target"]; - private _unit = _this select 0; + private _range = 100; - _unit setVariable ["NOAI",true,true]; - _unit setBehaviour "SAFE"; - [_unit, ""] remoteExec ["switchMove", 0, false]; + _looter globalChat format["Looting bodies and item piles within %1m into the %2", _range, (typeOf _target) call OT_fnc_vehicleGetName]; - if(!isNull objectParent _unit) then { - _car = (objectParent _unit); - doGetOut _unit; - _wasincar = true; - }; + _looter setBehaviour "SAFE"; + if (!isNull objectParent _looter) then { + doGetOut _looter; + waitUntil {sleep 1; (isNull objectParent _looter) || (!alive _looter)}; + }; - _t = _this select 1; + _looter doMove ASLtoAGL (getPosASL _target); - _unit globalchat format["Looting bodies within 100m into the %1",(typeof _t) call OT_fnc_vehicleGetName]; + // Wait until looter reaches the target container + private _timeout = time + 30; + waitUntil {sleep 1; (_looter distance _target < 12) || (!alive _looter) || (!alive _target) || (_timeout < time)}; + if ((!alive _looter) || (!alive _target) || (_timeout < time)) exitWith { + if (alive _looter) then {_looter globalChat format ["Can't get to the %1, cancelling loot order", (typeOf _target) call OT_fnc_vehicleGetName]}; + }; - private _istruck = true; - if(count _this isEqualTo 2) then { - _istruck = (_t isKindOf "Truck_F") || (_t isKindOf "ReammoBox_F"); + // Looter has reached the target container. Dump his loadout to it. + if !([_looter, _target] call OT_fnc_canDumpUnitLoadout) exitWith { + _looter globalChat "This vehicle is full, cancelling loot order"; }; - _unit doMove ASLtoAGL (getPosASL _t); - - _timeout = time + 30; - waitUntil {sleep 1; (!alive _unit) || (isNull _t) || (_unit distance _t < 10) || (_timeOut < time) || (unitReady _unit)}; - if(!alive _unit || (isNull _t) || (_timeOut < time)) exitWith {}; - - if !([_unit,_t] call OT_fnc_dumpStuff) then { - _unit globalchat "This vehicle is full, cancelling loot order"; - _active = false; - }; - private _weapons = (_t nearObjects ["WeaponHolder", 100]) + (_t nearEntities ["WeaponHolderSimulated", 100]); - _unit globalchat format["Looting %1 weapons",count _weapons]; - { - _weapon = _x; - _s = (weaponsItems _weapon) select 0; - if(!isNil {_s}) then { - _cls = (_s select 0); - _i = _s select 1; - if(_i != "") then {_t addItemCargoGlobal [_i,1]}; - _i = _s select 2; - if(_i != "") then {_t addItemCargoGlobal [_i,1]}; - _i = _s select 3; - if(_i != "") then {_t addItemCargoGlobal [_i,1]}; - - if (!(_t canAdd (_cls call BIS_fnc_baseWeapon)) && !_istruck) exitWith { - _unit globalchat "This vehicle is full, cancelling loot order"; - _active = false; - }; - _t addWeaponCargoGlobal [_cls call BIS_fnc_baseWeapon,1]; - deleteVehicle _weapon; + private _looterOwnUniform = uniform _looter; + + [_looter, _target] call OT_fnc_dumpUnitLoadout; + _looter setUnitLoadout [[], [], [], [_looterOwnUniform, []], [], [], "", "", [], ["", "", "", "", "", ""]]; + + while {true} do { + private _sortedBodies = []; + { + // Some bodies are inside vehicles, so we search through the crew of every vehicle + // we find. Luckily every man is crew of itself so the same code also works for + // bodies on the ground. + private _vehicleOrMan = _x; + { + private _body = _x; + if (!alive _body && !(_body getVariable ["OT_looterReserved", false])) then { + _sortedBodies pushBack _x; + }; + } forEach crew _vehicleOrMan; + } forEach (nearestObjects [_target, ["AllVehicles"], _range]); + + if (_sortedBodies isNotEqualTo []) then { + // There are bodies to be looted. Loot the nearest body. + + _looter globalChat format ["%1 bodies to loot", count _sortedBodies]; + private _body = _sortedBodies # 0; + + _body setVariable ["OT_looterReserved", true, false]; + _looter doMove ASLtoAGL (getPosASL _body); + [_looter, 1] call OT_fnc_experience; + + // Wait until looter reaches the body + _timeout = time + 30; + waitUntil {sleep 1; (_looter distance2D _body < 12) || (isNull _body) || (!alive _looter) || (!alive _target) || (_timeout < time)}; + if ((!alive _looter) || (!alive _target) || (_timeout < time)) then { + if (alive _looter) then {_looter globalChat "Can't get to a body, cancelling loot order"}; + _body setVariable ["OT_looterReserved", false, false]; + break; + }; + if (isNull _body) then { + _looter globalChat "Body has vanished, skipping"; + continue; + }; + + // Looter has reached the body. Transfer its loadout to the looter. + private _lootedLoadout = getUnitLoadout _body; + + // If the looter has his own uniform, keep it. + if (_looterOwnUniform isNotEqualTo "") then { + private _bodyUniformContent = _lootedLoadout # 3 param [1, []]; + _lootedLoadout set [3, [_looterOwnUniform, _bodyUniformContent]]; + }; + + // Also take the dropped weapons belonging to the body. The body may have dropped + // its weapons outside of the search range and they would get deleted when the body + // is deleted, so search for dropped weapons 10m further than bodies. It is still + // possible that the weapon has flown more than 10m outside the range, in which case + // it is lost. This code can be simplified in Arma 3 version 2.18 with the new + // command getCorpseWeaponholders. + // https://community.bistudio.com/wiki/getCorpseWeaponholders + private _droppedWeaponHolders = (_target nearEntities ["WeaponHolderSimulated", (_range + 10)]); + { + if (getCorpse _x isEqualTo _body) then { + private _weapon = weaponsItemsCargo _x # 0; + if (_weapon # 0 isKindOf ["Launcher", configFile >> "CfgWeapons"]) then { + _lootedLoadout set [1, _weapon]; + } else { + _lootedLoadout set [0, _weapon]; + }; + }; + } forEach _droppedWeaponHolders; + + _looter setUnitLoadout _lootedLoadout; + [_body] call OT_fnc_cleanupUnit; + + sleep 2; + _looter doMove ASLtoAGL (getPosASL _target); + + // Wait until looter reaches the target container + _timeout = time + 30; + waitUntil {sleep 1; (_looter distance _target < 12) || (!alive _looter) || (!alive _target) || (_timeout < time)}; + if ((!alive _looter) || (!alive _target) || (_timeout < time)) then { + if (alive _looter) then {_looter globalChat format ["Can't get back to the %1, cancelling loot order", (typeOf _target) call OT_fnc_vehicleGetName]}; + break; + }; + + // Looter has reached the target container. Dump his loadout to it. + if !([_looter, _target] call OT_fnc_canDumpUnitLoadout) then { + _looter globalChat "This vehicle is full, cancelling loot order"; + break; + }; + + [_looter, _target] call OT_fnc_dumpUnitLoadout; + _looter setUnitLoadout [[], [], [], [_looterOwnUniform, []], [], [], "", "", [], ["", "", "", "", "", ""]]; + } else { + // There are no longer any bodies to loot. Loot the nearest item pile. + + private _sortedWeaponHolders = nearestObjects [_target, ["WeaponHolder", "WeaponHolderSimulated"], _range] select {!(_x getVariable ["OT_looterReserved", false])}; + + if (_sortedWeaponHolders isEqualTo []) then { + _looter globalChat "All done!"; + break; + }; + + _looter globalChat format ["%1 item piles to loot", count _sortedWeaponHolders]; + private _weaponHolder = _sortedWeaponHolders # 0; + + _weaponHolder setVariable ["OT_looterReserved", true, false]; + _looter doMove ASLtoAGL (getPosASL _weaponHolder); + [_looter, 1] call OT_fnc_experience; + + // Wait until looter reaches the item pile + _timeout = time + 30; + waitUntil {sleep 1; (_looter distance2D _weaponHolder < 12) || (isNull _weaponHolder) || (!alive _looter) || (!alive _target) || (_timeout < time)}; + if ((!alive _looter) || (!alive _target) || (_timeout < time)) then { + if (alive _looter) then {_looter globalChat "Can't get to an item pile, cancelling loot order"}; + _weaponHolder setVariable ["OT_looterReserved", false, false]; + break; + }; + + // Looter has reached the item pile. Its contents may not fit in the looter's + // inventory, so fake the looting trip by running back empty handed and dumping the + // contents directly to the target container once there. + + sleep 2; + _looter doMove ASLtoAGL (getPosASL _target); + + // Wait until looter reaches the target container + _timeout = time + 30; + waitUntil {sleep 1; (_looter distance _target < 12) || (isNull _weaponHolder) || (!alive _looter) || (!alive _target) || (_timeout < time)}; + if ((!alive _looter) || (!alive _target) || (_timeout < time)) then { + if (alive _looter) then {_looter globalChat format ["Can't get back to the %1, cancelling loot order", (typeOf _target) call OT_fnc_vehicleGetName]}; + _weaponHolder setVariable ["OT_looterReserved", false, false]; + break; + }; + if (isNull _weaponHolder) then { + _looter globalChat "Item pile has vanished, skipping"; + continue; + }; + + // Looter has reached the target container. + if !([_weaponHolder, _target] call OT_fnc_canDumpContainer) then { + _looter globalChat "This vehicle is full, cancelling loot order"; + _weaponHolder setVariable ["OT_looterReserved", false, false]; + break; + }; + + [_weaponHolder, _target] call OT_fnc_dumpContainer; + deleteVehicle _weaponHolder; }; - }foreach(_weapons); - - while {_active} do { - _deadguys = []; - { - if !(_x isKindOf "CAManBase") then {continue}; - if (_x distance _t < 100) then { - _deadguys pushback _x; - }; - } forEach allDeadMen; - - if(count _deadguys isEqualTo 0) exitWith {_unit globalchat "All done!"}; - _unit globalchat format["%1 bodies to loot",count _deadguys]; - _sorted = [_deadguys,[],{_x distance _t},"ASCEND"] call BIS_fnc_SortBy; - - _timeout = time + 30; - _deadguy = _sorted select 0; - _deadguy setVariable ["OT_looted",true,true]; - _deadguy setvariable ["OT_lootedAt",time,true]; - - _unit doMove ASLtoAGL (getPosASL _deadguy); - [_unit,1] call OT_fnc_experience; - - waitUntil {sleep 1; (!alive _unit) || (isNull _t) || (_unit distance2D _deadguy < 12) || (_timeOut < time)}; - if((!alive _unit) || (_timeOut < time)) exitWith {_unit globalchat "Cant get to a body, cancelling loot order"}; - - [_deadguy,_unit] call OT_fnc_takeStuff; - sleep 2; - [_deadguy] call OT_fnc_cleanupUnit; - _timeout = time + 30; - _unit doMove ASLtoAGL (getPosASL _t); - waitUntil {sleep 1; (!alive _unit) || (isNull _t) || (_unit distance _t < 12) || (_timeOut < time)}; - if((!alive _unit) || (_timeOut < time)) exitWith {}; - - if !([_unit,_t] call OT_fnc_dumpStuff) exitWith { - _unit globalchat "This vehicle is full, cancelling loot order"; - _active = false; - }; - - sleep 1; - }; - - _unit setVariable ["NOAI",true,true]; - if(_wasincar) then { - _unit assignAsCargo _car; - [_unit] orderGetIn true; - }; - }; -}foreach(_myunits); + + sleep 1; + }; + }; +} forEach (_selectedUnits); diff --git a/addons/overthrow_main/functions/actions/fn_recover.sqf b/addons/overthrow_main/functions/actions/fn_recover.sqf index 553857b0..02a13ada 100644 --- a/addons/overthrow_main/functions/actions/fn_recover.sqf +++ b/addons/overthrow_main/functions/actions/fn_recover.sqf @@ -1,63 +1,72 @@ params ["_user"]; private _range = 150; +private _time = 15; -private _veh = vehicle _user; -if(_veh == _user) exitWith {}; -if((driver _veh) != _user) exitWith {"Loot must be initiated by the driver of this vehicle" call OT_fnc_notifyMinor}; -if((typeof _veh) != "OT_I_Truck_recovery") exitWith {"This command is only available when using a Recovery truck" call OT_fnc_notifyMinor}; +private _veh = objectParent _user; +if (_veh isEqualTo _user) exitWith {}; +if ((driver _veh) isNotEqualTo _user) exitWith { + "Loot must be initiated by the driver of this vehicle" call OT_fnc_notifyMinor; +}; +if !(_veh isKindOf "OT_I_Truck_recovery") exitWith { + "This command is only available when using a Recovery truck" call OT_fnc_notifyMinor; +}; -if(isPlayer _user) then { +if (isPlayer _user) then { _veh enableSimulation false; - [_veh] spawn { - sleep 20; - (_this select 0) enableSimulation true; + [_time, _veh] spawn { + params ["_time", "_veh"]; + sleep (_time + 5); + _veh enableSimulation true; //Fail safe for user input disabled. }; - format["Looting all bodies within %1m",_range] call OT_fnc_notifyMinor; - [5,false] call OT_fnc_progressBar; -}else { - _user globalchat format["Looting bodies within %1m using Recovery vehicle",_range]; + format ["Looting all bodies and item piles within %1m", _range] call OT_fnc_notifyMinor; + [_time, false] call OT_fnc_progressBar; +} else { + _user globalchat format["Looting bodies and item piles within %1m using Recovery vehicle", _range]; }; -private _end = time + 15; -waitUntil {time > _end}; +sleep _time; -//Get the loose weapons -private _count_weapons = 0; -private _weapons = (_veh nearObjects ["WeaponHolder", _range]) + (_veh nearEntities ["WeaponHolderSimulated", _range]); +// Get loose weapons and items +private _countWeaponHolders = 0; +// WeaponHolderSimulated = dropped weapons from bodies. Bodies inside the range may drop their +// weapons outside of it and they would get deleted when the body is looted, so loot dropped weapons +// 10m further than bodies. It is still possible that the weapon has flown more than 10m outside the +// range, in which case it is lost. +private _weaponHolders = (_veh nearObjects ["WeaponHolder", _range]) + (_veh nearEntities ["WeaponHolderSimulated", (_range + 10)]); { - _weapon = _x; - _s = (weaponsItems _weapon) select 0; - if(!isNil {_s}) then { - _cls = (_s select 0); - _i = _s select 1; - if(_i != "") then {_veh addItemCargoGlobal [_i,1]}; - _i = _s select 2; - if(_i != "") then {_veh addItemCargoGlobal [_i,1]}; - _i = _s select 3; - if(_i != "") then {_veh addItemCargoGlobal [_i,1]}; - - _veh addWeaponCargoGlobal [_cls call BIS_fnc_baseWeapon,1]; - deleteVehicle _weapon; - _count_weapons = _count_weapons + 1; - }; -}foreach(_weapons); + private _weaponHolder = _x; + + // Weapon holder may be any pile of stuff on the ground, not just weapons dropped from corpses, + // so we have to treat it as a generic container that might contain any items. + [_weaponHolder, _veh] call OT_fnc_dumpContainer; -//Get the bodies -private _count_bodies = 0; + deleteVehicle _weaponHolder; + _countWeaponHolders = _countWeaponHolders + 1; +} foreach _weaponHolders; + +// Get the bodies. This code can be simplified in Arma 3 version 2.18 with the new syntax 3 of +// nearEntities. https://community.bistudio.com/wiki/nearEntities +private _countBodies = 0; { - if !(_x isKindOf "CAManBase") then {continue}; - if (_x distance _veh < _range) then { - [_x, _veh] call OT_fnc_dumpStuff; - _count_bodies = _count_bodies + 1; - [_x] call OT_fnc_cleanupUnit; - }; -} foreach allDeadMen; + // Some bodies are inside vehicles, so we search through the crew of every vehicle we find. + // Luckily every man is crew of itself so the same code also works for bodies on the ground. + private _vehicleOrMan = _x; + { + private _body = _x; + if (!alive _body) then { + [_body, _veh] call OT_fnc_dumpUnitLoadout; + + [_body] call OT_fnc_cleanupUnit; + _countBodies = _countBodies + 1; + }; + } forEach crew _vehicleOrMan; +} forEach (_veh nearObjects ["AllVehicles", _range]); -if(isPlayer _user) then { +if (isPlayer _user) then { _veh enableSimulation true; - format["Looted %1 weapons and %2 bodies into this truck",_count_weapons,_count_bodies] call OT_fnc_notifyMinor; -}else { - _user globalchat format["All done! Looted %1 weapons and %2 bodies",_count_weapons,_count_bodies]; + format["Looted %1 item piles and %2 bodies into this truck", _countWeaponHolders, _countBodies] call OT_fnc_notifyMinor; +} else { + _user globalchat format["All done! Looted %1 item piles and %2 bodies", _countWeaponHolders, _countBodies]; }; diff --git a/addons/overthrow_main/functions/fn_initOverthrow.sqf b/addons/overthrow_main/functions/fn_initOverthrow.sqf index 30a7bfc5..1a9eae65 100644 --- a/addons/overthrow_main/functions/fn_initOverthrow.sqf +++ b/addons/overthrow_main/functions/fn_initOverthrow.sqf @@ -173,14 +173,6 @@ OT_tpl_checkpoint = [] call compileScript ["data\templates\NATOcheckpoint.sqf", ["OT_civilian_cleanup_crew", "time > OT_cleanup_civilian_loop"," OT_cleanup_civilian_loop = time + (5*60); private _totalcivs = {!captive _x} count (units civilian); - { - if(_x getVariable ['OT_Looted',false]) then { - private _stock = _x call OT_fnc_unitStock; - if((count _stock) isEqualTo 0) then { - [_x] call OT_fnc_cleanupUnit; - }; - }; - }forEach(alldeadmen); if(_totalcivs < 50) exitWith {}; { if (!(isPlayer _x) && !(_x getVariable ['shopcheck',false]) && { ({side _x isEqualTo civilian} count (_x nearEntities ['CAManBase',150])) > round(150*OT_spawnCivPercentage) } ) then { diff --git a/addons/overthrow_main/functions/inventory/fn_canDumpContainer.sqf b/addons/overthrow_main/functions/inventory/fn_canDumpContainer.sqf new file mode 100644 index 00000000..654cf72b --- /dev/null +++ b/addons/overthrow_main/functions/inventory/fn_canDumpContainer.sqf @@ -0,0 +1,41 @@ +/* + Description: + Checks if the contents of the origin container fit into the target container. + + Parameters: + _origin: OBJECT - Origin container or vehicle which has the content to add + _target: OBJECT - Target container or vehicle where the content is to be added + + Usage: + if !([_weaponHolder, _target] call OT_fnc_canDumpContainer) then { + _looter globalChat "Target vehicle is full, cancelling loot order"; + }; + + Returns: BOOL - True if the contents of the origin container fit into the target container +*/ + +params ["_origin", "_target"]; + +// If target is truck or ammobox, it can always be overloaded +if (_target isKindOf "Truck_F" || _target isKindOf "ReammoBox_F") exitWith {true}; + +// Load of the target container +private _targetLoad = loadAbs _target; +// Hack: Workaround for BIS bug where masses are double counted for items inside subcontainers. +// https://feedback.bistudio.com/T167469 +// The workaround is to simply subtract the load of every subcontainer from main container load. +// Taken from ACE +// https://github.com/acemod/ACE3/blob/71afce53c1bde666369344652a30a71ec8ad751a/addons/dragging/functions/fnc_getWeight.sqf +{ + _targetLoad = _targetLoad - loadAbs (_x # 1); +} forEach (everyContainer _target); + +// Load of the origin container +private _originLoad = loadAbs _origin; +// Same workaround here +{ + _originLoad = _originLoad - loadAbs (_x # 1); +} forEach (everyContainer _origin); + +// Return if the content of the origin container would fit in the target container +_targetLoad + _originLoad <= maxLoad _target diff --git a/addons/overthrow_main/functions/inventory/fn_canDumpUnitLoadout.sqf b/addons/overthrow_main/functions/inventory/fn_canDumpUnitLoadout.sqf new file mode 100644 index 00000000..e6dd72a1 --- /dev/null +++ b/addons/overthrow_main/functions/inventory/fn_canDumpUnitLoadout.sqf @@ -0,0 +1,37 @@ +/* + Description: + Checks if the loadout of the unit fits into the target container, excluding the unit's uniform. + + Parameters: + _unit: OBJECT - Unit which has the loadout to add + _target: OBJECT - Target container or vehicle where the content is to be added + + Usage: + if !([_looter, _target] call OT_fnc_canDumpUnitLoadout) then { + _looter globalChat "Target vehicle is full, cancelling loot order"; + }; + + Returns: BOOL - True if the loadout fits into the target container +*/ + +params ["_unit", "_target"]; + +// If target is truck or ammobox, it can always be overloaded +if (_target isKindOf "Truck_F" || _target isKindOf "ReammoBox_F") exitWith {true}; + +// Load of the target container +private _targetLoad = loadAbs _target; +// Hack: Workaround for BIS bug where masses are double counted for items inside subcontainers. +// https://feedback.bistudio.com/T167469 +// The workaround is to simply subtract the load of every subcontainer from main container load. +// Taken from ACE +// https://github.com/acemod/ACE3/blob/71afce53c1bde666369344652a30a71ec8ad751a/addons/dragging/functions/fnc_getWeight.sqf +{ + _targetLoad = _targetLoad - loadAbs (_x # 1); +} forEach (everyContainer _target); + +// Mass of the unit's loadout, excluding uniform which is not going to be dumped +private _unitDumpableLoad = loadAbs _unit - getNumber (configFile >> "CfgWeapons" >> (uniform _unit) >> "ItemInfo" >> "mass"); + +// Return if unit's loadout would fit in the target container +_targetLoad + _unitDumpableLoad <= maxLoad _target diff --git a/addons/overthrow_main/functions/inventory/fn_dumpContainer.sqf b/addons/overthrow_main/functions/inventory/fn_dumpContainer.sqf new file mode 100644 index 00000000..cc7fe11c --- /dev/null +++ b/addons/overthrow_main/functions/inventory/fn_dumpContainer.sqf @@ -0,0 +1,59 @@ +/* + Description: + Adds the entire content of the origin container to the target container, detaching every + attachment and magazine from weapons, unloading all contents from subcontainers (backpacks, + vests, uniforms) and replacing all vanilla medical items with ACE ones. The origin container is + not emptied, so after execution the items can be found in both containers. + + Parameters: + _origin: OBJECT - Origin container or vehicle which has the content to add + _target: OBJECT - Target container or vehicle where the content is added + + Usage: + [vehicle player, _ammoBox] call OT_fnc_dumpContainer; + + Returns: Nothing +*/ + +params ["_origin", "_target"]; + +// Vehicle inventory management in Arma is so full of weird edge cases, such as 4 separate item +// types with separate commands, containers inside containers, backpacks being vehicles instead of +// weapons, weapons having default attachments etc. This monster of a code is required simply to +// transfer everything from one container to another. + +// Transfer weapons and their attachments and magazines separately +{ + // Binocular and disposable launcher magazines cannot be changed in game, so keep them attached. + // For other weapons, detach all attachments and magazines. + if (_x # 0 isKindOf ["Binocular", configFile >> "CfgWeapons"] || isArray (configFile >> "CBA_DisposableLaunchers" >> _x # 0)) then { + _target addWeaponWithAttachmentsCargoGlobal [_x, 1]; + } else { + [_x, 1, _target] call OT_fnc_dumpWeapon; + }; +} forEach (weaponsItemsCargo _origin); + +// Transfer magazines with correct ammo counts +{ + _target addMagazineAmmoCargo [(_x # 0), 1, (_x # 1)]; +} forEach (magazinesAmmoCargo _origin); + +// Transfer backpacks as empty +(getBackpackCargo _origin) params ["_backpacks", "_amounts"]; +{ + // Many backpack classes have some default items in their inventory. Call BIS_fnc_basicBackpack + // to find the corresponding backpack class with no items. + _target addBackpackCargoGlobal [(_x call BIS_fnc_basicBackpack), (_amounts # _forEachIndex)]; +} forEach (_backpacks); + +// Transfer other items, including uniforms and vests as empty +(getItemCargo _origin) params ["_items", "_amounts"]; +{ + [_x, (_amounts # _forEachIndex), _target] call OT_fnc_dumpItem; +} forEach (_items); + +// Transfer subcontainers' (uniforms, vests, backpacks) contents +{ + // Call this function recursively on the subcontainer object + [_x # 1, _target] call OT_fnc_dumpContainer; +} forEach (everyContainer _origin); diff --git a/addons/overthrow_main/functions/inventory/fn_dumpItem.sqf b/addons/overthrow_main/functions/inventory/fn_dumpItem.sqf new file mode 100644 index 00000000..26e8837b --- /dev/null +++ b/addons/overthrow_main/functions/inventory/fn_dumpItem.sqf @@ -0,0 +1,60 @@ +/* + Description: + Adds an item to the target container and does ACE item replacements on it, such as replacing + first aid kits with ACE medical items. This is basically just a fancy addItemCargoGlobal + command. + + Parameters: + _item: STRING - Class name of the item to add, it cannot be a backpack + _amount: NUMBER - How many items to add + _target: OBJECT - Target container or vehicle where the item is added + + Usage: + ["FirstAidKit", 2, _ammoBox] call OT_fnc_dumpItem; + + Returns: Nothing +*/ + +params ["_item", "_amount", "_target"]; + +private _itemType = format ["$%1", getNumber (configFile >> "CfgWeapons" >> _item >> "ItemInfo" >> "type")]; + +// Replace vanilla medical items with corresponding ACE ones. Hack: ACE does not have a stable +// function for finding replacement items so using a semi-stable internal ACE variable +// ACE_common_itemReplacements to find them. For performance reasons we only support direct and type +// replacements, not inherited replacements as they might be slow and ACE does not currently use +// them. Related ACE code here: +// https://github.com/acemod/ACE3/blob/5c8ea65f7cd0a290e7ff6f8d0c44347617e77955/addons/medical_treatment/CfgReplacementItems.hpp +// https://github.com/acemod/ACE3/blob/5c8ea65f7cd0a290e7ff6f8d0c44347617e77955/addons/common/functions/fnc_replaceRegisteredItems.sqf +// +// ACE variables are being converted from CBA namespaces to hashmaps in a future ACE version, so +// right now we have to support both types. +// https://github.com/acemod/ACE3/commit/59af3e1f6d66ee08a1f8e4fd847efd45bb9ef73e#diff-3962a6b36168378fa5277c4012de0b4510de1122deb8afe38064a6cb574a29cfR25 +// In the future when that commit has been released, this code can be simplified. +private "_directReplacements"; +private "_typeReplacements"; +if (ACE_common_itemReplacements isEqualType locationNull) then { + // ACE_common_itemReplacements is a CBA namespace + _directReplacements = ACE_common_itemReplacements getVariable _item; + _typeReplacements = ACE_common_itemReplacements getVariable _itemType; +} else { + // ACE_common_itemReplacements is a hashmap + _directReplacements = ACE_common_itemReplacements get _item; + _typeReplacements = ACE_common_itemReplacements get _itemType; +}; + +// If replacements were found, add them. If not, add the item as it is. +if (!isNil "_directReplacements" || !isNil "_typeReplacements") then { + if (!isNil "_directReplacements") then { + { + _target addItemCargoGlobal [_x, _amount]; + } forEach (_directReplacements); + }; + if (!isNil "_typeReplacements") then { + { + _target addItemCargoGlobal [_x, _amount]; + } forEach (_typeReplacements); + }; +} else { + _target addItemCargoGlobal [_item, _amount]; +}; diff --git a/addons/overthrow_main/functions/inventory/fn_dumpUnitLoadout.sqf b/addons/overthrow_main/functions/inventory/fn_dumpUnitLoadout.sqf new file mode 100644 index 00000000..22d40b63 --- /dev/null +++ b/addons/overthrow_main/functions/inventory/fn_dumpUnitLoadout.sqf @@ -0,0 +1,125 @@ +/* + Description: + Adds the entire loadout of the unit to the target container, detaching every attachment and + magazine from weapons, unloading all contents from backpack, vest and uniform and replacing all + vanilla medical items with ACE ones, except the unit's uniform is not added. The unit's loadout + is not cleared, so after execution the items can be found both in the container and the unit. + + Parameters: + _unit: OBJECT - Unit which has the loadout to add + _target: OBJECT - Target container or vehicle where the content is added + + Usage: + [player, _ammoBox] call OT_fnc_dumpUnitLoadout; + + Returns: Nothing +*/ + +params ["_unit", "_target"]; + +// Helper function for dumping uniform, vest and backpack contents in the format given by +// getUnitLoadout +private _fnc_dumpLoadoutContainer = { + params ["_content", "_target"]; + + { + if (count _x isEqualTo 3) then { + // Magazine in format ["class", amount, ammo] + _target addMagazineAmmoCargo _x; + } else { + if (_x # 0 isEqualType []) then { + // Weapon in format [[weaponItems], amount] + // Binocular and disposable launcher magazines cannot be changed in game, so keep + // them attached. For other weapons, detach all attachments and magazines. + if ((_x # 0 # 0) isKindOf ["Binocular", configFile >> "CfgWeapons"] || isArray (configFile >> "CBA_DisposableLaunchers" >> (_x # 0 # 0))) then { + _target addWeaponWithAttachmentsCargoGlobal _x; + } else { + [(_x # 0), (_x # 1), _target] call OT_fnc_dumpWeapon; + }; + } else { + if (_x # 1 isEqualType 0) then { + // Item in format ["class", amount] + [(_x # 0), (_x # 1), _target] call OT_fnc_dumpItem; + } else { + // Subcontainer in format ["class", isBackpack] + // Subcontainers are not allowed to contain items (e.g. items inside a backpack + // inside soldier's backpack) so we don't need to check its contents. + if (_x # 1) then { + // Many backpack classes have some default items in their inventory. Call + // BIS_fnc_basicBackpack to find the corresponding backpack class with no + // items. + _target addBackpackCargoGlobal [((_x # 0) call BIS_fnc_basicBackpack), 1]; + } else { + _target addItemCargoGlobal [(_x # 0), 1]; + }; + }; + }; + }; + } forEach (_content); +}; + +private _loadout = getUnitLoadout _unit; + +private _primaryWeapon = _loadout # 0; +if (_primaryWeapon isNotEqualTo []) then { + [_primaryWeapon, 1, _target] call OT_fnc_dumpWeapon; +}; + +private _secondaryWeapon = _loadout # 1; +if (_secondaryWeapon isNotEqualTo []) then { + // Disposable launcher magazines cannot be changed in game, so keep them attached. For other + // launchers, detach all attachments and magazines. + if (isArray (configFile >> "CBA_DisposableLaunchers" >> _secondaryWeapon # 0)) then { + _target addWeaponWithAttachmentsCargoGlobal [_secondaryWeapon, 1]; + } else { + [_secondaryWeapon, 1, _target] call OT_fnc_dumpWeapon; + }; +}; + +private _handWeapon = _loadout # 2; +if (_handWeapon isNotEqualTo []) then { + [_handWeapon, 1, _target] call OT_fnc_dumpWeapon; +}; + +private _uniform = _loadout # 3; +if (_uniform isNotEqualTo []) then { + // Do not add the uniform itself + [_uniform # 1, _target] call _fnc_dumpLoadoutContainer; +}; + +private _vest = _loadout # 4; +if (_vest isNotEqualTo []) then { + _target addItemCargoGlobal [(_vest # 0), 1]; + [_vest # 1, _target] call _fnc_dumpLoadoutContainer; +}; + +private _backpack = _loadout # 5; +if (_backpack isNotEqualTo []) then { + // Many backpack classes have some default items in their inventory. Call BIS_fnc_basicBackpack + // to find the corresponding backpack class with no items. + _target addBackpackCargoGlobal [((_backpack # 0) call BIS_fnc_basicBackpack), 1]; + [_backpack # 1, _target] call _fnc_dumpLoadoutContainer; +}; + +private _headgear = _loadout # 6; +if (_headgear isNotEqualTo "") then { + _target addItemCargoGlobal [_headgear, 1]; +}; + +private _goggles = _loadout # 7; +if (_goggles isNotEqualTo "") then { + _target addItemCargoGlobal [_goggles, 1]; +}; + +private _binocular = _loadout # 8; +if (_binocular isNotEqualTo []) then { + // Binocular magazines cannot be changed in game, so keep them attached. + _target addWeaponWithAttachmentsCargoGlobal [_binocular, 1]; +}; + +private _assignedItems = _loadout # 9; +{ + if (_x isNotEqualTo "") then { + _target addItemCargoGlobal [_x, 1]; + }; +} forEach (_assignedItems); \ No newline at end of file diff --git a/addons/overthrow_main/functions/inventory/fn_dumpWeapon.sqf b/addons/overthrow_main/functions/inventory/fn_dumpWeapon.sqf new file mode 100644 index 00000000..4d901883 --- /dev/null +++ b/addons/overthrow_main/functions/inventory/fn_dumpWeapon.sqf @@ -0,0 +1,35 @@ +/* + Description: + Adds a weapon to the target container, with all of its attachments and magazines detached and + added separately. + + Parameters: + _weaponItems: ARRAY - Array of weapon items in the weaponsItems format: https://community.bistudio.com/wiki/weaponsItems + _amount: NUMBER - How many copies of weapon items to add + _target: OBJECT - Target container or vehicle where the weapon items are added + + Usage: + [["hgun_P07_F", "muzzle_snds_L", "", "", ["16Rnd_9x21_Mag", 11], [], ""], 2, _ammoBox] call OT_fnc_dumpWeapon; + + Returns: Nothing +*/ + +params ["_weaponItems", "_amount", "_target"]; + +// Many weapon classes have some default attachments attached to them. Call BIS_fnc_baseWeapon to +// try to find the corresponding weapon class with least attachments. Note: some base weapons such +// as arifle_MX_SW_F do still have attachments, so we must explicitly set its attachments to none +// anyway. +_target addWeaponWithAttachmentsCargoGlobal [[(_weaponItems # 0 call BIS_fnc_baseWeapon), "", "", "", [], [], ""], _amount]; +// suppressor +if (_weaponItems # 1 isNotEqualTo "") then {_target addItemCargoGlobal [(_weaponItems # 1), _amount]}; +// pointer +if (_weaponItems # 2 isNotEqualTo "") then {_target addItemCargoGlobal [(_weaponItems # 2), _amount]}; +// optics +if (_weaponItems # 3 isNotEqualTo "") then {_target addItemCargoGlobal [(_weaponItems # 3), _amount]}; +// primary mag +if (_weaponItems # 4 isNotEqualTo []) then {_target addMagazineAmmoCargo [(_weaponItems # 4 # 0), _amount, (_weaponItems # 4 # 1)]}; +// secondary mag +if (_weaponItems # 5 isNotEqualTo []) then {_target addMagazineAmmoCargo [(_weaponItems # 5 # 0), _amount, (_weaponItems # 5 # 1)]}; +// bipod +if (_weaponItems # 6 isNotEqualTo "") then {_target addItemCargoGlobal [(_weaponItems # 6), _amount]}; diff --git a/addons/overthrow_main/functions/player/fn_wantedSystem.sqf b/addons/overthrow_main/functions/player/fn_wantedSystem.sqf index 5ba7ed3b..9b4a84c4 100644 --- a/addons/overthrow_main/functions/player/fn_wantedSystem.sqf +++ b/addons/overthrow_main/functions/player/fn_wantedSystem.sqf @@ -10,8 +10,6 @@ _unit addEventHandler ["Take", { if (captive _me) then { //Looting dead bodies is illegal if(!alive _container && {typeof _container isKindOf ["CAManBase",configFile>>"CfgVehicles"]}) then { - _container setvariable ["OT_looted",true,true]; - _container setvariable ["OT_lootedAt",time,true]; if (!(_container call OT_fnc_hasOwner) && (_me call OT_fnc_unitSeen)) then { _me setCaptive false; [_me] call OT_fnc_revealToNATO; diff --git a/addons/overthrow_main/script_mod.hpp b/addons/overthrow_main/script_mod.hpp index dced72b3..599cdce9 100644 --- a/addons/overthrow_main/script_mod.hpp +++ b/addons/overthrow_main/script_mod.hpp @@ -37,7 +37,7 @@ #define VERSION_AR MAJOR,MINOR,PATCHLVL,BUILD // MINIMAL required ARMA version for the addon -#define REQUIRED_VERSION 1.70 +#define REQUIRED_VERSION 2.12 // MINIMAL required CBA_A3 version for the addon #define REQUIRED_CBA_VERSION {3,3,0}