Skip to content
9 changes: 9 additions & 0 deletions addons/common/CfgVehicles.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <DefaultItems.hpp>
class Logic;
Expand Down
1 change: 1 addition & 0 deletions addons/common/XEH_PREP.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ PREP(isModLoaded);
PREP(isPlayer);
PREP(isSwimming);
PREP(lightIntensityFromObject);
PREP(loadDeadPerson);
PREP(loadPerson);
PREP(loadPersonLocal);
PREP(moduleCheckPBOs);
Expand Down
17 changes: 17 additions & 0 deletions addons/common/XEH_postInit.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
138 changes: 138 additions & 0 deletions addons/common/functions/fnc_loadDeadPerson.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#include "..\script_component.hpp"
/*
* Author: Dystopian
* Loads dead person into most suitable seat in vehicle.
*
* Arguments:
* 0: Unit <OBJECT>
* 1: Vehicle <OBJECT>
*
* Return Value:
* None
*
* Example:
* [cursorObject, vehicle player] call ace_common_fnc_loadDeadPerson
*
* Public: No
*/

params ["_unit", "_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)};

#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 {
if (_priority == PRIORITY_CARGO) then {
_bestSeatsParams = [[_cargoIndex, _turretPath]]; // use only the last cargo seat
} else {
_bestSeatsParams pushBack [_cargoIndex, _turretPath];
};
};
} forEach _emptySeats;

if (_bestSeatsPriority == PRIORITY_NONE) exitWith {
TRACE_2("No seats found",_emptySeats apply {_x select [ARR_2(1,3)]},fullCrew _vehicle apply {_x select [ARR_2(0,4)]});
};

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 = [];

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] 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;
deleteVehicle _seatHolder;
};
break;
};
_seatHolderSeat select 0 params ["", "", "_cargoIndex", "_turretPath"];
TRACE_3("seatHolder",_emptyPositions,_cargoIndex,_turretPath);
if ([_cargoIndex, _turretPath] in _bestSeatsParams) then {
_vehicle deleteVehicleCrew _seatHolder;
_emptyPositions = 1;
break;
};
_seatHolders pushBack _seatHolder;
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)]});
};
};

{
_vehicle deleteVehicleCrew _x;
} forEach _seatHolders;
8 changes: 7 additions & 1 deletion addons/common/functions/fnc_loadPerson.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
41 changes: 29 additions & 12 deletions addons/common/functions/fnc_nearestVehiclesFreeSeat.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,44 @@
* 0: Unit <OBJECT>
* 1: Distance <NUMBER> (default: 10)
* 2: Restricted to cargo only <BOOL> (default: false)
* 3: Override vehicle to check instead of distance search <OBJECT> (default: objNull)
*
* Return Value:
* Nearest vehicles with a free seat <ARRAY>
*
* 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
}
}
3 changes: 2 additions & 1 deletion addons/dragging/functions/fnc_carryObjectPFH.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -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)} &&
Expand Down
13 changes: 9 additions & 4 deletions addons/dragging/functions/fnc_dropObject_carry.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion addons/dragging/script_component.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
6 changes: 1 addition & 5 deletions addons/medical_treatment/functions/fnc_loadUnit.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand Down
17 changes: 0 additions & 17 deletions addons/medical_treatment/stringtable.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1950,23 +1950,6 @@
<Turkish>Bu kişi (% 1) uyanık ve yüklenemiyor</Turkish>
<Ukrainian>Боєць (%1) у свідомості і не може бути завантажений</Ukrainian>
</Key>
<Key ID="STR_ACE_Medical_Treatment_CanNotLoadDead">
<English>This person (%1) is dead and cannot be loaded</English>
<Czech>Tato osoba (%1) je mrtvá a nemůže být naložena</Czech>
<French>%1 est mort et ne peut être embarqué.</French>
<Spanish>Esta persona (%1) está muerta y no puede ser cargado</Spanish>
<Italian>Questa persona (%1) è morta e non può essere caricata.</Italian>
<Polish>Ta osoba (%1) jest martwa i nie może zostać załadowana</Polish>
<Portuguese>Esta pessoa (%1) está morta e não pode ser carregada</Portuguese>
<Russian>Боец (%1) мертв и не может быть погружен</Russian>
<German>Diese Person (%1) ist tot und kann nicht verladen werden</German>
<Korean>이 사람 (%1) 은(는) 사망하여 태우지 못합니다</Korean>
<Japanese>患者 (%1) は死亡しており、積み込めない</Japanese>
<Chinese>此人(%1)已死亡,无法被装载</Chinese>
<Chinesesimp>此人(%1)已死亡,无法被装载</Chinesesimp>
<Turkish>Bu kişi (%1) ölü ve yüklenemiyor</Turkish>
<Ukrainian>Боєць (%1) мертвий і не може бути завантажений</Ukrainian>
</Key>
<Key ID="STR_ACE_Medical_Treatment_Carry">
<English>Carry</English>
<Czech>Nést</Czech>
Expand Down
Loading