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/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..655e4fd003e --- /dev/null +++ b/addons/common/functions/fnc_loadDeadPerson.sqf @@ -0,0 +1,131 @@ +#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 + +// 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 = []; +{ + _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 apply {_x select [ARR_2(1,4)]},fullCrew _vehicle apply {_x select [ARR_2(0,5)]}); +}; + +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 +while {_remainingEmptyPositions > 0} do { + 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__); + _vehicle deleteVehicleCrew _seatHolder; + if (!isNull _seatHolder) then { // failsafe + moveOut _seatHolder; + deleteVehicle _seatHolder; + }; + break; + }; + _seatHolderSeat select 0 params ["", "_role", "_cargoIndex", "_turretPath"]; + if (_role == _bestSeatsRole && {[_cargoIndex, _turretPath] in _bestSeatsParams}) then { + _vehicle deleteVehicleCrew _seatHolder; + 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 { + 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; + }; + 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..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,20 +15,36 @@ * 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 { - // 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..18542374ec3 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, 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 52d5d36c958..436301d7755 100644 --- a/addons/dragging/functions/fnc_dropObject_carry.sqf +++ b/addons/dragging/functions/fnc_dropObject_carry.sqf @@ -125,10 +125,15 @@ _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 { + // can't search nearest vehicles because target position can be desynced ATM + private _vehicles = [_target, nil, nil, _cursorObject] call EFUNC(common,nearestVehiclesFreeSeat); + + if (_vehicles isEqualTo [_cursorObject]) 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