diff --git a/addons/medical_damage/ACE_Medical_Injuries.hpp b/addons/medical_damage/ACE_Medical_Injuries.hpp index e3ffb7bfdec..4f99044d0f1 100644 --- a/addons/medical_damage/ACE_Medical_Injuries.hpp +++ b/addons/medical_damage/ACE_Medical_Injuries.hpp @@ -80,6 +80,9 @@ class ACE_Medical_Injuries { // bullets only create multiple wounds when the damage is very high thresholds[] = {{20, 10}, {4.5, 2}, {3, 1}, {0, 1}}; selectionSpecific = 1; + class woundHandlers: woundHandlers { + GVAR(armorPenetration) = QFUNC(woundsHandlerArmorPenetration); + }; class Avulsion { // at damage, weight. between points, weight is interpolated then wound is chosen by weighted random. diff --git a/addons/medical_damage/XEH_PREP.hpp b/addons/medical_damage/XEH_PREP.hpp index a17e7d739c5..b0168821482 100644 --- a/addons/medical_damage/XEH_PREP.hpp +++ b/addons/medical_damage/XEH_PREP.hpp @@ -1,11 +1,13 @@ PREP(debug_explosiveTest); PREP(determineIfFatal); +PREP(getAmmoData); PREP(getTypeOfDamage); PREP(handleIncapacitation); PREP(interpolatePoints); PREP(parseConfigForInjuries); PREP(parseWoundHandlersCfg); PREP(woundReceived); +PREP(woundsHandlerArmorPenetration); PREP(woundsHandlerBase); PREP(woundsHandlerBurning); PREP(woundsHandlerVehiclecrash); diff --git a/addons/medical_damage/XEH_preInit.sqf b/addons/medical_damage/XEH_preInit.sqf index b389a0eaa0c..36558eb1666 100644 --- a/addons/medical_damage/XEH_preInit.sqf +++ b/addons/medical_damage/XEH_preInit.sqf @@ -10,6 +10,9 @@ PREP_RECOMPILE_END; call FUNC(parseConfigForInjuries); +// Used for armor penetration calculation +GVAR(ammoCache) = createHashMap; + /* addMissionEventHandler ["Loaded",{ INFO("Mission Loaded - Reloading medical configs for extension"); diff --git a/addons/medical_damage/functions/fnc_getAmmoData.sqf b/addons/medical_damage/functions/fnc_getAmmoData.sqf new file mode 100644 index 00000000000..8a245cc3136 --- /dev/null +++ b/addons/medical_damage/functions/fnc_getAmmoData.sqf @@ -0,0 +1,36 @@ +#include "..\script_component.hpp" +/* + * Author: LinkIsGrim + * Returns base damage value, penetration factor, and expected muzzle velocity of a given round, either from a cache or by reading the ammo config. + * + * Arguments: + * 0: Ammo + * + * Return Value: + * Base damage value, penetration factor, muzzle velocity + * + * Example: + * "B_556x45_Ball" call ace_medical_engine_fnc_getAmmoData + * + * Public: No + */ + +// Baseline penetrability used for armor penetration calculation, see (https://community.bistudio.com/wiki/CfgAmmo_Config_Reference#caliber) +#define ARMOR_PENETRABILITY 0.015 + +params ["_ammo"]; + +GVAR(ammoCache) getOrDefaultCall [toLowerANSI _ammo, { + TRACE_1("Cache miss",_ammo); + private _ammoConfig = configFile >> "CfgAmmo" >> _ammo; + + if (isNull _ammoConfig) then { + [0, 0, 0] // return + } else { + private _hit = getNumber (_ammoConfig >> "hit"); + private _caliber = (getNumber (_ammoConfig >> "caliber")); + private _typicalSpeed = getNumber (_ammoConfig >> "typicalSpeed"); + private _penFactor = _caliber * ARMOR_PENETRABILITY; + [_hit, _penFactor, _typicalSpeed] // return + }; +}, true] diff --git a/addons/medical_damage/functions/fnc_woundsHandlerArmorPenetration.sqf b/addons/medical_damage/functions/fnc_woundsHandlerArmorPenetration.sqf new file mode 100644 index 00000000000..9f0c9da07a8 --- /dev/null +++ b/addons/medical_damage/functions/fnc_woundsHandlerArmorPenetration.sqf @@ -0,0 +1,82 @@ +#include "..\script_component.hpp" +/* + * Author: LinkIsGrim + * Custom wounds handler for armor penetration. Calculates damage based on round material penetration and unit armor + * + * Arguments: + * 0: Unit that was hit + * 1: Damage done to each body part + * 0: Engine damage + * 1: Body part + * 2: Real damage + * 2: Type of the damage done + * 3: Ammo + * + * Return Value: + * None + * + * Example: + * [player, [[0.5, "Body", 1]], "bullet"] call ace_medical_damage_fnc_woundsHandlerArmorPenetration + * + * Public: No + */ + +// This gets close to vanilla values on FMJ ammo +#define DAMAGE_SCALING_FACTOR 10 + +params ["_unit", "_allDamages", "_typeOfDamage", "_ammo"]; +TRACE_4("woundsHandlerArmorPenetration",_unit,_allDamages,_typeOfDamage,_ammo); + +if (!EGVAR(medical,alternateArmorPenetration)) exitWith {_this}; + +private _damageData = (_allDamages select 0); // selection specific +_damageData params ["_engineDamage", "_bodyPart", "_realDamage"]; + +private _ammoData = _ammo call FUNC(getAmmoData); + +// See (https://community.bistudio.com/wiki/CfgAmmo_Config_Reference#caliber), +// _penFactor is ammo "caliber" * RHA penetrability, armor plates according to BI are just made of RHAe material +_ammoData params ["_hit", "_penFactor", "_typicalSpeed"]; + +// Skip bad ammo +if (_hit <= 0) exitWith {_this}; + +private _armorLevelStep = [2, 4] select (_bodyPart == "body"); +private _armor = (_realDamage/_engineDamage) - 2; // remove base armor + +// There's no need to calculate penetration if there is no armor to begin with, base damage handling is good enough in this case +if (_armor <= _armorLevelStep) exitWith { + TRACE_3("skipping no armor",_armor,_bodyPart,_armorLevelStep); + _this // return +}; + +// Cap at Armor Level V +// Jumping from no armor to armor level 1 is 2 steps +private _armorLevel = 0 max (round ((_armor - (_armorLevelStep * 2)) / _armorLevelStep)) min 4; +TRACE_3("gotArmorLevel",_armorLevel,_armor,_armorLevelStep); + +// Armor RHA equivalent, non-linear, ref \a3\Data_F\Penetration\armour_plate/thin/medium/heavy.bisurf +// Divided by 2 to keep inline with vanilla caliber values +private _armorThickness = [ + 6, + 15, + 21, + 40, + 55 +] select _armorLevel; +TRACE_1("gotArmorThickness",_armorThickness); + +// Impact damage is hit * (impactSpeed / typicalSpeed): https://community.bistudio.com/wiki/CfgAmmo_Config_Reference#typicalSpeed +// Impact damage is already lowered by engine based on hit angle, so speed and therefore penetration are also naturally lowered +// Assume typicalSpeed < 1 means no damage dropoff +private _impactSpeed = (_realDamage/_hit) * (_typicalSpeed max 1); + +private _penDepth = _penFactor * _impactSpeed; + +// Max damage is the config value, go down from there based on armor penetration +private _finalDamage = (_hit * ((_penDepth/_armorThickness) min 1)) / DAMAGE_SCALING_FACTOR; +_damageData set [0, _finalDamage]; + +TRACE_3("Armor penetration handled, passing damage",_finalDamage,_damageData,_allDamages); + +_this // return diff --git a/addons/medical_damage/initSettings.inc.sqf b/addons/medical_damage/initSettings.inc.sqf index c6d7f49e343..8e0f02a651d 100644 --- a/addons/medical_damage/initSettings.inc.sqf +++ b/addons/medical_damage/initSettings.inc.sqf @@ -1,3 +1,12 @@ +[ + QEGVAR(medical,alternateArmorPenetration), + "CHECKBOX", + [LSTRING(AlternateArmorPenetration_DisplayName), LSTRING(AlternateArmorPenetration_Description)], + ELSTRING(medical,Category), + true, + true +] call CBA_fnc_addSetting; + [ QEGVAR(medical,fatalDamageSource), "LIST", diff --git a/addons/medical_damage/stringtable.xml b/addons/medical_damage/stringtable.xml index f3408868d7e..da2a5a4bba0 100644 --- a/addons/medical_damage/stringtable.xml +++ b/addons/medical_damage/stringtable.xml @@ -871,5 +871,11 @@ 사지 피해 사용 Utiliser les dommages aux membres + + Use Custom Armor Penetration + + + Controls whether ammo material penetration is taken into account for damage handling. + diff --git a/addons/medical_engine/functions/fnc_getHitpointArmor.sqf b/addons/medical_engine/functions/fnc_getHitpointArmor.sqf index d4fdd00fd36..db9370f4270 100644 --- a/addons/medical_engine/functions/fnc_getHitpointArmor.sqf +++ b/addons/medical_engine/functions/fnc_getHitpointArmor.sqf @@ -32,23 +32,19 @@ private _gear = [ private _rags = _gear joinString "$"; private _var = format [QGVAR(armorCache$%1), _hitpoint]; -_unit getVariable [_var, ["", 0, 0]] params ["_prevRags", "_armor", "_armorScaled"]; +_unit getVariable [_var, ["", 0]] params ["_prevRags", "_armor"]; if (_rags != _prevRags) then { _armor = 0; - _armorScaled = 0; { - ([_x, _hitpoint] call FUNC(getItemArmor)) params ["_itemArmor", "_itemArmorScaled"]; - _armor = _armor + _itemArmor; - _armorScaled = _armorScaled + _itemArmorScaled; + _armor = _armor + ([_x, _hitpoint] call FUNC(getItemArmor)); } forEach _gear; // Armor should be at least 1 to prevent dividing by 0 _armor = _armor max 1; - _armorScaled = _armorScaled max 1; - _unit setVariable [_var, [_rags, _armor, _armorScaled]]; + _unit setVariable [_var, [_rags, _armor]]; }; -[_armor, _armorScaled] // return +_armor // return diff --git a/addons/medical_engine/functions/fnc_getItemArmor.sqf b/addons/medical_engine/functions/fnc_getItemArmor.sqf index 01e6719a0fc..5125e2b0692 100644 --- a/addons/medical_engine/functions/fnc_getItemArmor.sqf +++ b/addons/medical_engine/functions/fnc_getItemArmor.sqf @@ -18,53 +18,42 @@ params ["_item", "_hitpoint"]; -private _key = format ["%1$%2", _item, _hitpoint]; -private _return = GVAR(armorCache) get _key; - -if (isNil "_return") then { +GVAR(armorCache) getOrDefaultCall [_this joinString "$", { + TRACE_2("Cache miss",_item,_hitpoint); private _armor = 0; - private _armorScaled = 0; private _passThrough = 1; - TRACE_2("Cache miss",_item,_hitpoint); - if ("" in [_item, _hitpoint]) exitWith { - _return = [_armor, _armorScaled]; - GVAR(armorCache) set [_key, _return]; - }; - private _itemInfo = configFile >> "CfgWeapons" >> _item >> "ItemInfo"; - private _itemType = getNumber (_itemInfo >> "type"); - private _passThroughEffect = [1, 0.6] select (_itemType == TYPE_VEST); + if !("" in [_item, _hitpoint]) then { + private _itemInfo = configFile >> "CfgWeapons" >> _item >> "ItemInfo"; + private _itemType = getNumber (_itemInfo >> "type"); - if (_itemType == TYPE_UNIFORM) then { - private _unitCfg = configFile >> "CfgVehicles" >> getText (_itemInfo >> "uniformClass"); - if (_hitpoint == "#structural") then { - // TODO: I'm not sure if this should be multiplied by the base armor value or not - _armor = getNumber (_unitCfg >> "armorStructural"); + if (_itemType == TYPE_UNIFORM) then { + private _unitCfg = configFile >> "CfgVehicles" >> getText (_itemInfo >> "uniformClass"); + if (_hitpoint == "#structural") then { + // TODO: I'm not sure if this should be multiplied by the base armor value or not + _armor = getNumber (_unitCfg >> "armorStructural"); + } else { + private _entry = _unitCfg >> "HitPoints" >> _hitpoint; + _armor = getNumber (_unitCfg >> "armor") * (1 max getNumber (_entry >> "armor")); + _passThrough = 0.1 max getNumber (_entry >> "passThrough") min 1; // prevent dividing by 0 + }; } else { - private _entry = _unitCfg >> "HitPoints" >> _hitpoint; - _armor = getNumber (_unitCfg >> "armor") * (1 max getNumber (_entry >> "armor")); - _passThrough = 0.1 max getNumber (_entry >> "passThrough") min 1; // prevent dividing by 0 + private _condition = format ["getText (_x >> 'hitpointName') == '%1'", _hitpoint]; + private _entry = configProperties [_itemInfo >> "HitpointsProtectionInfo", _condition] param [0, configNull]; + if (!isNull _entry) then { + _armor = getNumber (_entry >> "armor"); + _passThrough = 0.1 max getNumber (_entry >> "passThrough") min 1; + }; }; - } else { - private _condition = format ["getText (_x >> 'hitpointName') == '%1'", _hitpoint]; - private _entry = configProperties [_itemInfo >> "HitpointsProtectionInfo", _condition] param [0, configNull]; - if (!isNull _entry) then { - _armor = getNumber (_entry >> "armor"); - _passThrough = 0.1 max getNumber (_entry >> "passThrough") min 1; - }; - }; - // Scale armor using passthrough to fix explosive-resistant armor (#9063) - // Skip scaling for uniforms and items that don't cover the hitpoint to prevent infinite armor - if (_armor > 0) then { - if (_itemType == TYPE_UNIFORM) then { - _armorScaled = _armor; - } else { - _armorScaled = (log (_armor / (_passThrough ^ _passThroughEffect))) * 10; + // Scale armor using passthrough to fix explosive-resistant & stupid armor (#9063) + // Skip scaling for uniforms and items that don't cover the hitpoint to prevent infinite armor + private _armorLevelStep = [4, 2] select (_itemType == TYPE_HEADGEAR); + if (_itemType != TYPE_UNIFORM && (_armor > _armorLevelStep * 6)) then { + private _passThroughEffect = [1, 0.6] select (_itemType == TYPE_VEST); + _armor = (log (_armor / (_passThrough ^ _passThroughEffect))) * 10; }; }; - _return = [_armor, _armorScaled]; - GVAR(armorCache) set [_key, _return]; -}; -_return // return + _armor // return +}, true] diff --git a/addons/medical_engine/functions/fnc_handleDamage.sqf b/addons/medical_engine/functions/fnc_handleDamage.sqf index cf891f5e532..bbd081fce86 100644 --- a/addons/medical_engine/functions/fnc_handleDamage.sqf +++ b/addons/medical_engine/functions/fnc_handleDamage.sqf @@ -45,16 +45,11 @@ if (_context != 2 && {_context == 4 || _newDamage == 0}) exitWith { _oldDamage }; -// Get scaled armor value of hitpoint and calculate damage before armor +// Get 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 _armor = [_unit, _hitpoint] call FUNC(getHitpointArmor); 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) diff --git a/addons/medical_engine/initSettings.inc.sqf b/addons/medical_engine/initSettings.inc.sqf index 062e2a08220..9fe80afcb00 100644 --- a/addons/medical_engine/initSettings.inc.sqf +++ b/addons/medical_engine/initSettings.inc.sqf @@ -6,12 +6,3 @@ true, true ] call CBA_fnc_addSetting; - -[ - QGVAR(damagePassThroughEffect), - "SLIDER", - [LSTRING(damagePassThroughEffect_displayName), LSTRING(damagePassThroughEffect_description)], - ELSTRING(medical,Category), - [0, 1, 1, 2, true], - true -] call CBA_fnc_addSetting; diff --git a/addons/medical_engine/stringtable.xml b/addons/medical_engine/stringtable.xml index 19856781967..bcf0e0b0057 100644 --- a/addons/medical_engine/stringtable.xml +++ b/addons/medical_engine/stringtable.xml @@ -27,29 +27,5 @@ 車両衝突ダメージを有効化 启用车辆碰撞损坏 - - Controls effect of armor 'passThrough' on final damage. Makes high armor values, like ones used in GL rigs, less effective.\nUse 0% for pre 3.16.0 armor behavior.\nOnly touch this if you know what you're doing! - Contrôle l'effet de la "pénétration" de l'armure sur les dégâts finaux. Rend les valeurs d'armures élevées, comme celles utilisées dans les gilets GL, moins efficaces.\nUtilisez 0% pour le comportement des armures des versions antérieures à 3.16.0.\nNe modifiez la valeur que si vous savez ce que vous faîtes ! - Controla el efecto de 'passThrough' de armadura en el daño final. Hace que los valores altos de armadura, como los usados en los chalecos GL, sean menos efectivos.\nUsar 0% para comportamiento de armadura en versiones anteriores a 3.16.0.\nSólo modifica esto si sabes lo que estás haciendo! - Determina l'effetto di danni sul corpo che 'trapassano' l'armatura. Rende alti valori di protezione, come quelli su corpetti GL, meno efficaci.\nUtilizza 0% per il comportamento prima di v3.16.0.\nModifica questo valore solo se sai cosa stai facendo! - Kontroluje wpływ "penetracji" pancerza na ostateczne obrażenia. Sprawia, że wysokie wartości pancerza, takie jak te używane w kamizelkach GL, są mniej skuteczne.\nUżyj 0% dla zachowania pancerza sprzed wersji 3.16.0.\nZmień wartość tylko jeśli wiesz co robisz! - Controla o efeito de penetração (passThrough) da blindagem no dano final. Torna valores de blindagem altos, como os usados em coletes GL, menos eficazes.\nUse 0% para o comportamento de blindagem anterior à versão 3.16.0.\nSó mexa nisso se souber o que está fazendo! - Контролирует эффект `passThrough` при нанесении конечного урона. Делает высокие значения брони, подобные тем, которые используются в GL rigs, менее эффективными.\nИспользуйте 0% для поведения брони до версии 3.16.0.n\Прикасайтесь к этому, только если знаете, что делаете! - Steuert den Effekt des „Durchschlagens“ von Panzerung auf den Gesamtschaden. Macht hohe Panzerungswerte, wie sie in GL-Westen verwendet werden, weniger effektiv.\nVerwende 0% für das Panzerungsverhalten vor 3.16.0.\nÄndere den Wert nur, wenn du weißt, was du tust! - 최종 데미지에 대한 방어구의 'PassThrough' 효과를 조정합니다. GL 리그에 사용되는 것과 같은 높은 방호값을 덜 효과적으로 만듭니다\n3.16.0 이전의 방어구 동작에는 0%를 사용하십시오.\n당신이 뭘 하고 있는지 알고 있는 경우에만 이걸 설정하세요! - ボディアーマーの'passThrough'値が最終的な身体ダメージに与える影響を調整します。擲弾兵リグで使用されるような高い装甲値では効果が低くなります。\n3.16.0以前の挙動にするには0%にしてください。\nこれが何かわからない場合は変更しないことをお勧めします。 - - - Armor PassThrough Effect - Effet de pénétration d'armure - Efecto de Atravesar Armadura - Fattore di Trapasso Armatura - Efekt penetracji pancerza - Efeito de Penetração de Blindagem - Эффект сквозного прохождения брони - Effekt des Panzerungsdurchschlags - 방어구 PassThrough 효과 - 装甲貫通効果 -