Medical Engine - Apply damage to the correct hitpoint (#7415)

* HandleDamage uses armour values to determine which hitpoint was damaged

* Tidied up comments

* Newlines

* Tabs? In MY code?!

* Added uniform caching and option to force disable caching

* Review suggestions

* Review suggestions/code style

* Spelling and select

* Removed unnecessary validity check

* Apply suggestions from code review

Co-Authored-By: commy2 <commy-2@gmx.de>

* Tweaks and optimisations, removed _noCache

Also fixed cache nil vs empty

* Different approach with fewer loops
Lookup is now done per-hitpoint and default values cached
fnc_getItemArmor made a helper function as it's now only a few lines

* Tabs & newlines

* Moved uniform logic inside helper function

* Optimisations

* Tweaks & optimisations, improved formatting

* Ignore explosionShielding

* Moved getArmor back to separate func, add per-unit caching

* Formatting

* Review suggestions

Co-authored-by: commy2 <commy-2@gmx.de>
This commit is contained in:
pterolatypus 2020-02-28 16:54:53 +00:00 committed by GitHub
parent f069ac4c92
commit c949a07c83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 140 additions and 27 deletions

View File

@ -4,3 +4,5 @@ PREP(updateBodyPartVisuals);
PREP(updateDamageEffects);
PREP(setStructuralDamage);
PREP(setUnconsciousAnim);
PREP(getHitpointArmor);
PREP(getItemArmor);

View File

@ -28,6 +28,9 @@ if (isNil QUOTE(FATAL_SUM_DAMAGE_WEIBULL_K) || isNil QUOTE(FATAL_SUM_DAMAGE_WEIB
FATAL_SUM_DAMAGE_WEIBULL_L = _x1 / _b1^(1/FATAL_SUM_DAMAGE_WEIBULL_K);
};
// Cache for armor values of equipped items (vests etc)
GVAR(armorCache) = false call CBA_fnc_createNamespace;
// Hack for #3168 (units in static weapons do not take any damage):
// Doing a manual pre-load with a small distance seems to fix the LOD problems
// with handle damage not returning full results.

View File

@ -0,0 +1,47 @@
#include "script_component.hpp"
/*
* Author: Pterolatypus
* Checks a unit's equipment to calculate the total armor on a hitpoint.
*
* Arguments:
* 0: Unit <OBJECT>
* 1: Hitpoint <STRING>
*
* Return Value:
* Total armor for the given hitpoint <NUMBER>
*
* Example:
* [player, "HitChest"] call ace_medical_engine_fnc_getHitpointArmor
*
* Public: No
*/
params ["_unit", "_hitpoint"];
private _uniform = uniform _unit;
// If unit is naked, use its underwear class instead
if (_uniform isEqualTo "") then {
_uniform = getText (configFile >> "CfgVehicles" >> typeOf _unit >> "nakedUniform");
};
private _gear = [
_uniform,
vest _unit,
headgear _unit
];
private _rags = _gear joinString "$";
private _var = format [QGVAR(armorCache$%1), _hitpoint];
_unit getVariable [_var, [""]] params ["_prevRags", "_armor"];
if (_rags != _prevRags) then {
_armor = 0;
{
_armor = _armor + ([_x, _hitpoint] call FUNC(getItemArmor));
} forEach _gear;
_unit setVariable [_var, [_rags, _armor]];
};
_armor // return

View File

@ -0,0 +1,48 @@
#include "script_component.hpp"
/*
* Author: Pterolatypus
* Returns the armor value the given item provides to a particular hitpoint, either from a cache or by reading the item config.
*
* Arguments:
* 0: Item Class <STRING>
* 1: Hitpoint <STRING>
*
* Return Value:
* Item armor for the given hitpoint <NUMBER>
*
* Example:
* ["V_PlateCarrier_rgr", "HitChest"] call ace_medical_engine_fnc_getItemArmor
*
* Public: No
*/
params ["_item", "_hitpoint"];
private _key = format ["%1$%2", _item, _hitpoint];
private _armor = GVAR(armorCache) getVariable _key;
if (isNil "_armor") then {
TRACE_2("Cache miss",_item,_hitpoint);
if ("" in [_item, _hitpoint]) exitWith {
_armor = 0;
GVAR(armorCache) setVariable [_key, _armor];
};
private _itemInfo = configFile >> "CfgWeapons" >> _item >> "ItemInfo";
if (getNumber (_itemInfo >> "type") == TYPE_UNIFORM) then {
private _unitCfg = configFile >> "CfgVehicles" >> getText (_itemInfo >> "uniformClass");
private _entry = _unitCfg >> "HitPoints" >> _hitpoint;
_armor = getNumber (_unitCfg >> "armor") * getNumber (_entry >> "armor")
} else {
private _condition = format ["getText (_x >> 'hitpointName') == '%1'", _hitpoint];
private _entry = configProperties [_itemInfo >> "HitpointsProtectionInfo", _condition] param [0, configNull];
_armor = getNumber (_entry >> "armor");
};
GVAR(armorCache) setVariable [_key, _armor];
};
_armor // return

View File

@ -34,9 +34,13 @@ if (_hitPoint isEqualTo "") then {
// Damage can be disabled with old variable or via sqf command allowDamage
if !(isDamageAllowed _unit && {_unit getVariable [QEGVAR(medical,allowDamage), true]}) exitWith {_oldDamage};
// Damages are stored for "ace_hdbracket" event triggered last
private _newDamage = _damage - _oldDamage;
_unit setVariable [format [QGVAR($%1), _hitPoint], _newDamage];
// Get armor value of hitpoint and calculate damage before armor
private _armor = [_unit, _hitpoint] call FUNC(getHitpointArmor);
private _realDamage = _newDamage * _armor;
// Damages are stored for "ace_hdbracket" event triggered last
_unit setVariable [format [QGVAR($%1), _hitPoint], [_realDamage, _newDamage]];
TRACE_3("Received hit",_hitpoint,_newDamage,_realDamage);
// Engine damage to these hitpoints controls blood visuals, limping, weapon sway
// Handled in fnc_damageBodyPart, persist here
@ -51,52 +55,61 @@ if (_hitPoint isEqualTo "ace_hdbracket") exitWith {
private _damageStructural = _unit getVariable [HIT_STRUCTURAL, 0];
// --- Head
private _damageFace = _unit getVariable [QGVAR($HitFace), 0];
private _damageNeck = _unit getVariable [QGVAR($HitNeck), 0];
private _damageHead = (_unit getVariable [QGVAR($HitHead), 0]) max _damageFace max _damageNeck;
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 _damagePelvis = _unit getVariable [QGVAR($HitPelvis), 0];
private _damageAbdomen = _unit getVariable [QGVAR($HitAbdomen), 0];
private _damageDiaphragm = _unit getVariable [QGVAR($HitDiaphragm), 0];
private _damageChest = _unit getVariable [QGVAR($HitChest), 0];
private _damageBody = (_unit getVariable [QGVAR($HitBody), 0]) max _damagePelvis max _damageAbdomen max _damageDiaphragm max _damageChest;
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];
private _damageRightArm = _unit getVariable [QGVAR($HitRightArm), 0];
private _damageLeftLeg = _unit getVariable [QGVAR($HitLeftLeg), 0];
private _damageRightLeg = _unit getVariable [QGVAR($HitRightLeg), 0];
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 maxium damage
// Priority used for sorting if incoming damage is equivalent (e.g. max which is 4)
private _allDamages = [
[_damageHead, PRIORITY_HEAD, "Head"],
[_damageBody, PRIORITY_BODY, "Body"],
[_damageLeftArm, PRIORITY_LEFT_ARM, "LeftArm"],
[_damageRightArm, PRIORITY_RIGHT_ARM, "RightArm"],
[_damageLeftLeg, PRIORITY_LEFT_LEG, "LeftLeg"],
[_damageRightLeg, PRIORITY_RIGHT_LEG, "RightLeg"]
_damageHead + [PRIORITY_HEAD, "Head"],
_damageBody + [PRIORITY_BODY, "Body"],
_damageLeftArm + [PRIORITY_LEFT_ARM, "LeftArm"],
_damageRightArm + [PRIORITY_RIGHT_ARM, "RightArm"],
_damageLeftLeg + [PRIORITY_LEFT_LEG, "LeftLeg"],
_damageRightLeg + [PRIORITY_RIGHT_LEG, "RightLeg"]
];
TRACE_2("incoming",_allDamages,_damageStructural);
// represents all incoming damage for selecting a non-selectionSpecific wound location, (used for selectRandomWeighted [value1,weight1,value2....])
private _damageSelectionArray = [
HITPOINT_INDEX_HEAD, _damageHead, HITPOINT_INDEX_BODY, _damageBody, HITPOINT_INDEX_LARM, _damageLeftArm,
HITPOINT_INDEX_RARM, _damageRightArm, HITPOINT_INDEX_LLEG, _damageLeftLeg, HITPOINT_INDEX_RLEG, _damageRightLeg
HITPOINT_INDEX_HEAD, _damageHead select 1, HITPOINT_INDEX_BODY, _damageBody select 1, HITPOINT_INDEX_LARM, _damageLeftArm select 1,
HITPOINT_INDEX_RARM, _damageRightArm select 1, HITPOINT_INDEX_LLEG, _damageLeftLeg select 1, HITPOINT_INDEX_RLEG, _damageRightLeg select 1
];
_allDamages sort false;
(_allDamages select 0) params ["_receivedDamage", "", "_woundedHitPoint"];
if (_damageHead >= HEAD_DAMAGE_THRESHOLD) then {
TRACE_3("reporting fatal head damage instead of max",_damageHead,_receivedDamage,_woundedHitPoint);
_receivedDamage = _damageHead;
(_allDamages select 0) params ["", "_receivedDamage", "", "_woundedHitPoint"];
private _receivedDamageHead = _damageHead select 1;
if (_receivedDamageHead >= HEAD_DAMAGE_THRESHOLD) then {
TRACE_3("reporting fatal head damage instead of max",_receivedDamageHead,_receivedDamage,_woundedHitPoint);
_receivedDamage = _receivedDamageHead;
_woundedHitPoint = "Head";
};
// We know it's structural when no specific hitpoint is damaged
if (_receivedDamage == 0) then {
_receivedDamage = _damageStructural;
_receivedDamage = _damageStructural select 1;
_woundedHitPoint = "Body";
_damageSelectionArray = [1, 1]; // sum of weights would be 0
};