From 04305f751f878bf08d62d86af5ee6f847e46071f Mon Sep 17 00:00:00 2001 From: Dystopian Date: Sun, 19 Apr 2026 17:36:09 +0300 Subject: [PATCH 01/10] Add loading of dead units into vehicles --- addons/common/XEH_PREP.hpp | 1 + addons/common/XEH_postInit.sqf | 17 +++ .../common/functions/fnc_loadDeadPerson.sqf | 134 ++++++++++++++++++ addons/common/functions/fnc_loadPerson.sqf | 8 +- .../functions/fnc_nearestVehiclesFreeSeat.sqf | 29 ++-- .../dragging/functions/fnc_carryObjectPFH.sqf | 3 +- .../functions/fnc_dropObject_carry.sqf | 12 +- addons/dragging/script_component.hpp | 2 +- .../functions/fnc_loadUnit.sqf | 6 +- addons/medical_treatment/stringtable.xml | 17 --- 10 files changed, 191 insertions(+), 38 deletions(-) create mode 100644 addons/common/functions/fnc_loadDeadPerson.sqf diff --git a/addons/common/XEH_PREP.hpp b/addons/common/XEH_PREP.hpp index db068cf1375..0bdd72bfbb0 100644 --- a/addons/common/XEH_PREP.hpp +++ b/addons/common/XEH_PREP.hpp @@ -135,6 +135,7 @@ PREP(isModLoaded); PREP(isPlayer); PREP(isSwimming); PREP(lightIntensityFromObject); +PREP(loadDeadPerson); PREP(loadPerson); PREP(loadPersonLocal); PREP(moduleCheckPBOs); diff --git a/addons/common/XEH_postInit.sqf b/addons/common/XEH_postInit.sqf index 3df0ba4a9bf..c2450ead862 100644 --- a/addons/common/XEH_postInit.sqf +++ b/addons/common/XEH_postInit.sqf @@ -194,6 +194,23 @@ if (isServer) then { ["ace_loadPersonEvent", LINKFUNC(loadPersonLocal)] call CBA_fnc_addEventHandler; ["ace_unloadPersonEvent", LINKFUNC(unloadPersonLocal)] call CBA_fnc_addEventHandler; +[QGVAR(loadDeadPerson), LINKFUNC(loadDeadPerson)] call CBA_fnc_addEventHandler; +[QGVAR(deadPersonLoaded), { + params ["_unit"]; + TRACE_5("deadPersonLoaded event",_unit,isAwake _unit,local _unit,typeOf objectParent _unit,local objectParent _unit); + if (local _unit) exitWith {}; // no awake problems with local unit + if (isAwake _unit) then {_unit awake false}; + // Unit may be set to awake multiple times after moveIn; keep it in non-awake state + private _pfeh = [{ + params ["_unit"]; + if (isAwake _unit) then { + TRACE_5("deadPersonLoaded pfh",_unit,isAwake _unit,local _unit,typeOf objectParent _unit,local objectParent _unit); + _unit awake false; + }; + }, 0, _unit] call CBA_fnc_addPerFrameHandler; + [{_this call CBA_fnc_removePerFrameHandler}, _pfeh, 2] call CBA_fnc_waitAndExecute; +}] call CBA_fnc_addEventHandler; + [QGVAR(lockVehicle), { _this setVariable [QGVAR(lockStatus), locked _this]; _this lock 2; diff --git a/addons/common/functions/fnc_loadDeadPerson.sqf b/addons/common/functions/fnc_loadDeadPerson.sqf new file mode 100644 index 00000000000..ec61118e9f1 --- /dev/null +++ b/addons/common/functions/fnc_loadDeadPerson.sqf @@ -0,0 +1,134 @@ +#include "..\script_component.hpp" +/* + * Author: Dystopian + * Loads dead person into most suitable seat in vehicle. + * + * Arguments: + * 0: Unit + * 1: Vehicle + * + * Return Value: + * None + * + * Example: + * [cursorObject, vehicle player] call ace_common_fnc_loadDeadPerson + * + * Public: No + */ + +params ["_unit", "_vehicle"]; +TRACE_5("loadDeadPerson",_unit,_vehicle,typeOf _vehicle,local _unit,local _vehicle); + +if (!local _vehicle) exitWith {ERROR_4("loadDeadPerson vehicle not local _unit=%1[%2] _vehicle=%3[%4]",_unit,typeOf _unit,_vehicle,typeOf _vehicle)}; + +#define PRIORITY_NONE -1 +#define PRIORITY_DRIVER 0 +#define PRIORITY_GUNNER 1 +#define PRIORITY_COMMANDER 2 +#define PRIORITY_TURRET_NO_FFV 3 +#define PRIORITY_TURRET_FFV 4 +#define PRIORITY_TURRET_EMPTY 5 +#define PRIORITY_CARGO 6 + +#define SEATHOLDER_CLASS "VirtualMan_F" + +private _vehicleConfig = configOf _vehicle; +private _emptySeats = fullCrew [_vehicle, "", true] select {isNull (_x select 0)}; + +private _bestSeatsPriority = PRIORITY_NONE; +private _bestSeatsRole = ""; +private _bestSeatsParams = []; + +// Determine highest-priority available seats +{ + _x params ["", "_role", "_cargoIndex", "_turretPath", "_isPersonTurret"]; + private _priority = PRIORITY_NONE; + switch (_role) do { + case "driver": { + if ( + lockedDriver _vehicle + || {unitIsUAV _vehicle} + || {getNumber (_vehicleConfig >> "hasDriver") < 1} + || {getNumber (_vehicleConfig >> "ejectDeadDriver") > 0} + ) then {continue}; + _priority = PRIORITY_DRIVER; + }; + case "cargo": { + if ( + _vehicle lockedCargo _cargoIndex + || {getNumber (_vehicleConfig >> "ejectDeadCargo") > 0} + ) then {continue}; + _priority = PRIORITY_CARGO; + }; + default { + private _turretConfig = [_vehicleConfig, _turretPath] call CBA_fnc_getTurret; + if ( + _vehicle lockedTurret _turretPath + || {getNumber (_turretConfig >> "ejectDeadGunner") > 0} + || {_role == "gunner" && {unitIsUAV _vehicle}} + ) then {continue}; + _priority = switch (_role) do { + case "gunner": {PRIORITY_GUNNER}; + case "commander": {PRIORITY_COMMANDER}; + case "turret": { + if (_isPersonTurret) then {PRIORITY_TURRET_FFV} + else {[PRIORITY_TURRET_NO_FFV, PRIORITY_TURRET_EMPTY] select (getText (_turretConfig >> "gun") == "")} + }; + }; + }; + }; + TRACE_2("emptySeat",_x,_priority); + if (_priority > _bestSeatsPriority) then { + _bestSeatsPriority = _priority; + _bestSeatsRole = _role; + _bestSeatsParams = [[_cargoIndex, _turretPath]]; + continue; + }; + if (_priority == _bestSeatsPriority) then { + _bestSeatsParams pushBack [_cargoIndex, _turretPath]; + }; +} forEach _emptySeats; + +if (_bestSeatsPriority == PRIORITY_NONE) exitWith { + TRACE_2("No seats found",_emptySeats,fullCrew _vehicle); +}; + +TRACE_3("emptySeats",_bestSeatsPriority,_bestSeatsRole,_bestSeatsParams); + +// Probe seat positions using temporary units to identify exact seat +private _seatHolders = []; +private _remainingEmptyPositions = _vehicle emptyPositions ""; // Guard against infinite loop if moveInAny fails with true +while {_remainingEmptyPositions > 0} do { + private _seatHolder = createVehicleLocal [SEATHOLDER_CLASS, [0,0,0], [], 0, "CAN_COLLIDE"]; + private _seatHolderMoveSuccess = _seatHolder moveInAny _vehicle; + private _seatHolderSeatParams = fullCrew _vehicle select {_x select 0 == _seatHolder}; + if (!_seatHolderMoveSuccess || {_seatHolderSeatParams isEqualTo []}) then { + ERROR_8("moveInAny holder failed _unit=%1[%2] _vehicle=%3[%4] _seatHolder=%5 _seatHolderMoveSuccess=%6 fullCrew=%7 %8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_seatHolder,_seatHolderMoveSuccess,fullCrew _vehicle,__FILE__); + _vehicle deleteVehicleCrew _seatHolder; + if (!isNull _seatHolder) then { // failsafe + moveOut _seatHolder; + deleteVehicle _seatHolder; + }; + break; + }; + _seatHolderSeatParams select 0 params ["", "_role", "_cargoIndex", "_turretPath"]; + if (_role == _bestSeatsRole && {[_cargoIndex, _turretPath] in _bestSeatsParams}) then { + _vehicle deleteVehicleCrew _seatHolder; + private _unitMoveSuccess = _unit moveInAny _vehicle; + if (_unitMoveSuccess) then { + [QGVAR(deadPersonLoaded), _unit] call CBA_fnc_globalEvent; // Ensure remote dead unit stays unconscious + } else { + ERROR_8("moveInAny unit failed _unit=%1[%2] _vehicle=%3[%4] _seatHolder=%5 _seatHolderSeatParams=%6 fullCrew=%7 %8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_seatHolder,_seatHolderSeatParams,fullCrew _vehicle,__FILE__); + }; + TRACE_4("seat",_remainingEmptyPositions,_role,_cargoIndex,_turretPath); + break; + }; + TRACE_4("seatHolder",_remainingEmptyPositions,_role,_cargoIndex,_turretPath); + _seatHolders pushBack _seatHolder; + DEC(_remainingEmptyPositions); +}; + +// Cleanup all temporary seat holders +{ + _vehicle deleteVehicleCrew _x; +} forEach _seatHolders; diff --git a/addons/common/functions/fnc_loadPerson.sqf b/addons/common/functions/fnc_loadPerson.sqf index 5271f5d9020..6ad2f828457 100644 --- a/addons/common/functions/fnc_loadPerson.sqf +++ b/addons/common/functions/fnc_loadPerson.sqf @@ -31,7 +31,9 @@ if (isNull _vehicle) then { _vehicle = ([_unit] call FUNC(nearestVehiclesFreeSeat)) param [0, objNull]; }; -if (!isNull _vehicle) then { +if (isNull _vehicle) exitWith {objNull}; + +if (alive _unit) then { switch (true) do { case ((crew _vehicle isEqualTo []) && {side group _caller != side group _unit}): { [_unit, true, GROUP_SWITCH_ID, side group _caller] call FUNC(switchToGroupSide); @@ -43,6 +45,10 @@ if (!isNull _vehicle) then { TRACE_5("sending ace_loadPersonEvent",_unit,_vehicle,_caller,_preferredSeats,_reverseFill); ["ace_loadPersonEvent", [_unit, _vehicle, _caller, _preferredSeats, _reverseFill], _unit] call CBA_fnc_targetEvent; +} else { + // vehicle must be local + TRACE_2("sending loadDeadPerson event",_unit,_vehicle); + [QGVAR(loadDeadPerson), [_unit, _vehicle], _vehicle] call CBA_fnc_targetEvent; }; _vehicle diff --git a/addons/common/functions/fnc_nearestVehiclesFreeSeat.sqf b/addons/common/functions/fnc_nearestVehiclesFreeSeat.sqf index 5561810ae40..3d44026fd70 100644 --- a/addons/common/functions/fnc_nearestVehiclesFreeSeat.sqf +++ b/addons/common/functions/fnc_nearestVehiclesFreeSeat.sqf @@ -21,13 +21,24 @@ params ["_unit", ["_distance", 10], ["_cargoOnly", false]]; private _nearVehicles = nearestObjects [_unit, ["Car", "Air", "Tank", "Ship_F", "Pod_Heli_Transport_04_crewed_base_F"], _distance]; _nearVehicles select { - // Filter cargo seats that will eject unconscious units (e.g. quad bike) - private _canSitInCargo = (_unit call EFUNC(common,isAwake)) || {(getNumber (configOf _x >> "ejectDeadCargo")) == 0}; - ((fullCrew [_x, "", true]) findIf { - _x params ["_body", "_role", "_cargoIndex"]; - (isNull _body) // seat empty - && {_role != "DRIVER"} // not driver seat - && {_canSitInCargo || {_cargoIndex == -1}} // won't be ejected (uncon) - && {(!_cargoOnly) || {_cargoIndex != -1}} // not restricted (captive) - }) > -1 + private _vehicle = _x; + alive _vehicle + && {locked _vehicle < 2} + && {simulationEnabled _vehicle} + && {vectorUp _vehicle select 2 > 0.3} // flipped vehicles + && { + // Filter cargo seats that will eject unconscious units (e.g. quad bike) + private _canSitInCargo = (_unit call EFUNC(common,isAwake)) || {(getNumber (configOf _vehicle >> "ejectDeadCargo")) == 0}; + ((fullCrew [_vehicle, "", true]) findIf { + _x params ["_body", "_role", "_cargoIndex"]; + (isNull _body) // seat empty + && { + _role != "DRIVER" // not driver seat + || {!alive _unit} // dead unit in medical + || {_unit isKindOf QEGVAR(dragging,clone)} // dead unit in dragging + } + && {_canSitInCargo || {_cargoIndex == -1}} // won't be ejected (uncon) + && {(!_cargoOnly) || {_cargoIndex != -1}} // not restricted (captive) + }) > -1 + } } diff --git a/addons/dragging/functions/fnc_carryObjectPFH.sqf b/addons/dragging/functions/fnc_carryObjectPFH.sqf index a615d282521..24de3373f25 100644 --- a/addons/dragging/functions/fnc_carryObjectPFH.sqf +++ b/addons/dragging/functions/fnc_carryObjectPFH.sqf @@ -98,7 +98,8 @@ if ( !isNull _cursorObject && {[_unit, _cursorObject, ["isNotCarrying"]] call EFUNC(common,canInteractWith)} && { if (_target isKindOf "CAManBase") then { - (_unit distance _cursorObject <= MAX_LOAD_DISTANCE_MAN) && {[_cursorObject, 0, true] call EFUNC(common,nearestVehiclesFreeSeat) isNotEqualTo []} + [_unit, _cursorObject] call EFUNC(interaction,getInteractionDistance) < MAX_LOAD_DISTANCE_MAN + && {_target call EFUNC(common,nearestVehiclesFreeSeat) isNotEqualTo []} } else { ["ace_cargo"] call EFUNC(common,isModLoaded) && {EGVAR(cargo,enable)} && diff --git a/addons/dragging/functions/fnc_dropObject_carry.sqf b/addons/dragging/functions/fnc_dropObject_carry.sqf index 52d5d36c958..ab2e7c70873 100644 --- a/addons/dragging/functions/fnc_dropObject_carry.sqf +++ b/addons/dragging/functions/fnc_dropObject_carry.sqf @@ -125,10 +125,14 @@ _target setVariable [QGVAR(carryDirection_temp), nil]; if (_loadCargo) then { [_unit, _target, _cursorObject] call EFUNC(cargo,startLoadIn); } else { - if (_tryLoad && {_unit distance _cursorObject <= MAX_LOAD_DISTANCE_MAN} && {_target isKindOf "CAManBase"}) then { - private _vehicles = [_cursorObject, 0, true] call EFUNC(common,nearestVehiclesFreeSeat); - - if ([_cursorObject] isEqualTo _vehicles) then { + if ( + _tryLoad + && {_target isKindOf "CAManBase"} + && {[_unit, _cursorObject] call EFUNC(interaction,getInteractionDistance) < MAX_LOAD_DISTANCE_MAN} + ) then { + private _vehicles = _target call EFUNC(common,nearestVehiclesFreeSeat); + + if (_cursorObject in _vehicles) then { if (GETEGVAR(medical,enabled,false)) then { [_unit, _target, _cursorObject] call EFUNC(medical_treatment,loadUnit); } else { diff --git a/addons/dragging/script_component.hpp b/addons/dragging/script_component.hpp index 4417d5d941d..7c54cfb6537 100644 --- a/addons/dragging/script_component.hpp +++ b/addons/dragging/script_component.hpp @@ -16,7 +16,7 @@ #include "\z\ace\addons\main\script_macros.hpp" -#define MAX_LOAD_DISTANCE_MAN 5 +#define MAX_LOAD_DISTANCE_MAN 2 #define DRAG_ANIMATIONS ["amovpercmstpslowwrfldnon_acinpknlmwlkslowwrfldb_2", "amovpercmstpsraswpstdnon_acinpknlmwlksnonwpstdb_2", "amovpercmstpsnonwnondnon_acinpknlmwlksnonwnondb_2", "acinpknlmstpsraswrfldnon", "acinpknlmstpsnonwpstdnon", "acinpknlmstpsnonwnondnon", "acinpknlmwlksraswrfldb", "acinpknlmwlksnonwnondb", "ace_dragging_rifle_limpb", "ace_dragging", "ace_dragging_limpb", "ace_dragging_static", "ace_dragging_drop"] #define CARRY_ANIMATIONS ["acinpercmstpsnonwnondnon", "acinpknlmstpsnonwnondnon_acinpercmrunsnonwnondnon"] diff --git a/addons/medical_treatment/functions/fnc_loadUnit.sqf b/addons/medical_treatment/functions/fnc_loadUnit.sqf index 4e9ed928a49..ffda3aada2d 100644 --- a/addons/medical_treatment/functions/fnc_loadUnit.sqf +++ b/addons/medical_treatment/functions/fnc_loadUnit.sqf @@ -32,10 +32,6 @@ if (_patient call EFUNC(common,isBeingDragged)) then { [_medic, _patient] call EFUNC(dragging,dropObject); }; -if (!alive _patient) exitWith { - [[LSTRING(CanNotLoadDead), _patient call EFUNC(common,getName)]] call EFUNC(common,displayTextStructured); -}; - private _vehicle = [ _medic, _patient, @@ -48,7 +44,7 @@ if (isNull _vehicle) exitWith { TRACE_1("no vehicle found",_vehicle); }; [{ params ["_unit", "_vehicle"]; - (alive _unit) && {alive _vehicle} && {(vehicle _unit) == _vehicle} + alive _vehicle && {(objectParent _unit) == _vehicle} // objectParent instead of vehicle is for dead units }, { params ["_unit", "_vehicle"]; TRACE_2("success",_unit,_vehicle); diff --git a/addons/medical_treatment/stringtable.xml b/addons/medical_treatment/stringtable.xml index f16f2cd705a..ce798a5faa8 100644 --- a/addons/medical_treatment/stringtable.xml +++ b/addons/medical_treatment/stringtable.xml @@ -1950,23 +1950,6 @@ Bu kişi (% 1) uyanık ve yüklenemiyor Боєць (%1) у свідомості і не може бути завантажений - - This person (%1) is dead and cannot be loaded - Tato osoba (%1) je mrtvá a nemůže být naložena - %1 est mort et ne peut être embarqué. - Esta persona (%1) está muerta y no puede ser cargado - Questa persona (%1) è morta e non può essere caricata. - Ta osoba (%1) jest martwa i nie może zostać załadowana - Esta pessoa (%1) está morta e não pode ser carregada - Боец (%1) мертв и не может быть погружен - Diese Person (%1) ist tot und kann nicht verladen werden - 이 사람 (%1) 은(는) 사망하여 태우지 못합니다 - 患者 (%1) は死亡しており、積み込めない - 此人(%1)已死亡,无法被装载 - 此人(%1)已死亡,无法被装载 - Bu kişi (%1) ölü ve yüklenemiyor - Боєць (%1) мертвий і не може бути завантажений - Carry Nést From 7a87c3477c823c6e422074c63dabfcbfebb102e8 Mon Sep 17 00:00:00 2001 From: Dystopian Date: Fri, 1 May 2026 03:52:47 +0300 Subject: [PATCH 02/10] Fix cargo seats assigning, Fix dedicated server crash --- .../common/functions/fnc_loadDeadPerson.sqf | 130 +++++++++++++++--- 1 file changed, 111 insertions(+), 19 deletions(-) diff --git a/addons/common/functions/fnc_loadDeadPerson.sqf b/addons/common/functions/fnc_loadDeadPerson.sqf index ec61118e9f1..58e411a8ccc 100644 --- a/addons/common/functions/fnc_loadDeadPerson.sqf +++ b/addons/common/functions/fnc_loadDeadPerson.sqf @@ -17,7 +17,7 @@ */ params ["_unit", "_vehicle"]; -TRACE_5("loadDeadPerson",_unit,_vehicle,typeOf _vehicle,local _unit,local _vehicle); +TRACE_6("loadDeadPerson",_unit,_vehicle,typeOf _vehicle,local _unit,local _vehicle,isDedicated); if (!local _vehicle) exitWith {ERROR_4("loadDeadPerson vehicle not local _unit=%1[%2] _vehicle=%3[%4]",_unit,typeOf _unit,_vehicle,typeOf _vehicle)}; @@ -33,20 +33,64 @@ if (!local _vehicle) exitWith {ERROR_4("loadDeadPerson vehicle not local _unit=% #define SEATHOLDER_CLASS "VirtualMan_F" private _vehicleConfig = configOf _vehicle; -private _emptySeats = fullCrew [_vehicle, "", true] select {isNull (_x select 0)}; +/* + When moveInAny assigns a unit to a cargo seat, + it uses a slot with Position Number = count fullCrew [_vehicle, "cargo"], + even if the seat is locked or occupied. + https://feedback.bistudio.com/T198758 + Locked seats can be bypassed with moveInAny of a temporary unit. + Occupied seats can be bypassed with moveInCargo of a temporary unit to another empty seat. + When the whole second half of cargo is occupied, + moveInAny can not be used for cargo at all. + Cargo example: + 2 3 4 5 6 7 8 9 10 11 12 13 14 _cargoIndex (fullCrew, moveInCargo, lockedCargo) + 0 1 2 3 4 5 6 7 8 9 10 11 12 _cargoPositionNumber (action GetInCargo) + L o _ o L o L o _ o _ L L seats: _ empty, o unit, L empty locked + S P A T S + |___________| <--- moveInAny can fill (5(o) + A + T + C + C = 9) + P _cargoCurrentPositionNumber + A locked but can fill with moveInAny + T _targetCargoPositionNumber + s _cargoSpareIndexes +*/ + +// Pre-scan cargo seats to determine moveInAny target position and remapping data +private _cargoCurrentPositionNumber = count fullCrew [_vehicle, "cargo"]; +private _targetCargoPositionNumber = -1; +private _cargoSpareIndexes = []; +private _cargoIndexes = []; +private _cargoAvailable = 0 == getNumber (_vehicleConfig >> "ejectDeadCargo"); +if (_cargoAvailable) then { + { + _x params ["_crewUnit", "", "_cargoIndex"]; + _cargoIndexes pushBack _cargoIndex; + if (!isNull _crewUnit || {_vehicle lockedCargo _cargoIndex}) then {continue}; + private _cargoPositionNumber = _forEachIndex; + if (_targetCargoPositionNumber == -1 && {_cargoPositionNumber >= _cargoCurrentPositionNumber}) then { + _targetCargoPositionNumber = _cargoPositionNumber; + } else { + _cargoSpareIndexes pushBack _cargoIndex; + }; + } forEach fullCrew [_vehicle, "cargo", true]; + _cargoAvailable = _targetCargoPositionNumber > -1; +}; +TRACE_5("cargo pre-scan",_cargoAvailable,_cargoCurrentPositionNumber,_targetCargoPositionNumber,_cargoSpareIndexes,_cargoIndexes); +private _cargoRemap = createHashMap; + +// Determine highest-priority available seats private _bestSeatsPriority = PRIORITY_NONE; private _bestSeatsRole = ""; private _bestSeatsParams = []; - -// Determine highest-priority available seats +private _allSeats = fullCrew [_vehicle, "", true]; { - _x params ["", "_role", "_cargoIndex", "_turretPath", "_isPersonTurret"]; + _x params ["_crewUnit", "_role", "_cargoIndex", "_turretPath", "_isPersonTurret"]; private _priority = PRIORITY_NONE; switch (_role) do { case "driver": { if ( - lockedDriver _vehicle + !isNull _crewUnit + || {lockedDriver _vehicle} || {unitIsUAV _vehicle} || {getNumber (_vehicleConfig >> "hasDriver") < 1} || {getNumber (_vehicleConfig >> "ejectDeadDriver") > 0} @@ -54,16 +98,35 @@ private _bestSeatsParams = []; _priority = PRIORITY_DRIVER; }; case "cargo": { - if ( - _vehicle lockedCargo _cargoIndex - || {getNumber (_vehicleConfig >> "ejectDeadCargo") > 0} - ) then {continue}; - _priority = PRIORITY_CARGO; + if (!_cargoAvailable) then {continue}; + private _cargoPositionNumber = _cargoIndexes find _cargoIndex; + if (_cargoPositionNumber > _targetCargoPositionNumber) then {break}; + if (_cargoPositionNumber == _targetCargoPositionNumber) then { + _priority = PRIORITY_CARGO; + } else { + if ( + _cargoPositionNumber < _cargoCurrentPositionNumber // unavailable for moveInAny + || {isNull _crewUnit} // locked empty seat before _targetCargoPositionNumber + ) then { + TRACE_4("cargo skip",_cargoIndex,_cargoPositionNumber,_crewUnit,_vehicle lockedCargo _cargoIndex); + continue; + }; + // try to skip occupied seat + if (_cargoSpareIndexes isEqualTo []) then { + _cargoAvailable = false; // cannot advance moveInAny cargo position + _cargoRemap = createHashMap; + } else { + _cargoRemap set [_cargoPositionNumber, _cargoSpareIndexes deleteAt 0]; + }; + TRACE_4("cargo seat",_cargoIndex,_cargoPositionNumber,_crewUnit,_cargoAvailable); + continue; + }; }; default { private _turretConfig = [_vehicleConfig, _turretPath] call CBA_fnc_getTurret; if ( - _vehicle lockedTurret _turretPath + !isNull _crewUnit + || {_vehicle lockedTurret _turretPath} || {getNumber (_turretConfig >> "ejectDeadGunner") > 0} || {_role == "gunner" && {unitIsUAV _vehicle}} ) then {continue}; @@ -87,23 +150,51 @@ private _bestSeatsParams = []; if (_priority == _bestSeatsPriority) then { _bestSeatsParams pushBack [_cargoIndex, _turretPath]; }; -} forEach _emptySeats; +} forEach _allSeats; if (_bestSeatsPriority == PRIORITY_NONE) exitWith { - TRACE_2("No seats found",_emptySeats,fullCrew _vehicle); + TRACE_1("No seats found",_allSeats apply {_x select [ARR_2(0,5)]}); }; -TRACE_3("emptySeats",_bestSeatsPriority,_bestSeatsRole,_bestSeatsParams); +TRACE_4("emptySeats",_bestSeatsPriority,_bestSeatsRole,_bestSeatsParams,_cargoRemap); + +// createVehicle(Local) + moveInAny crashes dedicated server https://feedback.bistudio.com/T198846, use createAgent instead +private _createSeatHolder = if (isDedicated) then { + {createAgent [SEATHOLDER_CLASS, [0,0,0], [], 0, "CAN_COLLIDE"]} +} else { + {createVehicleLocal [SEATHOLDER_CLASS, [0,0,0], [], 0, "CAN_COLLIDE"]} +}; // Probe seat positions using temporary units to identify exact seat private _seatHolders = []; private _remainingEmptyPositions = _vehicle emptyPositions ""; // Guard against infinite loop if moveInAny fails with true while {_remainingEmptyPositions > 0} do { - private _seatHolder = createVehicleLocal [SEATHOLDER_CLASS, [0,0,0], [], 0, "CAN_COLLIDE"]; + // advance moveInAny cargo position + while {_cargoCurrentPositionNumber in _cargoRemap} do { + private _seatHolder = createAgent [SEATHOLDER_CLASS, [0,0,0], [], 0, "CAN_COLLIDE"]; + _seatHolders pushBack _seatHolder; + private _moveInCargoIndex = _cargoRemap get _cargoCurrentPositionNumber; + _seatHolder moveInCargo [_vehicle, _moveInCargoIndex]; // cannot use "false" 3rd argument here https://feedback.bistudio.com/T198984 + private _seatHolderSeatParams = fullCrew _vehicle select {_x select 0 == _seatHolder}; + if (_seatHolderSeatParams isEqualTo [] || {_seatHolderSeatParams select 0 select 2 != _moveInCargoIndex}) then { + ERROR_8("moveInCargo holder failed _unit=%1[%2] _vehicle=%3[%4] _seatHolder=%5 _moveInCargoIndex=%6 fullCrew=%7 %8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_seatHolder,_moveInCargoIndex,fullCrew _vehicle apply {_x select [ARR_2(0,5)]},__FILE__); + _vehicle deleteVehicleCrew _seatHolder; + if (!isNull _seatHolder) then { // failsafe + moveOut _seatHolder; + deleteVehicle _seatHolder; + }; + break; + }; + TRACE_4("cargo move",_remainingEmptyPositions,_cargoCurrentPositionNumber,_moveInCargoIndex,_seatHolderSeatParams); + INC(_cargoCurrentPositionNumber); + DEC(_remainingEmptyPositions); + }; + + private _seatHolder = call _createSeatHolder; private _seatHolderMoveSuccess = _seatHolder moveInAny _vehicle; private _seatHolderSeatParams = fullCrew _vehicle select {_x select 0 == _seatHolder}; if (!_seatHolderMoveSuccess || {_seatHolderSeatParams isEqualTo []}) then { - ERROR_8("moveInAny holder failed _unit=%1[%2] _vehicle=%3[%4] _seatHolder=%5 _seatHolderMoveSuccess=%6 fullCrew=%7 %8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_seatHolder,_seatHolderMoveSuccess,fullCrew _vehicle,__FILE__); + ERROR_8("moveInAny holder failed _unit=%1[%2] _vehicle=%3[%4] _seatHolder=%5 _seatHolderMoveSuccess=%6 fullCrew=%7 %8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_seatHolder,_seatHolderMoveSuccess,fullCrew _vehicle apply {_x select [ARR_2(0,5)]},__FILE__); _vehicle deleteVehicleCrew _seatHolder; if (!isNull _seatHolder) then { // failsafe moveOut _seatHolder; @@ -118,13 +209,14 @@ while {_remainingEmptyPositions > 0} do { if (_unitMoveSuccess) then { [QGVAR(deadPersonLoaded), _unit] call CBA_fnc_globalEvent; // Ensure remote dead unit stays unconscious } else { - ERROR_8("moveInAny unit failed _unit=%1[%2] _vehicle=%3[%4] _seatHolder=%5 _seatHolderSeatParams=%6 fullCrew=%7 %8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_seatHolder,_seatHolderSeatParams,fullCrew _vehicle,__FILE__); + ERROR_8("moveInAny unit failed _unit=%1[%2] _vehicle=%3[%4] _seatHolder=%5 _seatHolderSeatParams=%6 fullCrew=%7 %8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_seatHolder,_seatHolderSeatParams,fullCrew _vehicle apply {_x select [ARR_2(0,5)]},__FILE__); }; TRACE_4("seat",_remainingEmptyPositions,_role,_cargoIndex,_turretPath); break; }; - TRACE_4("seatHolder",_remainingEmptyPositions,_role,_cargoIndex,_turretPath); + TRACE_5("seatHolder",_remainingEmptyPositions,_role,_cargoIndex,_turretPath,_cargoCurrentPositionNumber); _seatHolders pushBack _seatHolder; + if (_role == "cargo") then {INC(_cargoCurrentPositionNumber)}; DEC(_remainingEmptyPositions); }; From c411943232002990383574c1e25c962487c982fb Mon Sep 17 00:00:00 2001 From: Dystopian Date: Fri, 1 May 2026 03:57:42 +0300 Subject: [PATCH 03/10] Fix unit not always loaded in after drop --- .../common/functions/fnc_nearestVehiclesFreeSeat.sqf | 12 +++++++++--- addons/dragging/functions/fnc_carryObjectPFH.sqf | 2 +- addons/dragging/functions/fnc_dropObject_carry.sqf | 5 +++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/addons/common/functions/fnc_nearestVehiclesFreeSeat.sqf b/addons/common/functions/fnc_nearestVehiclesFreeSeat.sqf index 3d44026fd70..5db4c58d56f 100644 --- a/addons/common/functions/fnc_nearestVehiclesFreeSeat.sqf +++ b/addons/common/functions/fnc_nearestVehiclesFreeSeat.sqf @@ -7,6 +7,7 @@ * 0: Unit * 1: Distance (default: 10) * 2: Restricted to cargo only (default: false) + * 3: Override vehicle to check instead of distance search (default: objNull) * * Return Value: * Nearest vehicles with a free seat @@ -14,12 +15,17 @@ * Example: * [cursorObject] call ace_common_fnc_nearestVehiclesFreeSeat * - * Public: Yes + * Public: No */ -params ["_unit", ["_distance", 10], ["_cargoOnly", false]]; +params ["_unit", ["_distance", 10], ["_cargoOnly", false], ["_overrideVehicle", objNull]]; + +private _nearVehicles = if (isNull _overrideVehicle) then { + nearestObjects [_unit, ["Car", "Air", "Tank", "Ship_F", "Pod_Heli_Transport_04_crewed_base_F"], _distance] +} else { + [_overrideVehicle] +}; -private _nearVehicles = nearestObjects [_unit, ["Car", "Air", "Tank", "Ship_F", "Pod_Heli_Transport_04_crewed_base_F"], _distance]; _nearVehicles select { private _vehicle = _x; alive _vehicle diff --git a/addons/dragging/functions/fnc_carryObjectPFH.sqf b/addons/dragging/functions/fnc_carryObjectPFH.sqf index 24de3373f25..18542374ec3 100644 --- a/addons/dragging/functions/fnc_carryObjectPFH.sqf +++ b/addons/dragging/functions/fnc_carryObjectPFH.sqf @@ -99,7 +99,7 @@ if ( { if (_target isKindOf "CAManBase") then { [_unit, _cursorObject] call EFUNC(interaction,getInteractionDistance) < MAX_LOAD_DISTANCE_MAN - && {_target call EFUNC(common,nearestVehiclesFreeSeat) isNotEqualTo []} + && {[_target, nil, nil, _cursorObject] call EFUNC(common,nearestVehiclesFreeSeat) isEqualTo [_cursorObject]} } else { ["ace_cargo"] call EFUNC(common,isModLoaded) && {EGVAR(cargo,enable)} && diff --git a/addons/dragging/functions/fnc_dropObject_carry.sqf b/addons/dragging/functions/fnc_dropObject_carry.sqf index ab2e7c70873..436301d7755 100644 --- a/addons/dragging/functions/fnc_dropObject_carry.sqf +++ b/addons/dragging/functions/fnc_dropObject_carry.sqf @@ -130,9 +130,10 @@ if (_loadCargo) then { && {_target isKindOf "CAManBase"} && {[_unit, _cursorObject] call EFUNC(interaction,getInteractionDistance) < MAX_LOAD_DISTANCE_MAN} ) then { - private _vehicles = _target call EFUNC(common,nearestVehiclesFreeSeat); + // can't search nearest vehicles because target position can be desynced ATM + private _vehicles = [_target, nil, nil, _cursorObject] call EFUNC(common,nearestVehiclesFreeSeat); - if (_cursorObject in _vehicles) then { + if (_vehicles isEqualTo [_cursorObject]) then { if (GETEGVAR(medical,enabled,false)) then { [_unit, _target, _cursorObject] call EFUNC(medical_treatment,loadUnit); } else { From 3438b34346df219aaa73504ccb37104f09b5d438 Mon Sep 17 00:00:00 2001 From: Dystopian Date: Fri, 1 May 2026 14:29:02 +0300 Subject: [PATCH 04/10] Fix flow, Fix comments, Improve naming --- .../common/functions/fnc_loadDeadPerson.sqf | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/addons/common/functions/fnc_loadDeadPerson.sqf b/addons/common/functions/fnc_loadDeadPerson.sqf index 58e411a8ccc..205f46f1c75 100644 --- a/addons/common/functions/fnc_loadDeadPerson.sqf +++ b/addons/common/functions/fnc_loadDeadPerson.sqf @@ -44,20 +44,20 @@ private _vehicleConfig = configOf _vehicle; When the whole second half of cargo is occupied, moveInAny can not be used for cargo at all. Cargo example: - 2 3 4 5 6 7 8 9 10 11 12 13 14 _cargoIndex (fullCrew, moveInCargo, lockedCargo) + 2 3 4 5 6 8 9 10 11 12 14 15 16 _cargoIndex (fullCrew, moveInCargo, lockedCargo) 0 1 2 3 4 5 6 7 8 9 10 11 12 _cargoPositionNumber (action GetInCargo) L o _ o L o L o _ o _ L L seats: _ empty, o unit, L empty locked S P A T S - |___________| <--- moveInAny can fill (5(o) + A + T + C + C = 9) + |___________| <--- moveInAny can fill (5(o) + A + T + S + S = 9) P _cargoCurrentPositionNumber A locked but can fill with moveInAny - T _targetCargoPositionNumber - s _cargoSpareIndexes + T _cargoTargetPositionNumber + S _cargoSpareIndexes */ // Pre-scan cargo seats to determine moveInAny target position and remapping data private _cargoCurrentPositionNumber = count fullCrew [_vehicle, "cargo"]; -private _targetCargoPositionNumber = -1; +private _cargoTargetPositionNumber = -1; private _cargoSpareIndexes = []; private _cargoIndexes = []; private _cargoAvailable = 0 == getNumber (_vehicleConfig >> "ejectDeadCargo"); @@ -67,15 +67,15 @@ if (_cargoAvailable) then { _cargoIndexes pushBack _cargoIndex; if (!isNull _crewUnit || {_vehicle lockedCargo _cargoIndex}) then {continue}; private _cargoPositionNumber = _forEachIndex; - if (_targetCargoPositionNumber == -1 && {_cargoPositionNumber >= _cargoCurrentPositionNumber}) then { - _targetCargoPositionNumber = _cargoPositionNumber; + if (_cargoTargetPositionNumber == -1 && {_cargoPositionNumber >= _cargoCurrentPositionNumber}) then { + _cargoTargetPositionNumber = _cargoPositionNumber; } else { _cargoSpareIndexes pushBack _cargoIndex; }; } forEach fullCrew [_vehicle, "cargo", true]; - _cargoAvailable = _targetCargoPositionNumber > -1; + _cargoAvailable = _cargoTargetPositionNumber > -1; }; -TRACE_5("cargo pre-scan",_cargoAvailable,_cargoCurrentPositionNumber,_targetCargoPositionNumber,_cargoSpareIndexes,_cargoIndexes); +TRACE_5("cargo pre-scan",_cargoAvailable,_cargoCurrentPositionNumber,_cargoTargetPositionNumber,_cargoSpareIndexes,_cargoIndexes); private _cargoRemap = createHashMap; // Determine highest-priority available seats @@ -100,13 +100,13 @@ private _allSeats = fullCrew [_vehicle, "", true]; case "cargo": { if (!_cargoAvailable) then {continue}; private _cargoPositionNumber = _cargoIndexes find _cargoIndex; - if (_cargoPositionNumber > _targetCargoPositionNumber) then {break}; - if (_cargoPositionNumber == _targetCargoPositionNumber) then { + if (_cargoPositionNumber > _cargoTargetPositionNumber) then {break}; + if (_cargoPositionNumber == _cargoTargetPositionNumber) then { _priority = PRIORITY_CARGO; } else { if ( _cargoPositionNumber < _cargoCurrentPositionNumber // unavailable for moveInAny - || {isNull _crewUnit} // locked empty seat before _targetCargoPositionNumber + || {isNull _crewUnit} // locked empty seat before _cargoTargetPositionNumber ) then { TRACE_4("cargo skip",_cargoIndex,_cargoPositionNumber,_crewUnit,_vehicle lockedCargo _cargoIndex); continue; @@ -156,7 +156,7 @@ if (_bestSeatsPriority == PRIORITY_NONE) exitWith { TRACE_1("No seats found",_allSeats apply {_x select [ARR_2(0,5)]}); }; -TRACE_4("emptySeats",_bestSeatsPriority,_bestSeatsRole,_bestSeatsParams,_cargoRemap); +TRACE_4("bestSeats",_bestSeatsPriority,_bestSeatsRole,_bestSeatsParams,_cargoRemap); // createVehicle(Local) + moveInAny crashes dedicated server https://feedback.bistudio.com/T198846, use createAgent instead private _createSeatHolder = if (isDedicated) then { @@ -167,16 +167,16 @@ private _createSeatHolder = if (isDedicated) then { // Probe seat positions using temporary units to identify exact seat private _seatHolders = []; -private _remainingEmptyPositions = _vehicle emptyPositions ""; // Guard against infinite loop if moveInAny fails with true +private _remainingEmptyPositions = _vehicle emptyPositions ""; // Guard against infinite loop while {_remainingEmptyPositions > 0} do { // advance moveInAny cargo position - while {_cargoCurrentPositionNumber in _cargoRemap} do { + if (_cargoCurrentPositionNumber in _cargoRemap) then { + // moveInCargo does not support createVehicleLocal units private _seatHolder = createAgent [SEATHOLDER_CLASS, [0,0,0], [], 0, "CAN_COLLIDE"]; - _seatHolders pushBack _seatHolder; private _moveInCargoIndex = _cargoRemap get _cargoCurrentPositionNumber; _seatHolder moveInCargo [_vehicle, _moveInCargoIndex]; // cannot use "false" 3rd argument here https://feedback.bistudio.com/T198984 - private _seatHolderSeatParams = fullCrew _vehicle select {_x select 0 == _seatHolder}; - if (_seatHolderSeatParams isEqualTo [] || {_seatHolderSeatParams select 0 select 2 != _moveInCargoIndex}) then { + private _seatHolderSeat = fullCrew _vehicle select {_x select 0 == _seatHolder}; + if (_seatHolderSeat isEqualTo [] || {_seatHolderSeat select 0 select 2 != _moveInCargoIndex}) then { ERROR_8("moveInCargo holder failed _unit=%1[%2] _vehicle=%3[%4] _seatHolder=%5 _moveInCargoIndex=%6 fullCrew=%7 %8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_seatHolder,_moveInCargoIndex,fullCrew _vehicle apply {_x select [ARR_2(0,5)]},__FILE__); _vehicle deleteVehicleCrew _seatHolder; if (!isNull _seatHolder) then { // failsafe @@ -185,15 +185,17 @@ while {_remainingEmptyPositions > 0} do { }; break; }; - TRACE_4("cargo move",_remainingEmptyPositions,_cargoCurrentPositionNumber,_moveInCargoIndex,_seatHolderSeatParams); + TRACE_4("cargo move",_remainingEmptyPositions,_cargoCurrentPositionNumber,_moveInCargoIndex,_seatHolderSeat); + _seatHolders pushBack _seatHolder; INC(_cargoCurrentPositionNumber); DEC(_remainingEmptyPositions); + continue; }; private _seatHolder = call _createSeatHolder; private _seatHolderMoveSuccess = _seatHolder moveInAny _vehicle; - private _seatHolderSeatParams = fullCrew _vehicle select {_x select 0 == _seatHolder}; - if (!_seatHolderMoveSuccess || {_seatHolderSeatParams isEqualTo []}) then { + private _seatHolderSeat = fullCrew _vehicle select {_x select 0 == _seatHolder}; + if (!_seatHolderMoveSuccess || {_seatHolderSeat isEqualTo []}) then { ERROR_8("moveInAny holder failed _unit=%1[%2] _vehicle=%3[%4] _seatHolder=%5 _seatHolderMoveSuccess=%6 fullCrew=%7 %8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_seatHolder,_seatHolderMoveSuccess,fullCrew _vehicle apply {_x select [ARR_2(0,5)]},__FILE__); _vehicle deleteVehicleCrew _seatHolder; if (!isNull _seatHolder) then { // failsafe @@ -202,14 +204,14 @@ while {_remainingEmptyPositions > 0} do { }; break; }; - _seatHolderSeatParams select 0 params ["", "_role", "_cargoIndex", "_turretPath"]; + _seatHolderSeat select 0 params ["", "_role", "_cargoIndex", "_turretPath"]; if (_role == _bestSeatsRole && {[_cargoIndex, _turretPath] in _bestSeatsParams}) then { _vehicle deleteVehicleCrew _seatHolder; private _unitMoveSuccess = _unit moveInAny _vehicle; if (_unitMoveSuccess) then { - [QGVAR(deadPersonLoaded), _unit] call CBA_fnc_globalEvent; // Ensure remote dead unit stays unconscious + [QGVAR(deadPersonLoaded), _unit] call CBA_fnc_globalEvent; // Ensure dead unit stays unconscious on all clients } else { - ERROR_8("moveInAny unit failed _unit=%1[%2] _vehicle=%3[%4] _seatHolder=%5 _seatHolderSeatParams=%6 fullCrew=%7 %8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_seatHolder,_seatHolderSeatParams,fullCrew _vehicle apply {_x select [ARR_2(0,5)]},__FILE__); + ERROR_8("moveInAny unit failed _unit=%1[%2] _vehicle=%3[%4] _seatHolder=%5 _seatHolderSeat=%6 fullCrew=%7 %8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_seatHolder,_seatHolderSeat,fullCrew _vehicle apply {_x select [ARR_2(0,5)]},__FILE__); }; TRACE_4("seat",_remainingEmptyPositions,_role,_cargoIndex,_turretPath); break; From e373abf62625af22b24a5355be25935175360669 Mon Sep 17 00:00:00 2001 From: Dystopian Date: Tue, 12 May 2026 00:41:13 +0300 Subject: [PATCH 05/10] Use fixes and features from dev 153732 --- addons/common/CfgVehicles.hpp | 9 ++ .../common/functions/fnc_loadDeadPerson.sqf | 137 +++--------------- 2 files changed, 29 insertions(+), 117 deletions(-) diff --git a/addons/common/CfgVehicles.hpp b/addons/common/CfgVehicles.hpp index 2e3a81d1612..5785a407e1d 100644 --- a/addons/common/CfgVehicles.hpp +++ b/addons/common/CfgVehicles.hpp @@ -21,6 +21,15 @@ class CfgVehicles { }; };*/ + class Animal_Base_F; + // createVehicleLocal CAManBase units spams to RPT: + // "Tried to create local-only container with backpacks, that does not work in multiplayer" + // animals have clean RPT + class GVAR(seatHolder): Animal_Base_F { + scope = 1; + model = "\A3\Animals_F\mosquito.p3d"; + }; + // += needs a non inherited entry in that class, otherwise it simply overwrites //#include class Logic; diff --git a/addons/common/functions/fnc_loadDeadPerson.sqf b/addons/common/functions/fnc_loadDeadPerson.sqf index 205f46f1c75..655e4fd003e 100644 --- a/addons/common/functions/fnc_loadDeadPerson.sqf +++ b/addons/common/functions/fnc_loadDeadPerson.sqf @@ -17,7 +17,7 @@ */ params ["_unit", "_vehicle"]; -TRACE_6("loadDeadPerson",_unit,_vehicle,typeOf _vehicle,local _unit,local _vehicle,isDedicated); +TRACE_5("loadDeadPerson",_unit,_vehicle,typeOf _vehicle,local _unit,local _vehicle); if (!local _vehicle) exitWith {ERROR_4("loadDeadPerson vehicle not local _unit=%1[%2] _vehicle=%3[%4]",_unit,typeOf _unit,_vehicle,typeOf _vehicle)}; @@ -30,67 +30,19 @@ if (!local _vehicle) exitWith {ERROR_4("loadDeadPerson vehicle not local _unit=% #define PRIORITY_TURRET_EMPTY 5 #define PRIORITY_CARGO 6 -#define SEATHOLDER_CLASS "VirtualMan_F" - -private _vehicleConfig = configOf _vehicle; - -/* - When moveInAny assigns a unit to a cargo seat, - it uses a slot with Position Number = count fullCrew [_vehicle, "cargo"], - even if the seat is locked or occupied. - https://feedback.bistudio.com/T198758 - Locked seats can be bypassed with moveInAny of a temporary unit. - Occupied seats can be bypassed with moveInCargo of a temporary unit to another empty seat. - When the whole second half of cargo is occupied, - moveInAny can not be used for cargo at all. - Cargo example: - 2 3 4 5 6 8 9 10 11 12 14 15 16 _cargoIndex (fullCrew, moveInCargo, lockedCargo) - 0 1 2 3 4 5 6 7 8 9 10 11 12 _cargoPositionNumber (action GetInCargo) - L o _ o L o L o _ o _ L L seats: _ empty, o unit, L empty locked - S P A T S - |___________| <--- moveInAny can fill (5(o) + A + T + S + S = 9) - P _cargoCurrentPositionNumber - A locked but can fill with moveInAny - T _cargoTargetPositionNumber - S _cargoSpareIndexes -*/ - -// Pre-scan cargo seats to determine moveInAny target position and remapping data -private _cargoCurrentPositionNumber = count fullCrew [_vehicle, "cargo"]; -private _cargoTargetPositionNumber = -1; -private _cargoSpareIndexes = []; -private _cargoIndexes = []; -private _cargoAvailable = 0 == getNumber (_vehicleConfig >> "ejectDeadCargo"); -if (_cargoAvailable) then { - { - _x params ["_crewUnit", "", "_cargoIndex"]; - _cargoIndexes pushBack _cargoIndex; - if (!isNull _crewUnit || {_vehicle lockedCargo _cargoIndex}) then {continue}; - private _cargoPositionNumber = _forEachIndex; - if (_cargoTargetPositionNumber == -1 && {_cargoPositionNumber >= _cargoCurrentPositionNumber}) then { - _cargoTargetPositionNumber = _cargoPositionNumber; - } else { - _cargoSpareIndexes pushBack _cargoIndex; - }; - } forEach fullCrew [_vehicle, "cargo", true]; - _cargoAvailable = _cargoTargetPositionNumber > -1; -}; -TRACE_5("cargo pre-scan",_cargoAvailable,_cargoCurrentPositionNumber,_cargoTargetPositionNumber,_cargoSpareIndexes,_cargoIndexes); -private _cargoRemap = createHashMap; - // Determine highest-priority available seats +private _emptySeats = fullCrew [_vehicle, "", true] select {isNull (_x select 0)}; +private _vehicleConfig = configOf _vehicle; private _bestSeatsPriority = PRIORITY_NONE; private _bestSeatsRole = ""; private _bestSeatsParams = []; -private _allSeats = fullCrew [_vehicle, "", true]; { - _x params ["_crewUnit", "_role", "_cargoIndex", "_turretPath", "_isPersonTurret"]; + _x params ["", "_role", "_cargoIndex", "_turretPath", "_isPersonTurret"]; private _priority = PRIORITY_NONE; switch (_role) do { case "driver": { if ( - !isNull _crewUnit - || {lockedDriver _vehicle} + lockedDriver _vehicle || {unitIsUAV _vehicle} || {getNumber (_vehicleConfig >> "hasDriver") < 1} || {getNumber (_vehicleConfig >> "ejectDeadDriver") > 0} @@ -98,35 +50,16 @@ private _allSeats = fullCrew [_vehicle, "", true]; _priority = PRIORITY_DRIVER; }; case "cargo": { - if (!_cargoAvailable) then {continue}; - private _cargoPositionNumber = _cargoIndexes find _cargoIndex; - if (_cargoPositionNumber > _cargoTargetPositionNumber) then {break}; - if (_cargoPositionNumber == _cargoTargetPositionNumber) then { - _priority = PRIORITY_CARGO; - } else { - if ( - _cargoPositionNumber < _cargoCurrentPositionNumber // unavailable for moveInAny - || {isNull _crewUnit} // locked empty seat before _cargoTargetPositionNumber - ) then { - TRACE_4("cargo skip",_cargoIndex,_cargoPositionNumber,_crewUnit,_vehicle lockedCargo _cargoIndex); - continue; - }; - // try to skip occupied seat - if (_cargoSpareIndexes isEqualTo []) then { - _cargoAvailable = false; // cannot advance moveInAny cargo position - _cargoRemap = createHashMap; - } else { - _cargoRemap set [_cargoPositionNumber, _cargoSpareIndexes deleteAt 0]; - }; - TRACE_4("cargo seat",_cargoIndex,_cargoPositionNumber,_crewUnit,_cargoAvailable); - continue; - }; + if ( + _vehicle lockedCargo _cargoIndex + || {getNumber (_vehicleConfig >> "ejectDeadCargo") > 0} + ) then {continue}; + _priority = PRIORITY_CARGO; }; default { private _turretConfig = [_vehicleConfig, _turretPath] call CBA_fnc_getTurret; if ( - !isNull _crewUnit - || {_vehicle lockedTurret _turretPath} + _vehicle lockedTurret _turretPath || {getNumber (_turretConfig >> "ejectDeadGunner") > 0} || {_role == "gunner" && {unitIsUAV _vehicle}} ) then {continue}; @@ -150,50 +83,21 @@ private _allSeats = fullCrew [_vehicle, "", true]; if (_priority == _bestSeatsPriority) then { _bestSeatsParams pushBack [_cargoIndex, _turretPath]; }; -} forEach _allSeats; +} forEach _emptySeats; if (_bestSeatsPriority == PRIORITY_NONE) exitWith { - TRACE_1("No seats found",_allSeats apply {_x select [ARR_2(0,5)]}); + TRACE_2("No seats found",_emptySeats apply {_x select [ARR_2(1,4)]},fullCrew _vehicle apply {_x select [ARR_2(0,5)]}); }; -TRACE_4("bestSeats",_bestSeatsPriority,_bestSeatsRole,_bestSeatsParams,_cargoRemap); - -// createVehicle(Local) + moveInAny crashes dedicated server https://feedback.bistudio.com/T198846, use createAgent instead -private _createSeatHolder = if (isDedicated) then { - {createAgent [SEATHOLDER_CLASS, [0,0,0], [], 0, "CAN_COLLIDE"]} -} else { - {createVehicleLocal [SEATHOLDER_CLASS, [0,0,0], [], 0, "CAN_COLLIDE"]} -}; +TRACE_3("emptySeats",_bestSeatsPriority,_bestSeatsRole,_bestSeatsParams); // Probe seat positions using temporary units to identify exact seat private _seatHolders = []; -private _remainingEmptyPositions = _vehicle emptyPositions ""; // Guard against infinite loop +private _moveInAnyPositions = [toUpper _bestSeatsRole]; +private _remainingEmptyPositions = _vehicle emptyPositions _moveInAnyPositions; // Guard against infinite loop while {_remainingEmptyPositions > 0} do { - // advance moveInAny cargo position - if (_cargoCurrentPositionNumber in _cargoRemap) then { - // moveInCargo does not support createVehicleLocal units - private _seatHolder = createAgent [SEATHOLDER_CLASS, [0,0,0], [], 0, "CAN_COLLIDE"]; - private _moveInCargoIndex = _cargoRemap get _cargoCurrentPositionNumber; - _seatHolder moveInCargo [_vehicle, _moveInCargoIndex]; // cannot use "false" 3rd argument here https://feedback.bistudio.com/T198984 - private _seatHolderSeat = fullCrew _vehicle select {_x select 0 == _seatHolder}; - if (_seatHolderSeat isEqualTo [] || {_seatHolderSeat select 0 select 2 != _moveInCargoIndex}) then { - ERROR_8("moveInCargo holder failed _unit=%1[%2] _vehicle=%3[%4] _seatHolder=%5 _moveInCargoIndex=%6 fullCrew=%7 %8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_seatHolder,_moveInCargoIndex,fullCrew _vehicle apply {_x select [ARR_2(0,5)]},__FILE__); - _vehicle deleteVehicleCrew _seatHolder; - if (!isNull _seatHolder) then { // failsafe - moveOut _seatHolder; - deleteVehicle _seatHolder; - }; - break; - }; - TRACE_4("cargo move",_remainingEmptyPositions,_cargoCurrentPositionNumber,_moveInCargoIndex,_seatHolderSeat); - _seatHolders pushBack _seatHolder; - INC(_cargoCurrentPositionNumber); - DEC(_remainingEmptyPositions); - continue; - }; - - private _seatHolder = call _createSeatHolder; - private _seatHolderMoveSuccess = _seatHolder moveInAny _vehicle; + private _seatHolder = createVehicleLocal [QGVAR(seatHolder), [0,0,0], [], 0, "CAN_COLLIDE"]; + private _seatHolderMoveSuccess = _seatHolder moveInAny [_vehicle, _moveInAnyPositions]; private _seatHolderSeat = fullCrew _vehicle select {_x select 0 == _seatHolder}; if (!_seatHolderMoveSuccess || {_seatHolderSeat isEqualTo []}) then { ERROR_8("moveInAny holder failed _unit=%1[%2] _vehicle=%3[%4] _seatHolder=%5 _seatHolderMoveSuccess=%6 fullCrew=%7 %8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_seatHolder,_seatHolderMoveSuccess,fullCrew _vehicle apply {_x select [ARR_2(0,5)]},__FILE__); @@ -207,7 +111,7 @@ while {_remainingEmptyPositions > 0} do { _seatHolderSeat select 0 params ["", "_role", "_cargoIndex", "_turretPath"]; if (_role == _bestSeatsRole && {[_cargoIndex, _turretPath] in _bestSeatsParams}) then { _vehicle deleteVehicleCrew _seatHolder; - private _unitMoveSuccess = _unit moveInAny _vehicle; + private _unitMoveSuccess = _unit moveInAny [_vehicle, _moveInAnyPositions]; if (_unitMoveSuccess) then { [QGVAR(deadPersonLoaded), _unit] call CBA_fnc_globalEvent; // Ensure dead unit stays unconscious on all clients } else { @@ -216,9 +120,8 @@ while {_remainingEmptyPositions > 0} do { TRACE_4("seat",_remainingEmptyPositions,_role,_cargoIndex,_turretPath); break; }; - TRACE_5("seatHolder",_remainingEmptyPositions,_role,_cargoIndex,_turretPath,_cargoCurrentPositionNumber); + TRACE_4("seatHolder",_remainingEmptyPositions,_role,_cargoIndex,_turretPath); _seatHolders pushBack _seatHolder; - if (_role == "cargo") then {INC(_cargoCurrentPositionNumber)}; DEC(_remainingEmptyPositions); }; From 1e17e47880f3a9966676cc8f2bb0d27c2c877a53 Mon Sep 17 00:00:00 2001 From: Dystopian Date: Thu, 28 May 2026 22:50:39 +0300 Subject: [PATCH 06/10] Remove useless condition --- addons/common/functions/fnc_loadDeadPerson.sqf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/common/functions/fnc_loadDeadPerson.sqf b/addons/common/functions/fnc_loadDeadPerson.sqf index 655e4fd003e..a2cfb07d6a9 100644 --- a/addons/common/functions/fnc_loadDeadPerson.sqf +++ b/addons/common/functions/fnc_loadDeadPerson.sqf @@ -109,7 +109,7 @@ while {_remainingEmptyPositions > 0} do { break; }; _seatHolderSeat select 0 params ["", "_role", "_cargoIndex", "_turretPath"]; - if (_role == _bestSeatsRole && {[_cargoIndex, _turretPath] in _bestSeatsParams}) then { + if ([_cargoIndex, _turretPath] in _bestSeatsParams) then { _vehicle deleteVehicleCrew _seatHolder; private _unitMoveSuccess = _unit moveInAny [_vehicle, _moveInAnyPositions]; if (_unitMoveSuccess) then { From b279089f6d30a402c4866032d9bd0330e03b1943 Mon Sep 17 00:00:00 2001 From: Dystopian Date: Thu, 28 May 2026 22:56:55 +0300 Subject: [PATCH 07/10] Use reverse fill for cargo --- addons/common/functions/fnc_loadDeadPerson.sqf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/common/functions/fnc_loadDeadPerson.sqf b/addons/common/functions/fnc_loadDeadPerson.sqf index a2cfb07d6a9..516b0af84c8 100644 --- a/addons/common/functions/fnc_loadDeadPerson.sqf +++ b/addons/common/functions/fnc_loadDeadPerson.sqf @@ -81,7 +81,11 @@ private _bestSeatsParams = []; continue; }; if (_priority == _bestSeatsPriority) then { + if (_priority == PRIORITY_CARGO) then { + _bestSeatsParams = [[_cargoIndex, _turretPath]]; // use only the last cargo seat + } else { _bestSeatsParams pushBack [_cargoIndex, _turretPath]; + }; }; } forEach _emptySeats; From 5e6ebd5e5d2dbd1bde67fb2b39f2517cbeff2f53 Mon Sep 17 00:00:00 2001 From: Dystopian Date: Thu, 28 May 2026 22:58:17 +0300 Subject: [PATCH 08/10] Remove useless comment --- addons/common/functions/fnc_loadDeadPerson.sqf | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/common/functions/fnc_loadDeadPerson.sqf b/addons/common/functions/fnc_loadDeadPerson.sqf index 516b0af84c8..23e3375094c 100644 --- a/addons/common/functions/fnc_loadDeadPerson.sqf +++ b/addons/common/functions/fnc_loadDeadPerson.sqf @@ -129,7 +129,6 @@ while {_remainingEmptyPositions > 0} do { DEC(_remainingEmptyPositions); }; -// Cleanup all temporary seat holders { _vehicle deleteVehicleCrew _x; } forEach _seatHolders; From 84c9ca69fb54a83fdff14eca1da4960410c14497 Mon Sep 17 00:00:00 2001 From: Dystopian Date: Thu, 28 May 2026 23:04:06 +0300 Subject: [PATCH 09/10] Make emptyPositions parameters clearer --- addons/common/functions/fnc_loadDeadPerson.sqf | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/common/functions/fnc_loadDeadPerson.sqf b/addons/common/functions/fnc_loadDeadPerson.sqf index 23e3375094c..bd0792445a9 100644 --- a/addons/common/functions/fnc_loadDeadPerson.sqf +++ b/addons/common/functions/fnc_loadDeadPerson.sqf @@ -84,7 +84,7 @@ private _bestSeatsParams = []; if (_priority == PRIORITY_CARGO) then { _bestSeatsParams = [[_cargoIndex, _turretPath]]; // use only the last cargo seat } else { - _bestSeatsParams pushBack [_cargoIndex, _turretPath]; + _bestSeatsParams pushBack [_cargoIndex, _turretPath]; }; }; } forEach _emptySeats; @@ -97,11 +97,11 @@ TRACE_3("emptySeats",_bestSeatsPriority,_bestSeatsRole,_bestSeatsParams); // Probe seat positions using temporary units to identify exact seat private _seatHolders = []; -private _moveInAnyPositions = [toUpper _bestSeatsRole]; -private _remainingEmptyPositions = _vehicle emptyPositions _moveInAnyPositions; // Guard against infinite loop +private _moveInAnyPosition = toUpper _bestSeatsRole; +private _remainingEmptyPositions = _vehicle emptyPositions [_moveInAnyPosition]; // Guard against infinite loop while {_remainingEmptyPositions > 0} do { private _seatHolder = createVehicleLocal [QGVAR(seatHolder), [0,0,0], [], 0, "CAN_COLLIDE"]; - private _seatHolderMoveSuccess = _seatHolder moveInAny [_vehicle, _moveInAnyPositions]; + private _seatHolderMoveSuccess = _seatHolder moveInAny [_vehicle, [_moveInAnyPosition]]; private _seatHolderSeat = fullCrew _vehicle select {_x select 0 == _seatHolder}; if (!_seatHolderMoveSuccess || {_seatHolderSeat isEqualTo []}) then { ERROR_8("moveInAny holder failed _unit=%1[%2] _vehicle=%3[%4] _seatHolder=%5 _seatHolderMoveSuccess=%6 fullCrew=%7 %8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_seatHolder,_seatHolderMoveSuccess,fullCrew _vehicle apply {_x select [ARR_2(0,5)]},__FILE__); @@ -115,7 +115,7 @@ while {_remainingEmptyPositions > 0} do { _seatHolderSeat select 0 params ["", "_role", "_cargoIndex", "_turretPath"]; if ([_cargoIndex, _turretPath] in _bestSeatsParams) then { _vehicle deleteVehicleCrew _seatHolder; - private _unitMoveSuccess = _unit moveInAny [_vehicle, _moveInAnyPositions]; + private _unitMoveSuccess = _unit moveInAny [_vehicle, [_moveInAnyPosition]]; if (_unitMoveSuccess) then { [QGVAR(deadPersonLoaded), _unit] call CBA_fnc_globalEvent; // Ensure dead unit stays unconscious on all clients } else { From 5e91d077759374d3c486178f882275f8073df2bf Mon Sep 17 00:00:00 2001 From: Dystopian Date: Fri, 29 May 2026 16:54:37 +0300 Subject: [PATCH 10/10] Rework main loop after dev 153788: emptyPositions respects locked state --- .../common/functions/fnc_loadDeadPerson.sqf | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/addons/common/functions/fnc_loadDeadPerson.sqf b/addons/common/functions/fnc_loadDeadPerson.sqf index bd0792445a9..b1104818c5a 100644 --- a/addons/common/functions/fnc_loadDeadPerson.sqf +++ b/addons/common/functions/fnc_loadDeadPerson.sqf @@ -17,9 +17,9 @@ */ params ["_unit", "_vehicle"]; -TRACE_5("loadDeadPerson",_unit,_vehicle,typeOf _vehicle,local _unit,local _vehicle); +TRACE_4("loadDeadPerson",_unit,_vehicle,typeOf _vehicle,local _unit); -if (!local _vehicle) exitWith {ERROR_4("loadDeadPerson vehicle not local _unit=%1[%2] _vehicle=%3[%4]",_unit,typeOf _unit,_vehicle,typeOf _vehicle)}; +if (!local _vehicle) exitWith {ERROR_4("loadDeadPerson vehicle not local; unit=%1[%2] vehicle=%3[%4]",_unit,typeOf _unit,_vehicle,typeOf _vehicle)}; #define PRIORITY_NONE -1 #define PRIORITY_DRIVER 0 @@ -90,21 +90,22 @@ private _bestSeatsParams = []; } forEach _emptySeats; if (_bestSeatsPriority == PRIORITY_NONE) exitWith { - TRACE_2("No seats found",_emptySeats apply {_x select [ARR_2(1,4)]},fullCrew _vehicle apply {_x select [ARR_2(0,5)]}); + TRACE_2("No seats found",_emptySeats apply {_x select [ARR_2(1,3)]},fullCrew _vehicle apply {_x select [ARR_2(0,4)]}); }; -TRACE_3("emptySeats",_bestSeatsPriority,_bestSeatsRole,_bestSeatsParams); +private _moveInAnyPosition = toUpper _bestSeatsRole; +private _emptyPositions = _vehicle emptyPositions [_moveInAnyPosition]; +TRACE_4("emptySeats",_bestSeatsPriority,_bestSeatsRole,_bestSeatsParams,_emptyPositions); // Probe seat positions using temporary units to identify exact seat private _seatHolders = []; -private _moveInAnyPosition = toUpper _bestSeatsRole; -private _remainingEmptyPositions = _vehicle emptyPositions [_moveInAnyPosition]; // Guard against infinite loop -while {_remainingEmptyPositions > 0} do { + +while {_emptyPositions > 1} do { private _seatHolder = createVehicleLocal [QGVAR(seatHolder), [0,0,0], [], 0, "CAN_COLLIDE"]; private _seatHolderMoveSuccess = _seatHolder moveInAny [_vehicle, [_moveInAnyPosition]]; private _seatHolderSeat = fullCrew _vehicle select {_x select 0 == _seatHolder}; if (!_seatHolderMoveSuccess || {_seatHolderSeat isEqualTo []}) then { - ERROR_8("moveInAny holder failed _unit=%1[%2] _vehicle=%3[%4] _seatHolder=%5 _seatHolderMoveSuccess=%6 fullCrew=%7 %8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_seatHolder,_seatHolderMoveSuccess,fullCrew _vehicle apply {_x select [ARR_2(0,5)]},__FILE__); + ERROR_8("moveInAny holder failed unit=%1[%2] vehicle=%3[%4] pos=%5 seatHolder=%6 success=%7 fullCrew=%8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_moveInAnyPosition,_seatHolder,_seatHolderMoveSuccess,fullCrew _vehicle apply {_x select [ARR_2(0,4)]}); _vehicle deleteVehicleCrew _seatHolder; if (!isNull _seatHolder) then { // failsafe moveOut _seatHolder; @@ -112,21 +113,24 @@ while {_remainingEmptyPositions > 0} do { }; break; }; - _seatHolderSeat select 0 params ["", "_role", "_cargoIndex", "_turretPath"]; + _seatHolderSeat select 0 params ["", "", "_cargoIndex", "_turretPath"]; + TRACE_3("seatHolder",_emptyPositions,_cargoIndex,_turretPath); if ([_cargoIndex, _turretPath] in _bestSeatsParams) then { _vehicle deleteVehicleCrew _seatHolder; - private _unitMoveSuccess = _unit moveInAny [_vehicle, [_moveInAnyPosition]]; - if (_unitMoveSuccess) then { - [QGVAR(deadPersonLoaded), _unit] call CBA_fnc_globalEvent; // Ensure dead unit stays unconscious on all clients - } else { - ERROR_8("moveInAny unit failed _unit=%1[%2] _vehicle=%3[%4] _seatHolder=%5 _seatHolderSeat=%6 fullCrew=%7 %8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_seatHolder,_seatHolderSeat,fullCrew _vehicle apply {_x select [ARR_2(0,5)]},__FILE__); - }; - TRACE_4("seat",_remainingEmptyPositions,_role,_cargoIndex,_turretPath); + _emptyPositions = 1; break; }; - TRACE_4("seatHolder",_remainingEmptyPositions,_role,_cargoIndex,_turretPath); _seatHolders pushBack _seatHolder; - DEC(_remainingEmptyPositions); + DEC(_emptyPositions); +}; + +if (_emptyPositions == 1) then { // need this check in case of seatHolder moveInAny fail + private _unitMoveSuccess = _unit moveInAny [_vehicle, [_moveInAnyPosition]]; + if (_unitMoveSuccess) then { + [QGVAR(deadPersonLoaded), _unit] call CBA_fnc_globalEvent; // Ensure dead unit stays unconscious on all clients + } else { + ERROR_8("moveInAny unit failed unit=%1[%2] vehicle=%3[%4] pos=%5 local=%6 seatHolders=%7 fullCrew=%8",_unit,typeOf _unit,_vehicle,typeOf _vehicle,_moveInAnyPosition,local _unit,_seatHolders,fullCrew _vehicle apply {_x select [ARR_2(0,4)]}); + }; }; {