ACE3/TO_MERGE/agm/Medical/functions/fn_handleDamage.sqf

287 lines
11 KiB
Plaintext

/*
* Author: KoffeinFlummi
*
* Called when some dude gets shot. Or stabbed. Or blown up. Or pushed off a cliff. Or hit by a car. Or burnt. Or poisoned. Or gassed. Or cut. You get the idea.
*
* Arguments:
* 0: Unit that got hit (Object)
* 1: Name of the selection that was hit (String); "" for structural damage
* 2: Amount of damage inflicted (Number)
* 3: Shooter (Object); Null for explosion damage, falling, fire etc.
* 4: Projectile (Object or String)
*
* Return value:
* Damage value to be inflicted (optional)
*/
#define UNCONSCIOUSNESSTRESHOLD 0.6
#define PAINKILLERTRESHOLD 0.1
#define PAINLOSS 0.0001
#define BLOODTRESHOLD1 0.35
#define BLOODTRESHOLD2 0
#define BLOODLOSSRATE 0.04
#define ARMOURCOEF 2
private ["_unit", "_selectionName", "_damage", "_source", "_source", "_projectile", "_hitSelections", "_hitPoints", "_newDamage", "_found", "_cache_projectiles", "_cache_hitpoints", "_cache_damages"];
_unit = _this select 0;
_selectionName = _this select 1;
_damage = _this select 2;
_source = _this select 3;
_projectile = _this select 4;
if (!local _unit) exitWith {nil}; //if not local, then return value shouldn't have any effect
if (typeName _projectile == "OBJECT") then {
_projectile = typeOf _projectile;
};
// Prevent unnecessary processing
if (damage _unit >= 1) exitWith {};
_unit setVariable ["AGM_isDiagnosed", False, True];
// @todo: figure out if this still applies.
// For some reason, everything is backwards in MP,
// so we need to untangle some things.
// -- seems fixed as of v1.36
/*if (isMultiplayer) then {
_selectionName = switch (_selectionName) do {
case "hand_r" : {"leg_l"};
case "leg_r" : {"hand_l"};
case "legs" : {"hand_r"};
default {_selectionName};
};
};*/
// This seems to only show up in MP too, but since it doesn't
// collide with anything, I'll check it in SP as well.
if (_selectionName == "r_femur_hit") then {
_selectionName = "leg_r";
};
_hitSelections = ["head", "body", "hand_l", "hand_r", "leg_l", "leg_r"];
_hitPoints = ["HitHead", "HitBody", "HitLeftArm", "HitRightArm", "HitLeftLeg", "HitRightLeg"];
// If the damage is being weird, we just tell it to fuck off.
// (Returning 0 seems to cause issues though, so return 0.01)
if !(_selectionName in (_hitSelections + [""])) exitWith {0.01};
// Calculate change in damage.
_newDamage = _damage - (damage _unit);
if (_selectionName in _hitSelections) then {
_newDamage = _damage - (_unit getHitPointDamage (_hitPoints select (_hitSelections find _selectionName)));
};
// Finished with the current frame, reset variables
// Note: sometimes handleDamage spans over 2 or even 3 frames.
if (diag_frameno > (_unit getVariable ["AGM_Medical_FrameNo", -3]) + 2) then {
_unit setVariable ["AGM_Medical_FrameNo", diag_frameno];
_unit setVariable ["AGM_Medical_isFalling", False];
_unit setVariable ["AGM_Medical_Projectiles", []];
_unit setVariable ["AGM_Medical_HitPoints", []];
_unit setVariable ["AGM_Medical_Damages", []];
_unit setVariable ["AGM_Medical_PreventDeath", False];
if (([_unit] call AGM_Core_fnc_isPlayer) or _unit getVariable ["AGM_allowUnconscious", False]) then {
if (!(_unit getVariable ["AGM_isUnconscious", False]) and
{_unit getVariable ["AGM_Medical_PreventInstaDeath", AGM_Medical_PreventInstaDeath]}) then {
_unit setVariable ["AGM_Medical_PreventDeath", True];
};
if ((_unit getVariable ["AGM_isUnconscious", False]) and
{_unit getVariable ["AGM_Medical_PreventDeathWhileUnconscious", AGM_Medical_PreventDeathWhileUnconscious]}) then {
_unit setVariable ["AGM_Medical_PreventDeath", True];
};
};
};
_damage = _damage - _newDamage;
if !(_unit getVariable ["AGM_allowDamage", True]) exitWith {_damage max 0.01};
_newDamage = _newDamage * (_unit getVariable ["AGM_Medical_CoefDamage", AGM_Medical_CoefDamage]);
// Exclude falling damage to everything other than legs; reduce structural damage.
if (((velocity _unit) select 2 < -5) and (vehicle _unit == _unit)) then {
_unit setVariable ["AGM_Medical_isFalling", True];
};
if (_unit getVariable "AGM_Medical_isFalling" and !(_selectionName in ["", "leg_l", "leg_r"])) exitWith {
(_unit getHitPointDamage (_hitPoints select (_hitSelections find _selectionName))) max 0.01;
};
if (_unit getVariable "AGM_Medical_isFalling") then {
_newDamage = _newDamage * 0.7;
};
// Increase damage for kinetic penetrators for people inside vehicles
// to simulate hot spikey things flying around (generally unpleasant).
// (only if AGM_Armour is used)
if (isClass (configFile >> "CfgPatches" >> "AGM_Armour") and _projectile != "" and vehicle _unit != _unit) then {
_hit = getNumber (configFile >> "CfgAmmo" >> _projectile >> "hit");
if (_hit >= 100) then {
_hit = linearConversion [100, 1000, _hit, 0, ARMOURCOEF, True];
_newDamage = _newDamage * (1 + _hit);
};
};
// Make sure there's only one damaged selection per projectile per frame.
_cache_projectiles = _unit getVariable "AGM_Medical_Projectiles";
_cache_hitpoints = _unit getVariable "AGM_Medical_HitPoints";
_cache_damages = _unit getVariable "AGM_Medical_Damages";
if (_selectionName != "" and !(_unit getVariable "AGM_Medical_isFalling")) then {
if (_projectile in _cache_projectiles) then {
_index = _cache_projectiles find _projectile;
_otherDamage = (_cache_damages select _index);
if (_otherDamage > _newDamage) then {
_newDamage = 0;
} else {
_hitPoint = _cache_hitpoints select _index;
_restore = ((_unit getHitPointDamage _hitPoint) - _otherDamage) max 0;
_unit setHitPointDamage [_hitPoint, _restore];
// Make entry unfindable
_cache_projectiles set [_index, objNull];
_cache_projectiles pushBack _projectile;
_cache_hitpoints pushBack (_hitPoints select (_hitSelections find _selectionName));
_cache_damages pushBack _newDamage;
};
} else {
_cache_projectiles pushBack _projectile;
_cache_hitpoints pushBack (_hitPoints select (_hitSelections find _selectionName));
_cache_damages pushBack _newDamage;
};
};
_unit setVariable ["AGM_Medical_Projectiles", _cache_projectiles];
_unit setVariable ["AGM_Medical_HitPoints", _cache_hitpoints];
_unit setVariable ["AGM_Medical_Damages", _cache_damages];
// we want to move damage to another selection; have to do it ourselves.
// this is only the case for limbs, so this will not impact the killed EH.
if (_selectionName != (_this select 1)) then {
_unit setHitPointDamage [_hitPoints select (_hitSelections find _selectionName), _damage + _newDamage];
_newDamage = 0;
};
_damage = _damage + _newDamage;
// Assign orphan structural damage to torso;
// using spawn with custom damage handling here, but since I just
// move damage, this shouldn't be any issue for the Killed EH
_unit spawn {
sleep 0.001;
_damagesum = (_this getHitPointDamage "HitHead")
+ (_this getHitPointDamage "HitBody")
+ (_this getHitPointDamage "HitLeftArm")
+ (_this getHitPointDamage "HitRightArm")
+ (_this getHitPointDamage "HitLeftLeg")
+ (_this getHitPointDamage "HitRightLeg");
if (_damagesum <= 0.06 and (damage _this) > 0.01) then {
_damage = damage _this;
_this setDamage 0;
_this setHitPointDamage ["HitBody", (_damage min 0.89)]; // just to be sure.
};
};
// Leg & Arm Damage
_legdamage = (_unit getHitPointDamage "HitLeftLeg") + (_unit getHitPointDamage "HitRightLeg");
if (_selectionName == "leg_l") then {
_legdamage = _damage + (_unit getHitPointDamage "HitRightLeg");
};
if (_selectionName == "leg_r") then {
_legdamage = (_unit getHitPointDamage "HitLeftLeg") + _damage;
};
_armdamage = (_unit getHitPointDamage "HitLeftArm") + (_unit getHitPointDamage "HitRightArm");
if (_selectionName == "hand_l") then {
_armdamage = _damage + (_unit getHitPointDamage "HitRightArm");
};
if (_selectionName == "hand_r") then {
_armdamage = (_unit getHitPointDamage "HitLeftArm") + _damage;
};
[_unit, _legdamage, _armdamage] call AGM_Medical_fnc_checkDamage;
// Unconsciousness
if (_selectionName == "" and
_damage >= UNCONSCIOUSNESSTRESHOLD and
_damage < 1 and
!(_unit getVariable ["AGM_isUnconscious", False]
)) then {
// random chance to kill AI instead of knocking them out, otherwise
// there'd be shittons of unconscious people after every firefight,
// causing executions. And nobody likes executions.
if (_unit getVariable ["AGM_allowUnconscious", ([_unit] call AGM_Core_fnc_isPlayer) or random 1 > 0.5]) then {
[_unit] call AGM_Medical_fnc_knockOut;
} else {
_damage = 1;
};
};
// Bleeding
if (_selectionName == "" and damage _unit == 0) then {
_unit spawn {
while {damage _this > 0 and damage _this < 1} do {
if !([_this] call AGM_Medical_fnc_isInMedicalVehicle) then {
_blood = _this getVariable ["AGM_Blood", 1];
_blood = _blood - BLOODLOSSRATE * (_this getVariable ["AGM_Medical_CoefBleeding", AGM_Medical_CoefBleeding]) * (damage _this);
_this setVariable ["AGM_Blood", _blood max 0, true];
if (_blood <= BLOODTRESHOLD1 and !(_this getVariable ["AGM_isUnconscious", False])) then {
[_this] call AGM_Medical_fnc_knockOut;
};
if (_blood <= BLOODTRESHOLD2 and {!AGM_Medical_PreventDeathWhileUnconscious}) then {
//_this setDamage 1;
_this setHitPointDamage ["HitHead", 1]; // fx: don't get the uniform bloody if there are no wounds
};
};
sleep 10;
};
};
};
// Pain Reduction
if (_unit getVariable ["AGM_Pain", 0] == 0) then {
_unit spawn {
while {_this getVariable ["AGM_Pain", 0] > 0} do {
sleep 1;
_pain = ((_this getVariable ["AGM_Pain", 0]) - 0.001) max 0;
_this setVariable ["AGM_Pain", _pain, True];
};
};
};
// Set Pain
_potentialPain = _damage * (_unit getVariable ["AGM_Painkiller", 1]);
if ((_selectionName == "") and (_potentialPain > _unit getVariable ["AGM_Pain", 0])) then {
_unit setVariable ["AGM_Pain", (_damage * (_unit getVariable ["AGM_Painkiller", 1])) min 1, True];
};
// again, using spawn, but there shouldn't be any death, so the killed EH should be fine.
if ((_unit getVariable "AGM_Medical_PreventDeath") and {vehicle _unit != _unit} and {damage (vehicle _unit) >= 1}) then {
_unit setPosATL [
(getPos _unit select 0) + (random 3) - 1.5,
(getPos _unit select 1) + (random 3) - 1.5,
0
];
if !(_unit getVariable ["AGM_isUnconscious", False]) then {
[_unit] call AGM_Medical_fnc_knockOut;
};
_unit setVariable ["AGM_allowDamage", False];
_unit spawn {
sleep 1;
_this setVariable ["AGM_allowDamage", True];
};
};
if ((_unit getVariable "AGM_Medical_PreventDeath")) then {
if ((_damage > 0.89) && (_selectionName in ["", "head", "body"])) then { //only change damage on hits that would be lethal
_damage = 0.89;
[_unit, "preventedDeath", [_unit]] call AGM_Core_fnc_callCustomEventHandlersGlobal;
if (!(_unit getVariable ["AGM_isUnconscious", False])) then {
[_unit] call AGM_Medical_fnc_knockOut; //knockOut when taking damage that should be lethal
};
};
};
_damage