#include "..\script_component.hpp" /* * Author: commy2, kymckay, LinkIsGrim * HandleDamage EH where wound events are raised based on incoming damage. * Be aware that for each source of damage, the EH can fire multiple times (once for each hitpoint). * We store these incoming damages and compare them on last iteration of the event (_context == 2). * * Arguments: * Handle damage EH * * Return Value: * Damage to be inflicted * * Public: No */ params ["_unit", "_selection", "_damage", "_shooter", "_ammo", "_hitPointIndex", "_instigator", "_hitpoint", "_directHit", "_context"]; // HD sometimes triggers for remote units - ignore. if !(local _unit) exitWith {nil}; // Get missing meta info private _oldDamage = 0; private _structuralDamage = _context == 0; if (_structuralDamage) then { _hitPoint = "#structural"; _oldDamage = damage _unit; } else { _oldDamage = _unit getHitIndex _hitPointIndex; }; // Damage can be disabled with old variable or via sqf command allowDamage if !(isDamageAllowed _unit && {_unit getVariable [QEGVAR(medical,allowDamage), true]}) exitWith {_oldDamage}; private _newDamage = _damage - _oldDamage; // _newDamage == 0 happens occasionally for vehiclehit events (see line 80 onwards), just exit early to save some frametime // context 4 is engine "bleeding". For us, it's just a duplicate event for #structural which we can ignore without any issues if (_context != 2 && {_context == 4 || _newDamage == 0}) exitWith { TRACE_4("Skipping engine bleeding or zero damage",_ammo,_newDamage,_directHit,_context); _oldDamage }; // Get scaled armor value of hitpoint and calculate damage before armor // We scale using passThrough to handle explosive-resistant armor properly (#9063) // We need realDamage to determine which limb was hit correctly [_unit, _hitpoint] call FUNC(getHitpointArmor) params ["_armor", "_armorScaled"]; private _realDamage = _newDamage * _armor; if (!_structuralDamage) then { private _armorCoef = _armor/_armorScaled; private _damageCoef = linearConversion [0, 1, GVAR(damagePassThroughEffect), 1, _armorCoef]; _newDamage = _newDamage * _damageCoef; }; TRACE_6("Received hit",_hitpoint,_ammo,_newDamage,_realDamage,_directHit,_context); // Drowning doesn't fire the EH for each hitpoint and never triggers _context=2 (LastHitPoint) // Damage occurs in consistent increments if ( _structuralDamage && {getOxygenRemaining _unit <= 0.5} && {_damage isEqualTo (_oldDamage + 0.005)} ) exitWith { TRACE_5("Drowning",_unit,_shooter,_instigator,_damage,_newDamage); [QEGVAR(medical,woundReceived), [_unit, [[_newDamage, "Body", _newDamage]], _unit, "drowning"]] call CBA_fnc_localEvent; 0 }; // Faster than (vehicle _unit), also handles dead units private _vehicle = objectParent _unit; private _inVehicle = !isNull _vehicle; private _environmentDamage = _ammo == ""; // Crashing a vehicle doesn't fire the EH for each hitpoint and never triggers _context=2 (LastHitPoint) // It does fire the EH multiple times, but this seems to scale with the intensity of the crash if ( EGVAR(medical,enableVehicleCrashes) && {_environmentDamage && _inVehicle && _structuralDamage} && {vectorMagnitude (velocity _vehicle) > 5} // todo: no way to detect if stationary and another vehicle hits you ) exitWith { TRACE_5("Crash",_unit,_shooter,_instigator,_damage,_newDamage); [QEGVAR(medical,woundReceived), [_unit, [[_newDamage, _hitPoint, _newDamage]], _unit, "vehiclecrash"]] call CBA_fnc_localEvent; 0 }; // Receiving explosive damage inside a vehicle doesn't trigger for each hitpoint // This is the case for mines, explosives, artillery, and catasthrophic vehicle explosions if ( (!_environmentDamage && _inVehicle && _structuralDamage) && { private _ammoCfg = configFile >> "CfgAmmo" >> _ammo; GET_NUMBER(_ammoCfg >> "explosive",0) > 0 || {GET_NUMBER(_ammoCfg >> "indirectHit",0) > 0} } ) exitwith { TRACE_5("Vehicle hit",_unit,_shooter,_instigator,_damage,_newDamage); _unit setVariable [QEGVAR(medical,lastDamageSource), _shooter]; _unit setVariable [QEGVAR(medical,lastInstigator), _instigator]; [QEGVAR(medical,woundReceived), [_unit, [[_newDamage, _hitPoint, _newDamage]], _shooter, "vehiclehit"]] call CBA_fnc_localEvent; 0 }; // Damages are stored for last iteration of the HandleDamage event (_context == 2) _unit setVariable [format [QGVAR($%1), _hitPoint], [_realDamage, _newDamage]]; // Ref https://community.bistudio.com/wiki/Arma_3:_Event_Handlers#HandleDamage // Context 2 means this is the last iteration of HandleDamage, so figure out which hitpoint took the most real damage and send wound event // Don't exit, as the last iteration can be one of the hitpoints that we need to keep _oldDamage for if (_context == 2) then { _unit setVariable [QEGVAR(medical,lastDamageSource), _shooter]; _unit setVariable [QEGVAR(medical,lastInstigator), _instigator]; private _damageStructural = _unit getVariable [QGVAR($#structural), [0,0]]; // --- Head private _damageHead = [ _unit getVariable [QGVAR($HitFace), [0,0]], _unit getVariable [QGVAR($HitNeck), [0,0]], _unit getVariable [QGVAR($HitHead), [0,0]] ]; _damageHead sort false; _damageHead = _damageHead select 0; // --- Body private _damageBody = [ _unit getVariable [QGVAR($HitPelvis), [0,0]], _unit getVariable [QGVAR($HitAbdomen), [0,0]], _unit getVariable [QGVAR($HitDiaphragm), [0,0]], _unit getVariable [QGVAR($HitChest), [0,0]] // HitBody removed as it's a placeholder hitpoint and the high armor value (1000) throws the calculations off ]; _damageBody sort false; _damageBody = _damageBody select 0; // --- Arms and Legs private _damageLeftArm = _unit getVariable [QGVAR($HitLeftArm), [0,0]]; private _damageRightArm = _unit getVariable [QGVAR($HitRightArm), [0,0]]; private _damageLeftLeg = _unit getVariable [QGVAR($HitLeftLeg), [0,0]]; private _damageRightLeg = _unit getVariable [QGVAR($HitRightLeg), [0,0]]; // Find hit point that received the maximum damage // Priority used for sorting if incoming damage is equal // _realDamage, priority, _newDamage, body part name private _allDamages = [ [_damageHead select 0, PRIORITY_HEAD, _damageHead select 1, "Head"], [_damageBody select 0, PRIORITY_BODY, _damageBody select 1, "Body"], [_damageLeftArm select 0, PRIORITY_LEFT_ARM, _damageLeftArm select 1, "LeftArm"], [_damageRightArm select 0, PRIORITY_RIGHT_ARM, _damageRightArm select 1, "RightArm"], [_damageLeftLeg select 0, PRIORITY_LEFT_LEG, _damageLeftLeg select 1, "LeftLeg"], [_damageRightLeg select 0, PRIORITY_RIGHT_LEG, _damageRightLeg select 1, "RightLeg"], [_damageStructural select 0, PRIORITY_STRUCTURAL, _damageStructural select 1, "#structural"] ]; TRACE_2("incoming",_allDamages,_damageStructural); _allDamages sort false; _allDamages = _allDamages apply {[_x select 2, _x select 3, _x select 0]}; // Environmental damage sources all have empty ammo string // No explicit source given, we infer from differences between them if (_environmentDamage) then { // Any collision with terrain/vehicle/object has a shooter // Check this first because burning can happen at any velocity if !(isNull _shooter) then { /* If shooter != unit then they hit unit, otherwise it could be: - Unit hitting anything at speed - An empty vehicle hitting unit - A physX object hitting unit Assume fall damage for downward velocity because it's most common */ if (_shooter == _unit && {(velocity _unit select 2) < -2}) then { _ammo = "falling"; TRACE_5("Fall",_unit,_shooter,_instigator,_damage,_allDamages); } else { _ammo = "collision"; TRACE_5("Collision",_unit,_shooter,_instigator,_damage,_allDamages); }; } else { // Anything else is almost guaranteed to be fire damage _ammo = "fire"; TRACE_5("Fire Damage",_unit,_shooter,_instigator,_damage,_allDamages); }; }; // No wounds for minor damage // TODO check if this needs to be changed for burning damage (occurs as lots of small events that we add together) if ((_allDamages select 0 select 0) > 1E-3) then { TRACE_1("received",_allDamages); [QEGVAR(medical,woundReceived), [_unit, _allDamages, _shooter, _ammo]] call CBA_fnc_localEvent; }; // Clear stored damages otherwise they will influence future damage events // (aka wounds will pile onto the historically most damaged hitpoint) { _unit setVariable [_x, nil]; } forEach [ QGVAR($HitFace),QGVAR($HitNeck),QGVAR($HitHead), QGVAR($HitPelvis),QGVAR($HitAbdomen),QGVAR($HitDiaphragm),QGVAR($HitChest),QGVAR($HitBody), QGVAR($HitLeftArm),QGVAR($HitRightArm),QGVAR($HitLeftLeg),QGVAR($HitRightLeg), QGVAR($#structural) ]; }; // Engine damage to these hitpoints controls blood visuals, limping, weapon sway // Handled in fnc_damageBodyPart, persist here // For all other hitpoints, we store our own damage values, so engine damage is unnecessary [0, _oldDamage] select (_hitPoint in ["hithead", "hitbody", "hithands", "hitlegs"])