-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathfnc_handleDamage.sqf
More file actions
273 lines (233 loc) · 13.8 KB
/
fnc_handleDamage.sqf
File metadata and controls
273 lines (233 loc) · 13.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#include "..\script_component.hpp"
/*
* Author: commy2, kymckay, modified by johnb43
* Original:
* 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 our final hitpoint: "ace_hdbracket".
* Added:
* Handling of damage to allow armor modifcation. For ACE 3.18.0 and later.
*
* Arguments:
* Handle damage EH
*
* Return Value:
* Damage to be inflicted <NUMBER>
*
* Public: No
*/
#define INSTAKILL_ALLOWED(unit) (unit isNotEqualTo (unit getVariable ["ace_medical_engine_blockInstaKill", objNull]))
params ["_args", ["_ignoreAllowDamageACE", false]];
_args 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 (_hitPoint isEqualTo "") 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 ["ace_medical_allowDamage", true] || _ignoreAllowDamageACE}) exitWith {_oldDamage};
// Killing units via End key is an edge case (#10375)
// This didn't matter pre-Arma 3 2.18 but now this goes through the event handler
// TODO: Structural fire damage >= 1 in a single damage event could still be caught here and we don't want that, but we haven't found a better way to catch this, fire damage should be small most of the time anyway
// Also triggers for catastrophic vehicle explosions which would kill crew outright, check for blocking
private _newDamage = _damage - _oldDamage;
if (_structuralDamage && {(abs (_newDamage - 1)) < 0.001 && _ammo == "" && isNull _shooter && isNull _instigator} && {INSTAKILL_ALLOWED(_unit)}) exitWith {_damage};
// _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
// Leverage this to block insta-kills on the same frame (see above)
if (_context != 2 && {_context == 4 || _newDamage == 0}) exitWith {
TRACE_4("Skipping engine bleeding or zero damage",_ammo,_newDamage,_directHit,_context);
if (INSTAKILL_ALLOWED(_unit)) then {
_unit setVariable ["ace_medical_engine_blockInstaKill", _unit];
[{_this setVariable ["ace_medical_engine_blockInstaKill", nil]}, _unit] call CBA_fnc_execNextFrame;
};
_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
private _armor = [_unit, _hitpoint] call FUNC(getHitpointArmor);
private _realDamage = _newDamage * _armor;
TRACE_6("Received hit",_hitpoint,_ammo,_newDamage,_realDamage,_directHit,_context);
// Drowning doesn't fire the EH for each hitpoint so the "ace_hdbracket" code never runs
// 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);
["ace_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 so the "ace_hdbracket" code never runs
// It does fire the EH multiple times, but this seems to scale with the intensity of the crash
if (
ace_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);
["ace_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 ["ace_medical_lastDamageSource", _shooter];
_unit setVariable ["ace_medical_lastInstigator", _instigator];
["ace_medical_woundReceived", [_unit, [[_newDamage, _hitPoint, _newDamage]], _shooter, "vehiclehit"]] call CBA_fnc_localEvent;
0
};
// Get setting for particular unit
private _multiplierArray = switch (true) do {
case (_hitPoint in ["hitface", "hitneck", "hithead"]): {
_unit getVariable [QGVAR(hitPointMultiplier_head), [GVAR(hitPointMultiplier_ai_head), GVAR(hitPointMultiplier_player_head)] select (isPlayer _unit)]
};
case (_hitPoint in ["hitpelvis" ,"hitabdomen", "hitdiaphragm", "hitchest"]): {
_unit getVariable [QGVAR(hitPointMultiplier_chest), [GVAR(hitPointMultiplier_ai_chest), GVAR(hitPointMultiplier_player_chest)] select (isPlayer _unit)]
};
case (_hitPoint in ["hitleftarm", "hitrightarm", "hitleftleg", "hitrightleg"]): {
_unit getVariable [QGVAR(hitPointMultiplier_limb), [GVAR(hitPointMultiplier_ai_limb), GVAR(hitPointMultiplier_player_limb)] select (isPlayer _unit)]
};
default {
DEFAULT_SETTINGS
};
};
private _modifiedNewDamage = _newDamage;
private _modifiedRealDamage = _realDamage;
// If default settings, we don't need to change anything, so skip calculcations and let ace handle damage
if (_multiplierArray isNotEqualTo DEFAULT_SETTINGS) then {
_multiplierArray params ["_hitPointMultiplier", "_armorMin", "_armorMax"];
switch (true) do {
case (_armorMin >= 1 && {_armor < _armorMin}): {
// This will decrease damage
_modifiedNewDamage = _newDamage * _armor / _armorMin;
_modifiedRealDamage = _realDamage * _armor / _armorMin;
TRACE_6("Under min armor",_armor,_armorMin,_newDamage,_modifiedNewDamage,_realDamage,_modifiedRealDamage);
};
case (_armorMax >= 1 && {_armor > _armorMax}): {
// This will increase damage
_modifiedNewDamage = _newDamage * _armor / _armorMax;
_modifiedRealDamage = _realDamage * _armor / _armorMax;
TRACE_6("Over max armor",_armor,_armorMax,_newDamage,_modifiedNewDamage,_realDamage,_modifiedRealDamage);
};
};
_modifiedNewDamage = _modifiedNewDamage / _hitPointMultiplier;
_modifiedRealDamage = _modifiedRealDamage / _hitPointMultiplier;
TRACE_5("Hitpoint damage multiplied",_armor,_newDamage,_modifiedNewDamage,_realDamage,_modifiedRealDamage);
};
// Damages are stored for last iteration of the HandleDamage event (_context == 2)
_unit setVariable [format ["ace_medical_engine_$%1", _hitPoint], [_realDamage, _newDamage, _modifiedRealDamage, _modifiedNewDamage]];
// 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 ["ace_medical_lastDamageSource", _shooter];
_unit setVariable ["ace_medical_lastInstigator", _instigator];
private _damageStructural = _unit getVariable ["ace_medical_engine_$#structural", [0,0,0,0]];
// --- Head
private _damageHead = [
_unit getVariable ["ace_medical_engine_$HitFace", [0,0,0,0]],
_unit getVariable ["ace_medical_engine_$HitNeck", [0,0,0,0]],
_unit getVariable ["ace_medical_engine_$HitHead", [0,0,0,0]]
];
_damageHead sort false;
_damageHead = _damageHead select 0;
// --- Body
private _damageBody = [
_unit getVariable ["ace_medical_engine_$HitPelvis", [0,0,0,0]],
_unit getVariable ["ace_medical_engine_$HitAbdomen", [0,0,0,0]],
_unit getVariable ["ace_medical_engine_$HitDiaphragm", [0,0,0,0]],
_unit getVariable ["ace_medical_engine_$HitChest", [0,0,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 ["ace_medical_engine_$HitLeftArm", [0,0,0,0]];
private _damageRightArm = _unit getVariable ["ace_medical_engine_$HitRightArm", [0,0,0,0]];
private _damageLeftLeg = _unit getVariable ["ace_medical_engine_$HitLeftLeg", [0,0,0,0]];
private _damageRightLeg = _unit getVariable ["ace_medical_engine_$HitRightLeg", [0,0,0,0]];
// Find hit point that received the maxium damage
// Priority used for sorting if incoming damage is equal
private _allDamages = [
// Real damage (ignoring armor), Actual damage (with armor), Real damage modified (ignoring armor), Modified damage (with armor)
[_damageHead select 0, PRIORITY_HEAD, _damageHead select 1, "Head", _damageHead param [2, _damageHead select 0], _damageHead param [3, _damageHead select 1]],
[_damageBody select 0, PRIORITY_BODY, _damageBody select 1, "Body", _damageBody param [2, _damageBody select 0], _damageBody param [3, _damageBody select 1]],
[_damageLeftArm select 0, PRIORITY_LEFT_ARM, _damageLeftArm select 1, "LeftArm", _damageLeftArm param [2, _damageLeftArm select 0], _damageLeftArm param [3, _damageLeftArm select 1]],
[_damageRightArm select 0, PRIORITY_RIGHT_ARM, _damageRightArm select 1, "RightArm", _damageRightArm param [2, _damageRightArm select 0], _damageRightArm param [3, _damageRightArm select 1]],
[_damageLeftLeg select 0, PRIORITY_LEFT_LEG, _damageLeftLeg select 1, "LeftLeg", _damageLeftLeg param [2, _damageLeftLeg select 0], _damageLeftLeg param [3, _damageLeftLeg select 1]],
[_damageRightLeg select 0, PRIORITY_RIGHT_LEG, _damageRightLeg select 1, "RightLeg", _damageRightLeg param [2, _damageRightLeg select 0], _damageRightLeg param [3, _damageRightLeg select 1]],
[_damageStructural select 0, PRIORITY_STRUCTURAL, _damageStructural select 1, "#structural", _damageStructural param [2, _damageStructural select 0], _damageStructural param [3, _damageStructural select 1]]
];
TRACE_2("incoming",_allDamages,_damageStructural);
_allDamages sort false;
// Use modified damages instead of initial ones
_allDamages = _allDamages apply {[_x select 5, _x select 3, _x select 4]};
// 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);
["ace_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 [
"ace_medical_engine_$HitFace","ace_medical_engine_$HitNeck","ace_medical_engine_$HitHead",
"ace_medical_engine_$HitPelvis","ace_medical_engine_$HitAbdomen","ace_medical_engine_$HitDiaphragm","ace_medical_engine_$HitChest","ace_medical_engine_$HitBody",
"ace_medical_engine_$HitLeftArm","ace_medical_engine_$HitRightArm","ace_medical_engine_$HitLeftLeg","ace_medical_engine_$HitRightLeg",
"ace_medical_engine_$#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"])