diff --git a/addons/frag/CfgAmmo.hpp b/addons/frag/CfgAmmo.hpp index 7a124833b3c..07bc5fc1cb0 100644 --- a/addons/frag/CfgAmmo.hpp +++ b/addons/frag/CfgAmmo.hpp @@ -4,272 +4,16 @@ class CfgAmmo { - // ~~~~ Bombs: - class ammo_Bomb_LaserGuidedBase; - class Bo_GBU12_LGB: ammo_Bomb_LaserGuidedBase { - GVAR(enabled) = 1; + #include "CfgAmmoBaseEH.hpp" - GVAR(classes)[] = {QGVAR(large), QGVAR(large), QGVAR(large_HD), QGVAR(large), QGVAR(huge), QGVAR(huge_HD), QGVAR(huge)}; - GVAR(metal) = 140000; - GVAR(charge) = 87000; - GVAR(gurney_c) = 2320; - GVAR(gurney_k) = "1/2"; - }; - class Bomb_04_F: ammo_Bomb_LaserGuidedBase { - GVAR(enabled) = 1; - - GVAR(classes)[] = {QGVAR(large), QGVAR(large), QGVAR(large_HD), QGVAR(large), QGVAR(huge), QGVAR(huge_HD), QGVAR(huge)}; - GVAR(metal) = 140000; - GVAR(charge) = 87000; - GVAR(gurney_c) = 2320; - GVAR(gurney_k) = "1/2"; - }; - class BombCore; - class Bo_Mk82: BombCore { - GVAR(enabled) = 1; - - GVAR(classes)[] = {QGVAR(large), QGVAR(large), QGVAR(large_HD), QGVAR(large), QGVAR(huge), QGVAR(huge_HD), QGVAR(huge)}; - GVAR(metal) = 140000; - GVAR(charge) = 87000; - GVAR(gurney_c) = 2320; - GVAR(gurney_k) = "1/2"; - }; - - // ~~~~ Grenades: - class GrenadeBase; - class Grenade; - class GrenadeHand: Grenade { - GVAR(enabled) = 1; - - GVAR(skip) = 0; - GVAR(force) = 1; - // This is a good high-drag frag type for grenades. - GVAR(classes)[] = {QGVAR(tiny_HD)}; - /* - These values are based on the M67 Grenade, should be tweaked for - individual grenades. - */ - GVAR(metal) = 210; // metal in grams - GVAR(charge) = 185; // explosive in grams - GVAR(gurney_c) = 2843; // Gurney velocity constant for explosive type. See: http://en.wikipedia.org/wiki/Gurney_equations - GVAR(gurney_k) = "3/5"; // Gurney shape factor, in this case a sphere. See: http://en.wikipedia.org/wiki/Gurney_equations - }; - class GrenadeHand_stone: GrenadeHand { - GVAR(skip) = 1; - }; - class SmokeShell: GrenadeHand { - GVAR(skip) = 1; - }; - class G_40mm_HE: GrenadeBase { - // Source: http://www.inetres.com/gp/military/infantry/grenade/40mm_ammo.html#M441 - GVAR(enabled) = 1; - GVAR(force) = 1; - - GVAR(classes)[] = {QGVAR(tiny_HD)}; - GVAR(metal) = 200; - GVAR(charge) = 32; - GVAR(gurney_c) = 2700; - GVAR(gurney_k) = "1/2"; - }; - class G_40mm_HEDP: G_40mm_HE { - // Source: http://www.inetres.com/gp/military/infantry/grenade/40mm_ammo.html#M433 - GVAR(enabled) = 1; - - GVAR(classes)[] = {QGVAR(tiny_HD)}; - GVAR(metal) = 200; - GVAR(charge) = 45; - GVAR(gurney_c) = 2830; - GVAR(gurney_k) = "1/2"; - }; - - class ACE_G_40mm_HEDP: G_40mm_HEDP {}; - class ACE_G_40mm_HE: G_40mm_HE {}; - class ACE_G_40mm_Practice: ACE_G_40mm_HE { - GVAR(skip) = 1; - GVAR(force) = 0; - EGVAR(vehicle_damage,incendiary) = 0; - }; - class ACE_G40mm_HE_VOG25P: G_40mm_HE { - GVAR(skip) = 0; - GVAR(force) = 1; - }; - - - // ~~~~ RPGs: - class MissileBase; - class R_PG32V_F; - class R_TBG32V_F: R_PG32V_F { // HE - GVAR(enabled) = 1; - GVAR(metal) = 400; - GVAR(charge) = 210; - GVAR(gurney_c) = 2800; - GVAR(gurney_k) = "3/5"; - GVAR(classes)[] = {"ACE_frag_medium_HD"}; - }; - class M_Titan_AA: MissileBase { - GVAR(skip) = 1; - }; - class M_Titan_AT: MissileBase { - GVAR(skip) = 1; - }; - class M_Titan_AP: M_Titan_AT { // "anti personnel" - GVAR(skip) = 0; - GVAR(enabled) = 1; - GVAR(metal) = 400; - GVAR(charge) = 210; - GVAR(gurney_c) = 2800; - GVAR(gurney_k) = "3/5"; - GVAR(classes)[] = {"ACE_frag_medium_HD"}; - }; - - // https://ofb.gov.in/product/products/product-details/84-mm-he-round-ffv-441-b - // https://armypubs.army.mil/epubs/DR_pubs/DR_a/pdf/web/ARN18072_TC%203-22x84%20FINAL%20WEB.pdf (page 99, Table A-6. HE 441D RS, 84-mm projectile) - class R_MRAAWS_HEAT_F; - class R_MRAAWS_HE_F: R_MRAAWS_HEAT_F { - GVAR(enabled) = 1; - GVAR(metal) = 2300; - GVAR(charge) = 590; - GVAR(gurney_c) = 2800; - GVAR(gurney_k) = "1/2"; - GVAR(classes)[] = {"ACE_frag_small"}; - }; - - - // ~~~~ Missiles: - class M_PG_AT; - class M_AT: M_PG_AT { // DAR (Hydra 70) - // Source: http://fas.org/man/dod-101/sys/missile/hydra-70.htm - GVAR(enabled) = 1; - - GVAR(classes)[] = {QGVAR(medium), QGVAR(medium_HD)}; - GVAR(metal) = 3850; - GVAR(charge) = 1040; - GVAR(gurney_c) = 2700; - GVAR(gurney_k) = "1/2"; - }; - class RocketBase; - class R_80mm_HE: RocketBase { - GVAR(skip) = 1; - }; - class Missile_AGM_02_F: MissileBase { - // Source: http://fas.org/man/dod-101/sys/smart/agm-65.htm - GVAR(enabled) = 1; - - GVAR(classes)[] = {QGVAR(medium), QGVAR(medium_HD)}; - GVAR(metal) = 56250; - GVAR(charge) = 39000; - GVAR(gurney_c) = 2700; - GVAR(gurney_k) = "1/2"; - }; - class Rocket_04_HE_F: MissileBase { // Shrieker (Hydra 70) - GVAR(enabled) = 1; - GVAR(classes)[] = {QGVAR(medium), QGVAR(medium_HD)}; - GVAR(metal) = 3850; - GVAR(charge) = 1040; - GVAR(gurney_c) = 2700; - GVAR(gurney_k) = "1/2"; - }; - class M_Scalpel_AT: MissileBase { // 9K121 Vikhr - GVAR(enabled) = 1; - GVAR(classes)[] = {QGVAR(medium), QGVAR(medium_HD)}; - GVAR(metal) = 10000; - GVAR(charge) = 3000; - GVAR(gurney_c) = 2700; - GVAR(gurney_k) = "1/2"; - }; - class ACE_Hellfire_AGM114K: M_Scalpel_AT { - // Source: http://www.designation-systems.net/dusrm/m-114.html - GVAR(enabled) = 1; - - GVAR(classes)[] = {QGVAR(medium), QGVAR(medium_HD)}; - GVAR(metal) = 8000; - GVAR(charge) = 2400; - GVAR(gurney_c) = 2700; - GVAR(gurney_k) = "1/2"; - }; - class M_Air_AA: MissileBase { - GVAR(skip) = 1; - }; - class Missile_AA_04_F: MissileBase { - GVAR(skip) = 1; - }; - - // curator ammo entries - class ShellBase; - class Sh_125mm_HEAT; - class Sh_155mm_AMOS: ShellBase { - // Source: http://www.globalsecurity.org/military/systems/munitions/m795.htm - GVAR(enabled) = 1; - - GVAR(classes)[] = {QGVAR(large), QGVAR(large), QGVAR(large_HD), QGVAR(large), QGVAR(huge), QGVAR(huge_HD), QGVAR(huge)}; - GVAR(metal) = 36000; - GVAR(charge) = 9979; - GVAR(gurney_c) = 2440; - GVAR(gurney_k) = "1/2"; - }; - class Sh_82mm_AMOS: Sh_155mm_AMOS { - // Source: http://www.arsenal-bg.com/defense_police/mortar_bombs_82mm.htm - GVAR(enabled) = 1; - - GVAR(classes)[] = {QGVAR(medium), QGVAR(medium_HD)}; - GVAR(metal) = 3200; - GVAR(charge) = 420; - GVAR(gurney_c) = 2440; - GVAR(gurney_k) = "1/2"; - }; - class ModuleOrdnanceMortar_F_Ammo: Sh_82mm_AMOS { - GVAR(enabled) = 1; - - GVAR(classes)[] = {QGVAR(medium), QGVAR(medium_HD)}; - GVAR(metal) = 800; - GVAR(charge) = 4200; - GVAR(gurney_c) = 2320; - GVAR(gurney_k) = "1/2"; - }; - class Sh_105mm_HEAT_MP: Sh_125mm_HEAT { - GVAR(enabled) = 1; - - GVAR(classes)[] = {QGVAR(medium), QGVAR(medium_HD)}; - GVAR(metal) = 11400; - GVAR(charge) = 7100; - GVAR(gurney_c) = 2800; - GVAR(gurney_k) = "1/2"; - }; - class Sh_120mm_HE: ShellBase { - GVAR(enabled) = 1; - - GVAR(classes)[] = {QGVAR(medium), QGVAR(medium_HD)}; - GVAR(metal) = 23000; - GVAR(charge) = 3148; - GVAR(gurney_c) = 2830; - GVAR(gurney_k) = "1/2"; - }; - class Sh_125mm_HE: Sh_120mm_HE { - GVAR(enabled) = 1; - - GVAR(classes)[] = {QGVAR(medium), QGVAR(medium_HD)}; - GVAR(metal) = 16000; - GVAR(charge) = 3200; - GVAR(gurney_c) = 2440; - GVAR(gurney_k) = "1/2"; - }; - class ModuleOrdnanceHowitzer_F_ammo: Sh_155mm_AMOS { - GVAR(enabled) = 1; - - GVAR(classes)[] = {QGVAR(large), QGVAR(large), QGVAR(large_HD), QGVAR(large), QGVAR(huge), QGVAR(huge_HD), QGVAR(huge)}; - GVAR(metal) = 1950; - GVAR(charge) = 15800; - GVAR(gurney_c) = 2320; - GVAR(gurney_k) = "1/2"; - }; - - - class B_65x39_Caseless; + class B_65x39_Caseless: BulletBase {}; class GVAR(base): B_65x39_Caseless { ACE_damageType = "grenade"; // compatibility with medical_damage, shrapnel should produce grenade wounds - timeToLive = 12; - typicalSpeed = 1500; + timeToLive = 4; + typicalSpeed = 800; + maxSpeed = 1500; deflecting = 65; + GVAR(skip) = 1; }; class GVAR(tiny): GVAR(base) { @@ -317,7 +61,7 @@ class CfgAmmo { }; class GVAR(large_HD): GVAR(large) { - hit = 28; + hit = 28; indirectHit = 2; indirectHitRange = 0.25; airFriction = QUOTE(BASE_DRAG_HD*0.65); @@ -340,21 +84,7 @@ class CfgAmmo { caliber = 2.8; }; - class GVAR(spall_small): GVAR(small) { - timeToLive = 0.1; - }; - - class GVAR(spall_medium): GVAR(medium) { - timeToLive = 0.15; - }; - - class GVAR(spall_large): GVAR(large) { - timeToLive = 0.25; - }; - - class GVAR(spall_huge): GVAR(huge) { - timeToLive = 0.3; - }; - - #include "CfgAmmoReflections.hpp" + #include "CfgAmmoSpall.hpp" + #include "CfgAmmoFragSpawner.hpp" + #include "CfgAmmoFragParameters.hpp" }; diff --git a/addons/frag/CfgAmmoBaseEH.hpp b/addons/frag/CfgAmmoBaseEH.hpp new file mode 100644 index 00000000000..413dfe3dd93 --- /dev/null +++ b/addons/frag/CfgAmmoBaseEH.hpp @@ -0,0 +1,47 @@ +// We need this since autocannons generally inherit from BulletBase +class BulletCore; +class BulletBase: BulletCore {}; + +class GrenadeCore; +class GrenadeBase: GrenadeCore {}; + +class LaserBombCore; +class ammo_Bomb_LaserGuidedBase: LaserBombCore {}; + +class MissileCore; +class MissileBase: MissileCore {}; + +class RocketCore; +class RocketBase: RocketCore { + GVAR(skip) = 1; +}; + +class ArtilleryRocketCore: RocketCore {}; + +class ShellCore; +class ShellBase: ShellCore {}; + +class ShotDeployCore; +class ShotDeployBase: ShotDeployCore { + GVAR(skip) = 1; +}; + +class ShotgunCore; +class ShotgunBase: ShotgunCore {}; + +class SubmunitionCore; +class SubmunitionBase: SubmunitionCore { + GVAR(skip) = 1; +}; + +class BoundingMineCore; +class BoundingMineBase: BoundingMineCore {}; + +class PipeBombCore; +class PipeBombBase: PipeBombCore {}; + +class DirectionalBombCore; +class DirectionalBombBase: DirectionalBombCore {}; + +class MineCore; +class MineBase: MineCore {}; diff --git a/addons/frag/CfgAmmoFragParameters.hpp b/addons/frag/CfgAmmoFragParameters.hpp new file mode 100644 index 00000000000..4638b87578a --- /dev/null +++ b/addons/frag/CfgAmmoFragParameters.hpp @@ -0,0 +1,875 @@ +// ~~~~ Autocannons +class B_19mm_HE: BulletBase { + GVAR(skip) = 1; +}; + +class B_20mm: BulletBase { + // Used in Weisel/AWC Nyx, which makes it a Rheinmetall Mk 20 Rh-202 + // Based on jane's ammunition handbook ~2002 (archive.org) + GVAR(skip) = 1; + GVAR(charge) = 6; + GVAR(metal) = 114; + GVAR(gurney_c) = 2830; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 100; + GVAR(classes)[] = {QGVAR(tiny)}; +}; + +class B_20mm_AP: BulletBase { + GVAR(skip) = 1; +}; + +class ammo_Gun20mmAABase: BulletBase { // 20x139mm + GVAR(skip) = 1; + GVAR(charge) = 6; + GVAR(metal) = 114; + GVAR(gurney_c) = 2830; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 100; + GVAR(classes)[] = {QGVAR(tiny)}; +}; + +class ammo_Gun30mmAABase: BulletBase { // 30x210mm HEI + GVAR(skip) = 0; + GVAR(charge) = 40; + GVAR(metal) = 410; + GVAR(gurney_k) = "1/2"; + GVAR(gurney_c) = 2901; + GVAR(fragCount) = 250; + GVAR(classes)[] = {QGVAR(tiny), QGVAR(tiny), QGVAR(small)}; + +}; + +class B_30mm_HE: B_19mm_HE { + // Used in Gorgon (Pandur II), assuming it's a L21A1 RARDEN, specifically HEI-T due to tracers + // https://ordtech-industries.com/30x170-mm-ammunition-for-cannons-oerlikon-kcb-hispano-hs831l-l21-rarden/ + GVAR(skip) = 0; + GVAR(charge) = 25; + GVAR(metal) = 320; + GVAR(gurney_c) = 2552; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 180; + GVAR(classes)[] = {QGVAR(tiny), QGVAR(tiny), QGVAR(small)}; +}; +class B_30mm_MP: B_30mm_HE { + // Used in Mora (FV510 Warrior), assuming it's a Mk44 Bushmaster II, specifically HEI-T due to tracers + // http://www.navweaps.com/Weapons/WNUS_30mm_BushmasterII.php + GVAR(metal) = 388; + GVAR(charge) = 56; + GVAR(gurney_c) = 2600; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 180; // assumed based on https://www.youtube.com/watch?v=c5SsspD0MeU + GVAR(classes)[] = {QGVAR(tiny), QGVAR(tiny), QGVAR(small)}; +}; + +class Gatling_30mm_HE_Plane_CAS_01_F: BulletBase { + GVAR(skip) = 0; + GVAR(classes)[] = {QGVAR(tiny), QGVAR(small)}; + GVAR(fragCount) = 180; + GVAR(metal) = 388; + GVAR(charge) = 56; + GVAR(gurney_c) = 2600; // guessed + GVAR(gurney_k) = "1/2"; +}; + + +class ammo_Gun35mmAABase: BulletBase { + // Gepard uses an Oerlikon GDF and the AA vehicles mimics it like it + // https://en.wikipedia.org/wiki/Oerlikon_GDF#Ammunition + // https://www.nammo.com/product/our-products/ammunition/medium-caliber-ammunition/35-mm-series/35-mm-x-228-hei-sd-and-hei-t-sd/ + GVAR(skip) = 0; + GVAR(charge) = 98; + GVAR(metal) = 400; + GVAR(gurney_k) = "1/2"; + GVAR(gurney_c) = 2700; + GVAR(fragCount) = 300; + GVAR(classes)[] = {QGVAR(tiny), QGVAR(tiny), QGVAR(small)}; +}; + +class B_35mm_AA: BulletBase { + // Gepard uses an Oerlikon GDF and the AA vehicles mimics it like it + // https://en.wikipedia.org/wiki/Oerlikon_GDF#Ammunition + // https://www.nammo.com/product/our-products/ammunition/medium-caliber-ammunition/35-mm-series/35-mm-x-228-hei-sd-and-hei-t-sd/ + GVAR(skip) = 0; + GVAR(charge) = 98; + GVAR(metal) = 400; + GVAR(gurney_k) = "1/2"; + GVAR(gurney_c) = 2700; + GVAR(fragCount) = 300; + GVAR(classes)[] = {QGVAR(tiny), QGVAR(tiny), QGVAR(small)}; +}; + +class B_40mm_GPR: B_30mm_HE { + // Based on noted 40mm Autocannons, base ROF, and ammo names, looks to be a CTAS40, specifically GPR-PD-T + // https://www.cta-international.com/ammunition/ + // https://ndiastorage.blob.core.usgovcloudapi.net/ndia/2002/gun/leslie.pdf + GVAR(classes)[] = {QGVAR(tiny), QGVAR(small)}; + GVAR(fragCount) = 250; + GVAR(metal) = 750; + GVAR(charge) = 120; + GVAR(gurney_c) = 2700; // guessed + GVAR(gurney_k) = "1/2"; +}; +class UnderwaterMine_Range_Ammo: MineBase { + GVAR(skip) = 1; +}; + +// ~~~~ Bombs: +class Bo_GBU12_LGB: ammo_Bomb_LaserGuidedBase { + GVAR(skip) = 0; + GVAR(charge) = 87000; + GVAR(metal) = 140000; + GVAR(gurney_c) = 2320; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 17500; + GVAR(classes)[] = {QGVAR(large), QGVAR(large), QGVAR(large_HD), QGVAR(large), QGVAR(huge), QGVAR(huge_HD), QGVAR(huge)}; +}; +class Bomb_03_F: ammo_Bomb_LaserGuidedBase { + GVAR(skip) = 0; + GVAR(charge) = 100000; + GVAR(metal) = 150000; + GVAR(gurney_c) = 2320; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 17500; + GVAR(classes)[] = {QGVAR(large), QGVAR(large), QGVAR(large), QGVAR(large), QGVAR(huge), QGVAR(huge_HD), QGVAR(huge)}; +}; +class Bomb_04_F: ammo_Bomb_LaserGuidedBase { + GVAR(skip) = 0; + GVAR(charge) = 87000; + GVAR(metal) = 140000; + GVAR(gurney_c) = 2320; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 17500; + GVAR(classes)[] = {QGVAR(large), QGVAR(large), QGVAR(large), QGVAR(large), QGVAR(huge), QGVAR(huge_HD), QGVAR(huge)}; +}; +class BombCluster_01_Ammo_F: Bomb_04_F { + GVAR(skip) = 1; +}; +class ammo_Bomb_SmallDiameterBase: ammo_Bomb_LaserGuidedBase { + GVAR(skip) = 0; + GVAR(charge) = 160; // kg + GVAR(metal) = 113; // kg + GVAR(gurney_c) = 2830; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 8000; + GVAR(classes)[] = {QGVAR(medium), QGVAR(medium), QGVAR(large), QGVAR(large), QGVAR(huge), QGVAR(huge_HD)}; +}; + +class BombCore; +class Bo_Mk82: BombCore { + GVAR(classes)[] = {QGVAR(large), QGVAR(large), QGVAR(large_HD), QGVAR(large), QGVAR(huge), QGVAR(huge_HD), QGVAR(huge)}; + GVAR(fragCount) = 17500; + GVAR(metal) = 140000; + GVAR(charge) = 87000; + GVAR(gurney_c) = 2320; + GVAR(gurney_k) = "1/2"; +}; + +class Mo_cluster_AP: ShellBase { + GVAR(skip) = 1; + GVAR(force) = 0; +}; +class Mo_cluster_Bomb_01_F: Mo_cluster_AP { // Mk 118 Rockeye + GVAR(skip) = 0; + GVAR(classes)[] = {QGVAR(small)}; + GVAR(fragCount) = 200; + GVAR(metal) = 400; + GVAR(charge) = 180; + GVAR(gurney_c) = 2700; + GVAR(gurney_k) = "1/2"; +}; +class Mo_cluster_Bomb_02_F: Mo_cluster_Bomb_01_F { // ShOAB-0.5 + GVAR(classes)[] = {QGVAR(small)}; + GVAR(fragCount) = 304; + GVAR(metal) = 400; + GVAR(charge) = 100; + GVAR(gurney_c) = 2700; +}; +class Mo_cluster_Bomb_03_F: Mo_cluster_Bomb_01_F { // idk, @lambda.tiger on the ace discord if you find out + GVAR(classes)[] = {QGVAR(small)}; + GVAR(fragCount) = 200; + GVAR(metal) = 400; + GVAR(charge) = 140; + GVAR(gurney_c) = 2400; +}; + +// ~~~~ Grenades: +class Grenade; +class GrenadeHand: Grenade { + GVAR(skip) = 0; + GVAR(force) = 1; + /* + These values are based on the M67 Grenade, should be tweaked for + individual grenades. + */ + GVAR(classes)[] = {QGVAR(tiny)}; + GVAR(fragCount) = 800; + GVAR(metal) = 210; // metal in grams + GVAR(charge) = 185; // explosive in grams + GVAR(gurney_c) = 2843; // Gurney velocity constant for explosive type. See: http://en.wikipedia.org/wiki/Gurney_equations + GVAR(gurney_k) = "3/5"; // Gurney shape factor, in this case a sphere. See: http://en.wikipedia.org/wiki/Gurney_equations +}; +class mini_Grenade: GrenadeHand { + GVAR(classes)[] = {QGVAR(tiny)}; + GVAR(fragCount) = 520; + GVAR(metal) = 104; + GVAR(charge) = 36; +}; +class GrenadeHand_stone: GrenadeHand { + GVAR(skip) = 1; +}; +class SmokeShell: GrenadeHand { + GVAR(skip) = 1; +}; +class G_40mm_HE: GrenadeBase { + // Source: http://www.inetres.com/gp/military/infantry/grenade/40mm_ammo.html#M441 + GVAR(skip) = 0; + GVAR(force) = 0; + GVAR(classes)[] = {QGVAR(small), QGVAR(tiny), QGVAR(small)}; + GVAR(fragCount) = 600; // guess based on probability hit of 1% + GVAR(metal) = 200; + GVAR(charge) = 32; + GVAR(gurney_c) = 2700; + GVAR(gurney_k) = "3/5"; // interior fragmenter/charge is a sphere +}; +class G_20mm_HE: G_40mm_HE { + GVAR(classes)[] = {QGVAR(tiny)}; + GVAR(fragCount) = 200; // Halved size & dimensions from 40mm HE + GVAR(metal) = 50; + GVAR(charge) = 8; + GVAR(gurney_c) = 2700; + GVAR(gurney_k) = "3/5"; // interior fragmenter/charge is a sphere +}; +class G_40mm_HEDP: G_40mm_HE { + // Source: http://www.inetres.com/gp/military/infantry/grenade/40mm_ammo.html#M433 + GVAR(classes)[] = {QGVAR(tiny), QGVAR(small), QGVAR(small_HD)}; + GVAR(fragCount) = 270; // seems to have greater framentation ability, but lower range per source + GVAR(metal) = 200; + GVAR(charge) = 45; + GVAR(gurney_c) = 2830; + GVAR(gurney_k) = "1/2"; +}; + +class ACE_G_40mm_HEDP: G_40mm_HEDP {}; +class ACE_G_40mm_HE: G_40mm_HE {}; +class ACE_G_40mm_Practice: ACE_G_40mm_HE { + GVAR(skip) = 1; + GVAR(force) = 0; + EGVAR(vehicle_damage,incendiary) = 0; +}; + +// ~~~~ Mines & UXO +class ATMine_Range_Ammo: MineBase { + GVAR(skip) = 1; +}; + +class APERSMine_Range_Ammo: MineBase { // VS-50 + GVAR(skip) = 0; + GVAR(force) = 0; + GVAR(classes)[] = {QGVAR(tiny), QGVAR(tiny), QGVAR(small)}; + GVAR(fragCount) = 500; + GVAR(metal) = 100; + GVAR(charge) = 50; + GVAR(gurney_c) = 2700; + GVAR(gurney_k) = "1/3"; +}; + +class APERSBoundingMine_Range_Ammo: MineBase { + GVAR(skip) = 0; + GVAR(force) = 0; + GVAR(classes)[] = {QGVAR(tiny)}; + GVAR(fragCount) = 200; + GVAR(metal) = 80; + GVAR(charge) = 170; + GVAR(gurney_c) = 2700; + GVAR(gurney_k) = "3/5"; +}; + +class TrainingMine_Ammo: APERSMine_Range_Ammo { + GVAR(skip) = 1; +}; + +class SLAMDirectionalMine_Wire_Ammo: DirectionalBombBase { + GVAR(skip) = 1; +}; + +class APERSTripMine_Wire_Ammo: DirectionalBombBase { + GVAR(skip) = 0; + GVAR(force) = 0; + GVAR(classes)[] = {QGVAR(tiny)}; + GVAR(fragCount) = 800; + GVAR(metal) = 210; + GVAR(charge) = 185; + GVAR(gurney_c) = 2843; + GVAR(gurney_k) = "3/5"; +}; + +class IEDUrbanBig_Remote_Ammo: PipeBombBase { + GVAR(skip) = 0; + GVAR(classes)[] = {QGVAR(large), QGVAR(large), QGVAR(large_HD), QGVAR(large), QGVAR(huge), QGVAR(huge_HD), QGVAR(huge)}; + GVAR(fragCount) = 2000; + GVAR(metal) = 36000; + GVAR(charge) = 9979; + GVAR(gurney_c) = 2440; + GVAR(gurney_k) = "3/5"; +}; +class IEDLandBig_Remote_Ammo: PipeBombBase { + GVAR(skip) = 0; + GVAR(classes)[] = {QGVAR(large), QGVAR(large), QGVAR(large_HD), QGVAR(large), QGVAR(huge), QGVAR(huge_HD), QGVAR(huge)}; + GVAR(fragCount) = 2000; + GVAR(metal) = 36000; + GVAR(charge) = 9979; + GVAR(gurney_c) = 2440; + GVAR(gurney_k) = "3/5"; +}; +class IEDUrbanSmall_Remote_Ammo: PipeBombBase { + GVAR(skip) = 0; + GVAR(fragCount) = 1200; + GVAR(metal) = 23000; + GVAR(charge) = 3148; + GVAR(gurney_c) = 2830; + GVAR(gurney_k) = "3/5"; + GVAR(classes)[] = {QGVAR(small), QGVAR(medium), QGVAR(medium_HD)}; +}; +class IEDLandSmall_Remote_Ammo: PipeBombBase { + GVAR(skip) = 0; + GVAR(fragCount) = 1200; + GVAR(metal) = 23000; + GVAR(charge) = 3148; + GVAR(gurney_c) = 2830; + GVAR(gurney_k) = "3/5"; + GVAR(classes)[] = {QGVAR(small), QGVAR(medium), QGVAR(medium_HD)}; +}; + +class APERSMineDispenser_Ammo: PipeBombBase { + GVAR(skip) = 1; +}; + +class UXO1_Ammo_Base_F: APERSMine_Range_Ammo { // Mk 118 Rockeye + GVAR(skip) = 0; + GVAR(charge) = 180; + GVAR(metal) = 400; + GVAR(gurney_c) = 2700; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 200; + GVAR(classes)[] = {QGVAR(small)}; +}; + +class UXO2_Ammo_Base_F: UXO1_Ammo_Base_F { + GVAR(charge) = 100; + GVAR(fragCount) = 304; + GVAR(classes)[] = {QGVAR(small), QGVAR(tiny)}; +}; + +class APERSMineDispenser_Mine_Ammo: APERSMine_Range_Ammo { + GVAR(skip) = 1; +}; + +class UXO_deploy_base_f: SubmunitionBase {}; + +class ClaymoreDirectionalMine_Remote_Ammo: DirectionalBombBase { + GVAR(skip) = 1; +}; + +class SatchelCharge_Remote_Ammo: PipeBombBase { + GVAR(skip) = 1; +}; + +class DemoCharge_Remote_Ammo: PipeBombBase { + GVAR(skip) = 1; +}; + +class Drone_explosive_ammo: MineBase { + GVAR(skip) = 1; +}; + +class BombDemine_01_Ammo_F: BombCore { + GVAR(skip) = 1; +}; + +class BombDemine_01_SubAmmo_F: ShellBase { + GVAR(skip) = 1; +}; + +// ~~~~ Rockets: +class R_PG7_F: RocketBase { + GVAR(skip) = 0; + GVAR(force) = 0; + GVAR(classes)[] = {QGVAR(tiny), QGVAR(small)}; + GVAR(fragCount) = 400; + GVAR(metal) = 100; + GVAR(charge) = 560; + GVAR(gurney_c) = 2730; + GVAR(gurney_k) = "1/2"; +}; + +class R_PG32V_F: RocketBase { + GVAR(skip) = 1; +}; +class R_TBG32V_F: R_PG32V_F { // Thermobaric + GVAR(skip) = 0; + GVAR(fragCount) = 200; + GVAR(metal) = 400; + GVAR(charge) = 210; + GVAR(gurney_c) = 2800; + GVAR(gurney_k) = "3/5"; + GVAR(classes)[] = {QGVAR(medium_HD)}; +}; + +class M_SPG9_HEAT: RocketBase { + GVAR(skip) = 0; + GVAR(fragCount) = 200; + GVAR(metal) = 4150; + GVAR(charge) = 340; + GVAR(gurney_c) = 2970; + GVAR(gurney_k) = "1/2"; + GVAR(classes)[] = {QGVAR(small_HD), QGVAR(medium_HD)}; +}; +class M_SPG9_HE: M_SPG9_HEAT { + GVAR(fragCount) = 800; + GVAR(metal) = 4695; + GVAR(charge) = 655; + GVAR(gurney_c) = 2800; + GVAR(gurney_k) = "1/2"; + GVAR(classes)[] = {QGVAR(small), QGVAR(tiny), QGVAR(medium), QGVAR(medium)}; +}; + + +// https://armypubs.army.mil/epubs/DR_pubs/DR_a/pdf/web/ARN18072_TC%203-22x84%20FINAL%20WEB.pdf +class R_MRAAWS_HEAT_F: RocketBase { // Table A-20. HEAT 751 + GVAR(skip) = 0; + GVAR(fragCount) = 500; + GVAR(metal) = 2265; + GVAR(charge) = 635; + GVAR(gurney_c) = 2970; + GVAR(gurney_k) = "1/2"; + GVAR(classes)[] = {QGVAR(small)}; + +}; +class R_MRAAWS_HEAT55_F: R_MRAAWS_HEAT_F { // Table A-16. HEAT 551C + GVAR(fragCount) = 300; + GVAR(metal) = 1940; + GVAR(charge) = 460; +}; +class R_MRAAWS_HE_F: R_MRAAWS_HEAT_F { // Table A-6. HE 441D RS + GVAR(fragCount) = 800; + GVAR(metal) = 2300; + GVAR(charge) = 590; + GVAR(gurney_c) = 2800; + GVAR(gurney_k) = "3/5"; + GVAR(classes)[] = {QGVAR(small)}; +}; + +class R_80mm_HE: RocketBase { // S-8D + GVAR(skip) = 0; + GVAR(charge) = 2150; + GVAR(metal) = 1650; + GVAR(gurney_c) = 2700; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 1200; + GVAR(classes)[] = {QGVAR(small), QGVAR(tiny), QGVAR(tiny)}; +}; +class R_60mm_HE: R_80mm_HE { // no idea but looks like a FFAR so made it weaker + GVAR(fragCount) = 1000; + GVAR(metal) = 1040; + GVAR(charge) = 3850; +}; + +class m_70mm_saami: RocketBase {///!!! fix me + GVAR(skip) = 0; + GVAR(charge) = 2150; + GVAR(metal) = 1650; + GVAR(gurney_c) = 2700; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 1200; + GVAR(classes)[] = {QGVAR(small), QGVAR(tiny), QGVAR(tiny)}; +}; + +class Rocket_04_HE_F: MissileBase { // Shrieker (Hydra 70) + GVAR(skip) = 0; + GVAR(classes)[] = {QGVAR(medium), QGVAR(medium_HD)}; + GVAR(fragCount) = 1200; // guesstimate / provides ~100 m frag range (1% chance to hit) + GVAR(metal) = 3850; + GVAR(charge) = 1040; + GVAR(gurney_c) = 2700; + GVAR(gurney_k) = "1/2"; +}; +class Rocket_03_HE_F: Rocket_04_HE_F { // S-8DM makes the most sense + GVAR(fragCount) = 600; // Thermobaric rounds usually have fewer fragments + GVAR(metal) = 1; + GVAR(charge) = 2; + GVAR(gurney_c) = 2300; +}; +class Rocket_04_AP_F: Rocket_04_HE_F { + GVAR(skip) = 1; +}; + +class ammo_Missile_CannonLaunchedBase: MissileBase { + GVAR(skip) = 1; +}; + +class R_230mm_fly: RocketBase { + GVAR(skip) = 0; + GVAR(charge) = 100; // kg + GVAR(metal) = 150; // kg + GVAR(gurney_c) = 2320; + GVAR(gurney_k) = "3/5"; + GVAR(fragCount) = 17500; + GVAR(classes)[] = {QGVAR(large), QGVAR(large), QGVAR(large), QGVAR(large), QGVAR(huge), QGVAR(huge_HD), QGVAR(huge)}; +}; + +class M_PG_AT: MissileBase { // DAGR M247 warhead + GVAR(skip) = 0; + GVAR(classes)[] = {QGVAR(tiny), QGVAR(small), QGVAR(small), QGVAR(medium)}; + GVAR(charge) = 910; + GVAR(metal) = 3085; + GVAR(gurney_c) = 2700; + GVAR(fragCount) = 700; // guesstimate / provides ~70 m frag range (0.5% chance to hit) + GVAR(gurney_k) = "1/2"; +}; +class M_AT: M_PG_AT { // DAR (Hydra 70) M151 warhead + GVAR(classes)[] = {QGVAR(medium), QGVAR(medium_HD)}; + GVAR(charge) = 1040; + GVAR(metal) = 3850; + GVAR(gurney_c) = 2700; + GVAR(fragCount) = 1200; // guesstimate / provides ~100 m frag range (0.5% chance to hit) +}; + +// ~~~~ Missiles: +class Missile_AGM_02_F: MissileBase { + // Source: http://fas.org/man/dod-101/sys/smart/agm-65.htm + GVAR(skip) = 0; + GVAR(classes)[] = {QGVAR(medium), QGVAR(medium_HD)}; + GVAR(fragCount) = 500; + GVAR(metal) = 56250; + GVAR(charge) = 39000; + GVAR(gurney_c) = 2700; + GVAR(gurney_k) = "1/2"; +}; +class Missile_AGM_01_F: Missile_AGM_02_F { // Kh-25MTP !!! fix me + GVAR(fragCount) = 1600; +}; +class M_Jian_AT: Missile_AGM_01_F { // imaginary missile? Not simiklar to any modern HJ-x or otherwise + GVAR(fragCount) = 600; +}; + +class M_Titan_AA: MissileBase { + GVAR(skip) = 0; + GVAR(fragCount) = 600; + GVAR(metal) = 1980; + GVAR(charge) = 1020; + GVAR(gurney_c) = 2501; + GVAR(gurney_k) = "1/2"; + GVAR(classes)[] = {QGVAR(tiny), QGVAR(tiny), QGVAR(small)}; +}; +class M_Titan_AT: MissileBase { + GVAR(skip) = 1; +}; +class M_Titan_AP: M_Titan_AT { + GVAR(skip) = 0; + GVAR(fragCount) = 800; // guesstimate / provides ~80 m frag range + GVAR(metal) = 400; + GVAR(charge) = 210; + GVAR(gurney_c) = 2800; + GVAR(gurney_k) = "3/5"; + GVAR(classes)[] = {QGVAR(medium_HD)}; +}; + + +class M_Scalpel_AT: MissileBase { // 9K121 Vikhr + GVAR(skip) = 0; + GVAR(classes)[] = {QGVAR(small), QGVAR(medium), QGVAR(medium_HD)}; + GVAR(fragCount) = 800; // guesstimate, provides ~80 m frag range (1% chance to hit) + GVAR(metal) = 10000; + GVAR(charge) = 3000; + GVAR(gurney_c) = 2700; + GVAR(gurney_k) = "1/2"; +}; +class ACE_Hellfire_AGM114K: M_Scalpel_AT { + // Source: http://www.designation-systems.net/dusrm/m-114.html + GVAR(skip) = 0; + + GVAR(classes)[] = {QGVAR(medium), QGVAR(medium_HD)}; + GVAR(fragCount) = 350; // guesstimate based on frag jacket, provides ~50 m frag range (1% chance to hit) + GVAR(metal) = 8000; + GVAR(charge) = 2400; + GVAR(gurney_c) = 2700; + GVAR(gurney_k) = "1/2"; +}; + +class ammo_Missile_CruiseBase: MissileBase {}; +class ammo_missile_cruise_01: ammo_Missile_CruiseBase { + GVAR(skip) = 0; + GVAR(classes)[] = {QGVAR(small), QGVAR(medium), QGVAR(large)}; + GVAR(fragCount) = 20000; + GVAR(metal) = 700000; + GVAR(charge) = 110000; + GVAR(gurney_c) = 2600; + GVAR(gurney_k) = "3/5"; +}; +class ammo_Missile_Cruise_01_Cluster: ammo_missile_cruise_01 {}; + +class ammo_Missile_AntiRadiationBase: MissileBase { + GVAR(gurney_k) = "1/2"; + GVAR(gurney_c) = 2400; +}; +class ammo_Missile_HARM: ammo_Missile_AntiRadiationBase { + GVAR(skip) = 0; + GVAR(classes)[] = {QGVAR(small), QGVAR(medium), QGVAR(large)}; + GVAR(charge) = 10000; + GVAR(metal) = 58000; + GVAR(fragCount) = 5000; +}; +class ammo_Missile_KH58: ammo_Missile_AntiRadiationBase { + GVAR(skip) = 0; + GVAR(classes)[] = {QGVAR(small), QGVAR(medium), QGVAR(large)}; + GVAR(charge) = 20000; + GVAR(metal) = 129000; + GVAR(fragCount) = 7500; +}; + +class M_Zephyr: M_Titan_AA { // model is an AMRAAM- WDU-41/B warhead + GVAR(skip) = 0; + GVAR(metal) = 12800; + GVAR(charge) = 4000; + GVAR(gurney_c) = 2900; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 2000; + GVAR(classes)[] = {QGVAR(tiny), QGVAR(medium), QGVAR(small), QGVAR(small)}; +}; + +class M_Air_AA: MissileBase { // Looks not real, maybe r-73 inspired? + GVAR(skip) = 0; + GVAR(charge) = 2450; + GVAR(metal) = 4950; + GVAR(gurney_c) = 2700; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 1000; + GVAR(classes)[] = {QGVAR(tiny), QGVAR(medium), QGVAR(small), QGVAR(small)}; +}; + +class Missile_AA_04_F: MissileBase { + GVAR(skip) = 0; + GVAR(charge) = 4400; + GVAR(metal) = 5000; + GVAR(gurney_c) = 2830; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 1600; +}; +class Missile_AA_03_F: Missile_AA_04_F { + GVAR(charge) = 2450; + GVAR(metal) = 4950; + GVAR(gurney_c) = 2700; + GVAR(fragCount) = 200; +}; + +class ammo_Missile_ShortRangeAABase: MissileBase { + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 1200; +}; +class ammo_Missile_rim116: ammo_Missile_ShortRangeAABase { + GVAR(skip) = 0; + GVAR(charge) = 40; // dg + GVAR(metal) = 73; // dg + GVAR(gurney_c) = 2400; + GVAR(fragCount) = 2000; + GVAR(classes)[] = {QGVAR(tiny), QGVAR(small)}; +}; +class ammo_Missile_BIM9X: ammo_Missile_ShortRangeAABase { + GVAR(skip) = 0; + GVAR(charge) = 44; // dg + GVAR(metal) = 50; // dg + GVAR(gurney_c) = 2900; + GVAR(fragCount) = 1600; + GVAR(classes)[] = {QGVAR(small), QGVAR(tiny)}; +}; +class ammo_Missile_AA_R73: ammo_Missile_ShortRangeAABase { + GVAR(skip) = 0; + GVAR(charge) = 245; // cg + GVAR(metal) = 495; // cg + GVAR(gurney_c) = 2700; + GVAR(classes)[] = {QGVAR(small), QGVAR(small), QGVAR(tiny)}; +}; + +class ammo_Missile_MediumRangeAABase: MissileBase { + GVAR(gurney_c) = 2900; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 2000; +}; +class ammo_Missile_rim162: ammo_Missile_MediumRangeAABase { + GVAR(skip) = 0; + GVAR(charge) = 138; // dg + GVAR(metal) = 252; // dg + GVAR(gurney_c) = 2400; + GVAR(classes)[] = {QGVAR(small), QGVAR(medium), QGVAR(medium), QGVAR(large)}; +}; +class ammo_Missile_AMRAAM_C: ammo_Missile_MediumRangeAABase { + GVAR(skip) = 0; + GVAR(charge) = 70; // dg + GVAR(metal) = 128; // dg + GVAR(classes)[] = {QGVAR(small), QGVAR(medium), QGVAR(medium)}; +}; +class ammo_Missile_AMRAAM_D: ammo_Missile_MediumRangeAABase { + GVAR(skip) = 0; + GVAR(charge) = 70; // dg + GVAR(metal) = 128; // dg + GVAR(classes)[] = {QGVAR(small), QGVAR(medium), QGVAR(medium)}; +}; +class ammo_Missile_AA_R77: ammo_Missile_MediumRangeAABase { + GVAR(skip) = 0; + GVAR(charge) = 80; // dg + GVAR(metal) = 145; // dg + GVAR(gurney_c) = 2700; + GVAR(fragCount) = 1200; + GVAR(classes)[] = {QGVAR(small), QGVAR(medium), QGVAR(large), QGVAR(large)}; +}; + +class ammo_Missile_LongRangeAABase: MissileBase { + GVAR(skip) = 1; +}; + +class M_Vorona_HEAT: MissileBase { + // tandem shaped charges + GVAR(skip) = 1; +}; +class M_Vorona_HE: M_Vorona_HEAT { + // All signs point to this being a thermobaric round so low frag count + GVAR(skip) = 0; + GVAR(fragCount) = 200; + GVAR(metal) = 13800; + GVAR(charge) = 4950; + GVAR(gurney_c) = 2800; + GVAR(gurney_k) = "1/2"; + GVAR(classes)[] = {QGVAR(tiny)}; +}; + +class M_127mm_Firefist_AT: RocketBase { // HOT missile + GVAR(skip) = 1; +}; + +class M_NLAW_AT_F: MissileBase { + GVAR(skip) = 1; +}; + +// ~~~~ Shell +class Sh_75mm_Railgun_APFSDS: ShellBase { + GVAR(skip) = 1; +}; +class Sh_120mm_APFSDS: ShellBase { + GVAR(skip) = 1; +}; +class Sh_125mm_APFSDS: ShellBase { + GVAR(skip) = 1; +}; + +class Sh_155mm_AMOS: ShellBase { + // Source: http://www.globalsecurity.org/military/systems/munitions/m795.htm + GVAR(skip) = 0; + GVAR(charge) = 9979; + GVAR(metal) = 36000; + GVAR(gurney_c) = 2440; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 3250; // http://18.195.19.6/bitstream/handle/20.500.12242/1200/15-01916.pdf?sequence=1&isAllowed=y + GVAR(classes)[] = {QGVAR(large), QGVAR(large), QGVAR(large_HD), QGVAR(large), QGVAR(huge), QGVAR(huge_HD), QGVAR(huge)}; +}; + +class Sh_82mm_AMOS: Sh_155mm_AMOS { // VO-832DU + GVAR(charge) = 420; + GVAR(metal) = 2680; + GVAR(fragCount) = 1600; // based on mass and fragment energy/count + GVAR(classes)[] = {QGVAR(medium), QGVAR(medium_HD)}; +}; + +class Sh_120mm_HE: ShellBase { + GVAR(skip) = 0; + GVAR(charge) = 3148; + GVAR(metal) = 23000; + GVAR(gurney_c) = 2830; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 2000; + GVAR(classes)[] = {QGVAR(small), QGVAR(medium), QGVAR(medium), QGVAR(medium_HD)}; +}; +class Sh_120mm_HEAT_MP: ShellBase { + GVAR(skip) = 0; + GVAR(charge) = 2500; + GVAR(metal) = 5000; + GVAR(gurney_c) = 2500; + GVAR(gurney_k) = "1/2"; + GVAR(fragCount) = 1000; + GVAR(classes)[] = {QGVAR(small), QGVAR(medium), QGVAR(medium), QGVAR(medium_HD)}; +}; + +class ammo_ShipCannon_120mm_HE: Sh_155mm_AMOS { + GVAR(charge) = 3148; + GVAR(metal) = 23000; + GVAR(gurney_c) = 2830; + GVAR(fragCount) = 2000; + GVAR(classes)[] = {QGVAR(small), QGVAR(small), QGVAR(medium), QGVAR(large)}; +}; + +class Sh_125mm_HEAT: Sh_120mm_HE { + GVAR(skip) = 1; + GVAR(fragCount) = 400; + GVAR(metal) = 16760; + GVAR(charge) = 1640; + GVAR(gurney_c) = 2901; +}; +class Sh_125mm_HE: Sh_120mm_HE { + GVAR(metal) = 19900; + GVAR(charge) = 3400; + GVAR(gurney_c) = 2901; +}; + +class Sh_105mm_HEAT_MP: Sh_125mm_HEAT { + GVAR(skip) = 0; + GVAR(classes)[] = {QGVAR(small), QGVAR(medium), QGVAR(medium), QGVAR(medium_HD)}; + GVAR(fragCount) = 1600; // based on mass and fragment energy/count + GVAR(metal) = 11400; + GVAR(charge) = 7100; + GVAR(gurney_c) = 2800; +}; + +class ModuleOrdnanceHowitzer_F_ammo: Sh_155mm_AMOS { + GVAR(skip) = 0; + GVAR(metal) = 1950; + GVAR(charge) = 15800; + GVAR(gurney_c) = 2320; + GVAR(gurney_k) = "1/2"; + GVAR(classes)[] = {QGVAR(large), QGVAR(large), QGVAR(large_HD), QGVAR(large), QGVAR(huge), QGVAR(huge_HD), QGVAR(huge)}; +}; +class ammo_Penetrator_Base: ShellBase { + GVAR(skip) = 1; +}; + +// ~~~~ Special +class ProbingBeam_01_F: BulletBase { + GVAR(skip) = 1; +}; + +class IRStrobeBase: GrenadeCore { + GVAR(skip) = 1; +}; +class FlareCore: GrenadeCore { // flares shouldn't have EH, but in case + GVAR(skip) = 1; +}; + +class Default; +class Laserbeam: Default { + GVAR(skip) = 1; +}; + +class FuelExplosion: Default { + GVAR(skip) = 1; +}; + +class HelicopterExploSmall: ShellBase { + GVAR(skip) = 1; +}; + +class LightningBolt: ShellBase { + GVAR(skip) = 1; +}; + +class M_Mo_82mm_AT: MissileBase { + GVAR(skip) = 1; +}; diff --git a/addons/frag/CfgAmmoFragSpawner.hpp b/addons/frag/CfgAmmoFragSpawner.hpp new file mode 100644 index 00000000000..b1bd0726e1d --- /dev/null +++ b/addons/frag/CfgAmmoFragSpawner.hpp @@ -0,0 +1,81 @@ +#define TARGETED_SPAWNER_PROTOTYPE(size) class GVAR(DOUBLES(size,spawner_2_short)): GVAR(spawnbase_targeted) {\ + submunitionAmmo = QGVAR(size);\ +};\ +class GVAR(DOUBLES(size,spawner_2_mid)): GVAR(DOUBLES(size,spawner_2_short)) {\ + submunitionConeAngle = 2;\ +};\ +class GVAR(DOUBLES(size,spawner_2_far)): GVAR(DOUBLES(size,spawner_2_short)) {\ + submunitionConeAngle = 0.9;\ +};\ +class GVAR(DOUBLES(size,spawner_3_short)): GVAR(DOUBLES(size,spawner_2_short)) {\ + submunitionConeType[] = {"random", 3};\ +};\ +class GVAR(DOUBLES(size,spawner_3_mid)): GVAR(DOUBLES(size,spawner_3_short)) {\ + submunitionConeAngle = 2;\ +};\ +class GVAR(DOUBLES(size,spawner_3_far)): GVAR(DOUBLES(size,spawner_3_short)) {\ + submunitionConeAngle = 0.9;\ +} + +#define RANDOM_SPAWNER_PROTOTYPE(size,count) class GVAR(DOUBLES(TRIPLES(random,size,count),mid)): GVAR(spawnbase) {\ + submunitionConeType[] = {"random", count};\ + submunitionAmmo = QGVAR(size);\ + submunitionConeAngle = 85;\ + triggerSpeedCoef[] = {-1.5, 1.5};\ +};\ +class GVAR(DOUBLES(TRIPLES(random,size,count),high)): GVAR(spawnbase) {\ + submunitionConeType[] = {"random", count};\ + submunitionAmmo = QGVAR(size);\ + submunitionConeAngle = 80;\ + triggerSpeedCoef[] = {0.75, 1.5};\ +};\ +class GVAR(DOUBLES(TRIPLES(random,size,count),top)): GVAR(spawnbase) {\ + submunitionConeType[] = {"random", count};\ + submunitionAmmo = QGVAR(size);\ + submunitionConeAngle = 60;\ + triggerSpeedCoef[] = {0.75, 1.5};\ +} + +class GVAR(spawnbase): B_65x39_Caseless { + GVAR(skip) = 1; + deleteParentWhenTriggered = 1; + explosionEffects = ""; + submunitionConeType[] = {"random", 25}; + submunitionAmmo[] = {QGVAR(tiny), 3, QGVAR(tiny_HD), 3, QGVAR(small), 4, QGVAR(small_HD), 4, QGVAR(medium_HD), 5}; + submunitionDirectionType = "SubmunitionModelDirection"; + submunitionConeAngleHorizontal = 15; + submunitionConeAngle = 87; + submunitionInitialOffset[] = {0,0,0}; + submunitionInitSpeed = 0; + submunitionParentSpeedCoef = 1; + triggerSpeedCoef[] = {0.75, 1.5}; + triggerTime = 0; +}; + + RANDOM_SPAWNER_PROTOTYPE(tiny,15); + RANDOM_SPAWNER_PROTOTYPE(tiny,10); + RANDOM_SPAWNER_PROTOTYPE(tiny,5); + RANDOM_SPAWNER_PROTOTYPE(small,15); + RANDOM_SPAWNER_PROTOTYPE(small,10); + RANDOM_SPAWNER_PROTOTYPE(small,5); + +/* + * Targeted fragment spawner, for when multiple fragments are spawned (1-3) + */ +class GVAR(spawnbase_targeted): GVAR(spawnbase) { + submunitionConeType[] = {"random", 2}; + submunitionConeAngle = 4.5; + triggerSpeedCoef[] = {0.5, 1}; +}; + + +TARGETED_SPAWNER_PROTOTYPE(tiny); +TARGETED_SPAWNER_PROTOTYPE(tiny_HD); +TARGETED_SPAWNER_PROTOTYPE(small); +TARGETED_SPAWNER_PROTOTYPE(small_HD); +TARGETED_SPAWNER_PROTOTYPE(medium); +TARGETED_SPAWNER_PROTOTYPE(medium_HD); +TARGETED_SPAWNER_PROTOTYPE(large); +TARGETED_SPAWNER_PROTOTYPE(large_HD); +TARGETED_SPAWNER_PROTOTYPE(huge); +TARGETED_SPAWNER_PROTOTYPE(huge_HD); diff --git a/addons/frag/CfgAmmoSpall.hpp b/addons/frag/CfgAmmoSpall.hpp new file mode 100644 index 00000000000..60780e564ed --- /dev/null +++ b/addons/frag/CfgAmmoSpall.hpp @@ -0,0 +1,158 @@ +class GVAR(spallBase): B_65x39_Caseless { + GVAR(skip) = 1; + submunitionAmmo[] = {QGVAR(small),4,QGVAR(medium),3,QGVAR(large),2,QGVAR(huge),1}; + submunitionConeType[] = {"random", 20}; + submunitionConeAngle = 40; + submunitionDirectionType = "SubmunitionModelDirection"; + triggerTime = 0; + submunitionInitialOffset[] = {0,0,0}; + submunitionInitSpeed = 0; + triggerSpeedCoef[] = {0.75,1.25}; + deleteParentWhenTriggered = 1; + submunitionParentSpeedCoef = 1; +}; + + +/* + * ground + */ +class GVAR(ground_spall_tiny): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 9, QGVAR(small), 1}; + submunitionConeType[] = {"poissondisccenter", 4}; +}; + +class GVAR(ground_spall_small): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(small), 4,QGVAR(medium), 1}; + submunitionConeType[] = {"poissondisccenter", 6}; +}; + +class GVAR(ground_spall_medium): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(small), 2, QGVAR(small_HD), 1,QGVAR(medium), 3, QGVAR(medium_HD), 1, QGVAR(large), 2}; + submunitionConeType[] = {"poissondisccenter", 15}; +}; + +class GVAR(ground_spall_large): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 3, QGVAR(tiny_HD), 3, QGVAR(small), 4, QGVAR(small_HD), 4, QGVAR(medium_HD), 5, QGVAR(large), 1, QGVAR(large_HD), 2}; + submunitionConeType[] = {"poissondisccenter", 15}; +}; + +class GVAR(ground_spall_huge): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 3, QGVAR(tiny_HD), 3, QGVAR(small), 4, QGVAR(small_HD), 4, QGVAR(medium), 5, QGVAR(large), 1, QGVAR(large_HD), 2}; + submunitionConeType[] = {"poissondisccenter", 20}; +}; + + +/* + * rock + */ +class GVAR(rock_spall_tiny): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(small), 4, QGVAR(medium_HD), 1}; + submunitionConeType[] = {"poissondisccenter", 4}; +}; + +class GVAR(rock_spall_small): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(small), 4, QGVAR(medium), 1}; + submunitionConeType[] = {"poissondisccenter", 5}; +}; + +class GVAR(rock_spall_medium): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(small), 4, QGVAR(medium), 1, QGVAR(medium_HD), 2, QGVAR(large_HD), 1}; + submunitionConeType[] = {"poissondisccenter", 7}; +}; + +class GVAR(rock_spall_large): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(small), 5, QGVAR(medium), 2, QGVAR(large), 1}; + submunitionConeType[] = {"poissondisccenter", 10}; +}; + +class GVAR(rock_spall_huge): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(small), 5, QGVAR(medium), 2, QGVAR(large), 1, QGVAR(huge_HD), 1}; + submunitionConeType[] = {"poissondisccenter", 13}; +}; + + +/* + * wood + */ +class GVAR(wood_spall_tiny): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 2, QGVAR(small), 4}; + submunitionConeType[] = {"poissondisccenter", 4}; +}; + +class GVAR(wood_spall_small): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 2, QGVAR(small), 4, QGVAR(medium), 1}; + submunitionConeType[] = {"poissondisccenter", 6}; +}; + +class GVAR(wood_spall_medium): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 2, QGVAR(small), 2, QGVAR(medium), 2, QGVAR(medium_HD), 1, QGVAR(large_HD), 1}; + submunitionConeType[] = {"poissondisccenter", 8}; +}; + +class GVAR(wood_spall_large): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 1, QGVAR(small), 3, QGVAR(medium), 2, QGVAR(large_HD), 2, QGVAR(huge_HD), 1}; + submunitionConeType[] = {"poissondisccenter", 10}; +}; + +class GVAR(wood_spall_huge): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 1, QGVAR(small), 3, QGVAR(medium), 2, QGVAR(large_HD), 2, QGVAR(huge_HD), 1}; + submunitionConeType[] = {"poissondisccenter", 12}; +}; + + +/* + * concrete + */ +class GVAR(concrete_spall_tiny): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 6, QGVAR(tiny_HD), 3, QGVAR(small), 1}; + submunitionConeType[] = {"poissondisccenter", 4}; +}; + +class GVAR(concrete_spall_small): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 4, QGVAR(tiny_HD), 2, QGVAR(small), 2, QGVAR(medium), 1}; + submunitionConeType[] = {"poissondisccenter", 6}; +}; + +class GVAR(concrete_spall_medium): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 6, QGVAR(tiny_HD), 1, QGVAR(small), 4, QGVAR(medium), 5, QGVAR(large_HD), 1}; + submunitionConeType[] = {"poissondisccenter", 9}; +}; + +class GVAR(concrete_spall_large): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 6, QGVAR(small), 4, QGVAR(medium), 3, QGVAR(large), 1, QGVAR(large_HD), 2}; + submunitionConeType[] = {"poissondisccenter", 12}; +}; + +class GVAR(concrete_spall_huge): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 8, QGVAR(small), 4, QGVAR(medium), 3, QGVAR(large), 1, QGVAR(large_HD), 1, QGVAR(huge_HD), 1}; + submunitionConeType[] = {"poissondisccenter", 18}; +}; + + +/* + * metal + */ +class GVAR(metal_spall_tiny): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 9, QGVAR(small), 1}; + submunitionConeType[] = {"poissondisccenter", 4}; +}; + +class GVAR(metal_spall_small): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 4, QGVAR(small), 2, QGVAR(medium), 1}; + submunitionConeType[] = {"poissondisccenter", 6}; +}; + +class GVAR(metal_spall_medium): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 5, QGVAR(small), 4, QGVAR(medium), 2, QGVAR(large), 1}; + submunitionConeType[] = {"poissondisccenter", 8}; +}; + +class GVAR(metal_spall_large): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 4, QGVAR(small), 3, QGVAR(medium), 2, QGVAR(large), 1, QGVAR(huge), 1}; + submunitionConeType[] = {"poissondisccenter", 12}; +}; + +class GVAR(metal_spall_huge): GVAR(spallBase) { + submunitionAmmo[] = {QGVAR(tiny), 8, QGVAR(small), 3, QGVAR(medium), 2, QGVAR(large), 3, QGVAR(huge), 1}; + submunitionConeType[] = {"poissondisccenter", 18}; +}; diff --git a/addons/frag/README.md b/addons/frag/README.md index ca62771f00e..797e652b207 100644 --- a/addons/frag/README.md +++ b/addons/frag/README.md @@ -1,4 +1,4 @@ ace_frag ======== -Shrapnel system for explosives. +Explosive fragmentation, round spalling, and explosive reflection diff --git a/addons/frag/XEH_PREP.hpp b/addons/frag/XEH_PREP.hpp index a7fb8ff8c34..7af205aa8e7 100644 --- a/addons/frag/XEH_PREP.hpp +++ b/addons/frag/XEH_PREP.hpp @@ -1,25 +1,25 @@ -PREP(dev_debugAmmo); - -PREP(doSpall); -PREP(fired); -PREP(frago); -PREP(spallTrack); - -// * Other */ PREP(addBlackList); -PREP(dev_addTrack); -PREP(dev_drawTraces); -PREP(spallHP); -PREP(dev_startTracing); -PREP(dev_stopTracing); -PREP(dev_trackTrace); - -// New tracking mechanisms -PREP(masterPFH); -PREP(pfhRound); -PREP(addPfhRound); - -// Explosive Reflection -PREP(findReflections); +PREP(dev_addRound); +PREP(dev_clearTraces); +PREP(dev_debugAmmo); +PREP(dev_drawTrace); +PREP(dev_fragCalcDump); +PREP(dev_trackHitBox); +PREP(dev_trackObj); +PREP(dev_sphereDraw); PREP(doExplosions); +PREP(doFrag); +PREP(doFragRandom); +PREP(doFragTargeted); PREP(doReflections); +PREP(doSpallLocal); +PREP(doSpallServer); +PREP(findReflections); +PREP(fired); +PREP(getMaterialInfo); +PREP(getSpallInfo); +PREP(getFragInfo); +PREP(initMaterialCache); +PREP(roundInitFrag); +PREP(shouldFrag); +PREP(shouldSpall); diff --git a/addons/frag/XEH_postInit.sqf b/addons/frag/XEH_postInit.sqf index cc58e1d15b2..47affc99718 100644 --- a/addons/frag/XEH_postInit.sqf +++ b/addons/frag/XEH_postInit.sqf @@ -1,32 +1,46 @@ #include "script_component.hpp" -if (isServer) then { - GVAR(lastFragTime) = -1; - [QGVAR(frag_eh), LINKFUNC(frago)] call CBA_fnc_addEventHandler; -}; - -["CBA_settingsInitialized", { - if (!GVAR(enabled)) exitWith {}; - - // Register fire event handler - ["ace_firedPlayer", LINKFUNC(fired)] call CBA_fnc_addEventHandler; - ["ace_firedNonPlayer", LINKFUNC(fired)] call CBA_fnc_addEventHandler; - ["ace_firedPlayerVehicle", LINKFUNC(fired)] call CBA_fnc_addEventHandler; - ["ace_firedNonPlayerVehicle", LINKFUNC(fired)] call CBA_fnc_addEventHandler; - - addMissionEventHandler ["EachFrame", {call FUNC(masterPFH)}]; -}] call CBA_fnc_addEventHandler; - -// Cache for ammo type configs -GVAR(cacheRoundsTypesToTrack) = createHashMap; - - -// Debug stuff: - -#ifdef DRAW_FRAG_INFO -[] call FUNC(dev_startTracing); +[ + "CBA_settingsInitialized", + { + ["ace_firedPlayer", LINKFUNC(fired)] call CBA_fnc_addEventHandler; + ["ace_firedNonPlayer", LINKFUNC(fired)] call CBA_fnc_addEventHandler; + ["ace_firedPlayerVehicle", LINKFUNC(fired)] call CBA_fnc_addEventHandler; + ["ace_firedNonPlayerVehicle", LINKFUNC(fired)] call CBA_fnc_addEventHandler; +#ifdef DEBUG_MODE_DRAW + [QGVAR(dev_clearTraces), LINKFUNC(dev_clearTraces)] call CBA_fnc_addEventHandler; + + if (!hasInterface) exitWith {}; + ["ace_firedPlayerVehicleNonLocal", LINKFUNC(dev_fired)] call CBA_fnc_addEventHandler; + ["ace_firedPlayerNonLocal", LINKFUNC(dev_fired)] call CBA_fnc_addEventHandler; + GVAR(dev_drawPFEH) = [LINKFUNC(dev_drawTrace), 0] call CBA_fnc_addPerFrameHandler; + [ + "ace_interact_menu_newControllableObject", + { + params ["_type"]; + + private _action = [ + QGVAR(debugReset), + "Reset ACE Frag traces", + "", + { + [QGVAR(dev_clearTraces), []] call CBA_fnc_globalEvent; + }, + {GVAR(dev_trackLines) isNotEqualTo createHashMap} + ] call EFUNC(interact_menu,createAction); + [ + _type, + 1, + ["ACE_SelfActions"], + _action, + true + ] call EFUNC(interact_menu,addActionToClass); + } + ] call CBA_fnc_addEventHandler; #endif + } +] call CBA_fnc_addEventHandler; -#ifdef DEBUG_MODE_FULL +#ifdef LOG_FRAG_INFO [true, true, 30] call FUNC(dev_debugAmmo); #endif diff --git a/addons/frag/XEH_preInit.sqf b/addons/frag/XEH_preInit.sqf index dc616917b88..3782ddf6e7d 100644 --- a/addons/frag/XEH_preInit.sqf +++ b/addons/frag/XEH_preInit.sqf @@ -6,20 +6,30 @@ PREP_RECOMPILE_START; #include "XEH_PREP.hpp" PREP_RECOMPILE_END; -GVAR(blackList) = []; -GVAR(traceFrags) = false; +call FUNC(initMaterialCache); -GVAR(spallHPData) = []; -GVAR(spallIsTrackingCount) = 0; +GVAR(spallInfoCache) = createHashMap; +GVAR(shouldSpallCache) = createHashMap; +GVAR(nextSpallAllowTime) = -1; -GVAR(traceID) = -1; -GVAR(traces) = []; -GVAR(tracesStarted) = false; - -GVAR(lastIterationIndex) = 0; -GVAR(objects) = []; -GVAR(arguments) = []; +GVAR(shouldFragCache) = createHashMap; +GVAR(fragInfoCache) = createHashMap; +GVAR(lastFragTime) = -1; #include "initSettings.inc.sqf" +GVAR(dev_trackLines) = createHashMap; +GVAR(dev_hitBoxes) = createHashMap; +GVAR(dev_eventSpheres) = []; +GVAR(dev_drawPFEH) = -1; + +#ifdef DEBUG_MODE_DRAW +#include "initSettingsDebug.inc.sqf" +#endif + +if (isServer) then { + [QGVAR(explosionEvent), LINKFUNC(doFragServer)] call CBA_fnc_addEventHandler; + [QGVAR(spallEvent), LINKFUNC(doSpallServer)] call CBA_fnc_addEventHandler; +}; + ADDON = true; diff --git a/addons/frag/functions/fnc_addBlackList.sqf b/addons/frag/functions/fnc_addBlacklist.sqf similarity index 56% rename from addons/frag/functions/fnc_addBlackList.sqf rename to addons/frag/functions/fnc_addBlacklist.sqf index d0cc127d059..aecdbf832d2 100644 --- a/addons/frag/functions/fnc_addBlackList.sqf +++ b/addons/frag/functions/fnc_addBlacklist.sqf @@ -10,12 +10,12 @@ * None * * Example: - * [bullet] call ace_frag_fnc_addBlackList + * [_projectile] call ace_frag_fnc_addBlackList * * Public: No */ -params ["_round"]; -TRACE_1("addBlackList",_round); +params ["_projectile"]; +TRACE_2("addBlackList",_projectile,typeOf projectile); -GVAR(blackList) pushBack _round; +_projectile setVariable [QGVAR(blacklisted)]; diff --git a/addons/frag/functions/fnc_addPfhRound.sqf b/addons/frag/functions/fnc_addPfhRound.sqf deleted file mode 100644 index 358a9ee71ea..00000000000 --- a/addons/frag/functions/fnc_addPfhRound.sqf +++ /dev/null @@ -1,77 +0,0 @@ -#include "..\script_component.hpp" -/* - * Author: Jaynus, NouberNou - * Starts tracking a round that will frag. - * Should only be called once per round. - * - * Arguments: - * 0: Shooter - * 1: Ammo classname - * 2: Projectile - * - * Return Value: - * None - * - * Example: - * [player, "handGrenade", bullet] call ace_frag_fnc_addPfhRound - * - * Public: No - */ - -params ["_gun", "_type", "_round"]; -TRACE_3("addPfhRound",_gun,_type,_round); - -if (!GVAR(enabled)) exitWith {TRACE_1("setting disabled",_this);}; - -if (!alive _round) exitWith {TRACE_1("round dead?",_this);}; - -if (_round in GVAR(blackList)) exitWith { - TRACE_1("round in blackList",_this); - REM(GVAR(blackList),_round); -}; - -// Exit on max track -if ((count GVAR(objects)) >= GVAR(maxTrack)) exitWith {TRACE_1("maxTrack limit",count GVAR(objects));}; - -private _doSpall = false; -if (GVAR(SpallEnabled)) then { - if (GVAR(spallIsTrackingCount) <= 0) then { - GVAR(spallHPData) = []; - }; - if (GVAR(spallIsTrackingCount) > 5) then { - TRACE_1("At Spall Limit",GVAR(spallIsTrackingCount)); - } else { - _doSpall = true; - INC(GVAR(spallIsTrackingCount)); - }; - TRACE_2("",_doSpall,GVAR(spallIsTrackingCount)); -}; - -#ifdef DRAW_FRAG_INFO -[ACE_player, _round, [0, 1, 0, 1]] call FUNC(dev_addTrack); -#endif - -// We only do the single track object check here. -// We should do an {!(_round in GVAR(objects))} -// But we leave that out here for optimization. So this cannot be a framework function -// Otherwise, it should only be added once and from the FiredEH -if (alive _round) then { - private _spallTrack = []; - private _spallTrackID = []; - - private _args = [ - _round, getPosASL _round, velocity _round, _type, diag_frameno, getPosASL _round, _doSpall, _spallTrack, _spallTrackID, - getNumber (configFile >> "CfgAmmo" >> _type >> QGVAR(skip)), - getNumber (configFile >> "CfgAmmo" >> _type >> "explosive"), - getNumber (configFile >> "CfgAmmo" >> _type >> "indirectHitRange"), - getNumber (configFile >> "CfgAmmo" >> _type >> QGVAR(force)), - getNumber (configFile >> "CfgAmmo" >> _type >> "indirecthit") * (sqrt (getNumber (configFile >> "CfgAmmo" >> _type >> "indirectHitRange"))) - ]; - TRACE_1("Initializing track",_round); - GVAR(objects) pushBack _round; - GVAR(arguments) pushBack _args; - - if (_doSpall) then { - [_round, 1, _spallTrack, _spallTrackID] call FUNC(spallTrack); - }; -}; diff --git a/addons/frag/functions/fnc_dev_addRound.sqf b/addons/frag/functions/fnc_dev_addRound.sqf new file mode 100644 index 00000000000..96b8d1e1aeb --- /dev/null +++ b/addons/frag/functions/fnc_dev_addRound.sqf @@ -0,0 +1,93 @@ +#include "..\script_component.hpp" +/* + * Author: Lambda.Tiger + * This function initialize projectile tracking of a round so that it's path + * can be drawn in debug mode. It may optionally include hit / explode / + * deflected event handlers that spawn color coded spheres on each event, + * green / red / blue, respectively. + * + * Arguments: + * 0: Projectile to be tracked + * 1: Add projectile hit/explode/defelceted event handlers (default: true) + * 2: Should the round track be blue. True results in blue traces, false in red (default: true) + * + * Return Value: + * Nothing Useful + * + * Example: + * [_projectile, false, false] call ace_frag_dev_addRound + * + * Public: No + */ + +params [ + "_projectile", + ["_addProjectileEventHandlers", true], + ["_isTraceBlue", true] +]; + +if (_isTraceBlue) then { + GVAR(dev_trackLines) set [getObjectID _projectile, [[getPosATL _projectile], [0, 0, 1, 1]]]; +} else { + GVAR(dev_trackLines) set [getObjectID _projectile, [[getPosATL _projectile], [1, 0, 0, 1]]]; +}; + +// event handler to track round and cleanup when round is "dead" +[ + { + if (isGamePaused || accTime == 0) exitWith {}; + params ["_projectile", "_handle"]; + + if (!alive _projectile) exitWith { + _handle call CBA_fnc_removePerFrameHandler; + }; + + private _projectileArray = GVAR(dev_trackLines) get (getObjectID _projectile); + + if (isNil "_projectileArray") exitWith { + _handle call CBA_fnc_removePerFrameHandler; + }; + + (_projectileArray#0) pushBack getPosATL _projectile; + }, + 0, + _projectile +] call CBA_fnc_addPerFrameHandler; + +if (!_addProjectileEventHandlers) exitWith {}; + +_projectile addEventHandler [ + "HitPart", + { + params ["_projectile", "", "", "_posASL"]; + private _posArr = (GVAR(dev_trackLines) get (getObjectID _projectile))#0; + _posArr pushBack ASLtoATL _posASL; + if (GVAR(dbgSphere)) then { + [_posASL, "green"] call FUNC(dev_sphereDraw); + }; + } +]; + +_projectile addEventHandler [ + "Explode", + { + params ["_projectile", "_posASL"]; + private _posArr = (GVAR(dev_trackLines) get (getObjectID _projectile))#0; + _posArr pushBack ASLtoATL _posASL; + if (GVAR(dbgSphere)) then { + [_posASL, "red"] call FUNC(dev_sphereDraw); + }; + } +]; + +_projectile addEventHandler [ + "Deflected", + { + params ["_projectile", "_posASL"]; + private _posArr = (GVAR(dev_trackLines) get (getObjectID _projectile))#0; + _posArr pushBack ASLtoATL _posASL; + if (GVAR(dbgSphere)) then { + [_posASL, "blue"] call FUNC(dev_sphereDraw); + }; + } +]; diff --git a/addons/frag/functions/fnc_dev_addTrack.sqf b/addons/frag/functions/fnc_dev_addTrack.sqf deleted file mode 100644 index 0e75a9fb994..00000000000 --- a/addons/frag/functions/fnc_dev_addTrack.sqf +++ /dev/null @@ -1,26 +0,0 @@ -#include "..\script_component.hpp" -/* - * Author: ACE-Team - * - * - * Arguments: - * None - * - * Return Value: - * None - * - * Example: - * call ace_frag_fnc_dev_addTrack - * - * Public: No - */ - -params ["_origin", "_obj", ["_color", [1, 0, 0, 1]]]; - -private _positions = []; -private _objSpd = vectorMagnitude (velocity _obj); -_positions pushBack [getPos _obj, _objSpd]; -private _data = [_origin, typeOf _origin, typeOf _obj, _objSpd, _positions, _color]; - -private _index = GVAR(traces) pushBack _data; -[DFUNC(dev_trackTrace), 0, [_obj, _index, CBA_missionTime]] call CBA_fnc_addPerFrameHandler; diff --git a/addons/frag/functions/fnc_dev_clearTraces.sqf b/addons/frag/functions/fnc_dev_clearTraces.sqf new file mode 100644 index 00000000000..6e2b820bacb --- /dev/null +++ b/addons/frag/functions/fnc_dev_clearTraces.sqf @@ -0,0 +1,25 @@ +#include "..\script_component.hpp" +/* + * Author: Lambda.Tiger + * Clears all dev spheres and traces. + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * call ace_frag_fnc_dev_clearTraces + * + * Public: No + */ + +{ + deleteVehicle _x; +} forEach GVAR(dev_eventSpheres); + +GVAR(dev_eventSpheres) = []; + +GVAR(dev_trackLines) = createHashMap; +GVAR(dev_hitBoxes) = createHashMap; diff --git a/addons/frag/functions/fnc_dev_debugAmmo.sqf b/addons/frag/functions/fnc_dev_debugAmmo.sqf index 4484edbdc43..fae388f1b8c 100644 --- a/addons/frag/functions/fnc_dev_debugAmmo.sqf +++ b/addons/frag/functions/fnc_dev_debugAmmo.sqf @@ -1,11 +1,15 @@ -#define DEBUG_MODE_FULL #include "..\script_component.hpp" /* - * Author: ACE-Team - * + * Author: ACE-Team, Lambda.Tiger + * This function will dump every ammo config that would generate ace_frag + * fragments that could be fired from a weapon. * * Arguments: - * None + * 0: Log ammo types that wouldn't normally frag (default: false) + * 1: Only print ammo without ACE_frag entries, inherited or otherwise (default: true) + * 2: Only export ammo classes of classes referenced in CfgMagazines and their + * submunitions (default: false) + * 3: Force a CSV format on debug print. (default: false) * * Return Value: * None @@ -17,65 +21,91 @@ */ params [ - ["_debugMissing", true, [false]], - ["_debugForce", false, [false]], - ["_debugSkippedFragPower", 30, [0]] + ["_logAll", false, [false]], + ["_printOnlyIncomplete", true, [true]], + ["_onlyShotAmmoTypes", false, [false]], + ["_csvFormat", false, [false]] ]; diag_log text format ["~~~~~~~~~~~~~Start [%1]~~~~~~~~~~~~~", _this]; +if (_csvFormat) then { + diag_log text format ["ammo,gurney_c,gurney_m,gurney_k,gurney_gC,skip,fragCount,Inheritance"]; +}; -private _allMagsConfigs = configProperties [configFile >> "CfgMagazines", "isClass _x", true]; -private _processedCfgAmmos = []; +// Gather all configs, either those that could be created from firing or all classes +private _allAmmoConfigs = []; +if (_onlyShotAmmoTypes) then { + private _configSearchFunction = { + params [ + ["_ammo", "", [""]] + ]; + if (_ammo isEqualTo "" || {_ammo in _allAmmoConfigs}) exitWith {}; + _allAmmoConfigs pushBack _ammo; + private _cfgAmmoRoot = configFile >> "CfgAmmo"; + private _submunitionConfig = _cfgAmmoRoot >> _ammo >> "submunitionAmmo"; + if (isArray _submunitionConfig) then { + private _subMunition = getArray _submunitionConfig; + for "_i" from 0 to count _subMunition - 1 do { + if (_i mod 2 == 0) then { + configName (_cfgAmmoRoot >> (_subMunition#_i)) call _configSearchFunction; + }; + }; + } else { + private _subMunition = getText _submunitionConfig; + if (_subMunition isNotEqualTo "") then { + configName (_cfgAmmoRoot >> _subMunition) call _configSearchFunction; + }; + }; + }; + private _allMagazineConfigs = configProperties [configFile >> "CfgMagazines", "isClass _x", true]; + private _cfgAmmoCfgPath = configFile >> "CfgAmmo"; + { + private _magAmmo = getText (_x >> "ammo"); + configName (_cfgAmmoCfgPath >> _magAmmo) call _configSearchFunction; + } forEach _allMagazineConfigs; +} else { + _allAmmoConfigs = configProperties [configFile >> "CfgAmmo", "isClass _x && !('ace_frag' in configName _x)", true] apply {configName _x}; +}; -{ - private _ammo = toLowerANSI getText (_x >> "ammo"); - if (_ammo != "" && {!(_ammo in _processedCfgAmmos)}) then { +private _processedCfgAmmos = []; +private _printCount = 0; +{ // Begin forEach to check each ammo type + private _ammo = _x; + if (_ammo isNotEqualTo "") then { _processedCfgAmmos pushBack _ammo; - //Ignore mines/bombs - if (_ammo isKindOf "TimeBombCore") exitWith {}; - - _ammoConfig = configFile >> "CfgAmmo" >> _ammo; - - //Read configs and test if it would actually cause a frag, using same logic as FUNC(pfhRound) - private _skip = getNumber (_ammoConfig >> QGVAR(skip)); - private _explosive = getNumber (_ammoConfig >> "explosive"); - private _indirectRange = getNumber (_ammoConfig >> "indirectHitRange"); - private _force = getNumber (_ammoConfig >> QGVAR(force)); - private _fragPower = getNumber (_ammoConfig >> "indirecthit") * (sqrt ((getNumber (_ammoConfig >> "indirectHitRange")))); - - private _shouldAdd = (_skip == 0) && {(_force == 1) || {_explosive > 0.5 && {_indirectRange >= 4.5} && {_fragPower >= 35}}}; + private _ammoConfig = configFile >> "CfgAmmo" >> _ammo; + _ammo call FUNC(shouldFrag) params ["_shouldFrag"]; - if (_shouldAdd) then { - if (_debugForce && {((getNumber(_ammoConfig >> "hit")) < 5) || {_fragPower < 10}}) then { - diag_log text format ["Ammo [%1] from Mag [%2] - Weak but will still frag!", _ammo, configName _x]; - diag_log text format [" - _force=%1,_fragPower=%2", _force, _fragPower]; - }; - - private _warn = false; + if (_shouldFrag || _logAll) then { - _fragTypes = getArray (_ammoConfig >> QGVAR(CLASSES)); - if (_fragTypes isEqualTo []) then {_warn = true;}; - _c = getNumber(_ammoConfig >> QGVAR(CHARGE)); - if (_c == 0) then {_warn = true;}; - _m = getNumber(_ammoConfig >> QGVAR(METAL)); - if (_m == 0) then {_warn = true;}; - _k = getNumber(_ammoConfig >> QGVAR(GURNEY_K)); - if (_k == 0) then {_warn = true;}; - _gC = getNumber(_ammoConfig >> QGVAR(GURNEY_C)); - if (_gC == 0) then {_warn = true;}; + private _print = false; + private _skip = getNumber (_ammoConfig >> QGVAR(skip)); + private _fragTypes = getArray (_ammoConfig >> QGVAR(classes)); + if (_fragTypes isEqualTo []) then {_print = true;}; + private _c = getNumber (_ammoConfig >> QGVAR(charge)); + if (_c == 0) then {_print = true;}; + private _m = getNumber (_ammoConfig >> QGVAR(metal)); + if (_m == 0) then {_print = true;}; + private _k = getNumber (_ammoConfig >> QGVAR(gurney_k)); + if (_k == 0) then {_print = true;}; + private _gC = getNumber (_ammoConfig >> QGVAR(gurney_c)); + if (_gC == 0) then {_print = true;}; + private _fragCount = getNumber (_ammoConfig >> QGVAR(fragCount)); + if (_fragCount == 0) then {_fragCount = 200; _print = true;}; - if (_debugMissing && {_warn}) then { - diag_log text format ["Ammo [%1] from Mag [%2] MISSING frag configs:", _ammo, configName _x]; - diag_log text format [" - _c=%1,_m=%2,_k=%3,_gC=%4,_fragTypes=%5", _c, _m, _k, _gC, _fragTypes]; - }; - } else { - if ((_fragPower > _debugSkippedFragPower) && {isArray (_ammoConfig >> QGVAR(CLASSES))}) then { - diag_log text format ["Ammo [%1] from Mag [%2] has frag configs but will NOT frag:", _ammo, configName _x]; - diag_log text format ["- skip=%1,explosive=%2,indirectHitRange=%3,force=%4,fragPower=%5", _skip, _explosive, _indirectRange, _force, _fragPower]; + if (!_printOnlyIncomplete || {_print && _skip != 0}) then { + INC(_printCount); + if (_csvFormat) then { + diag_log text format ["%7,%1,%2,%3,%4,%5,%6,%9,%8", _c, _m, _k, _gC, _skip, _fragCount, _ammo, [_ammoConfig, true] call BIS_fnc_returnParents, _shouldFrag]; + } else { + diag_log text format ["Ammo [%1] MISSING frag configs:", _ammo]; + diag_log text format ["_c=%1,_m=%2,_k=%3,_gC=%4,_skip=%5,_fragCount=%6,_fragTypes=%7", _c, _m, _k, _gC, _skip, _fragCount, _fragTypes]; + }; }; }; }; -} forEach _allMagsConfigs; +} forEach _allAmmoConfigs; -diag_log text format ["~~~~~~~~~~~~~End [%1-%2]~~~~~~~~~~~~~", count _allMagsConfigs, count _processedCfgAmmos]; +diag_log text format ["~~~~~~~~~~~~~~End [%1-%2]~~~~~~~~~~~~~~", count _allAmmoConfigs, count _processedCfgAmmos]; +diag_log text format ["~~~~~~~~~~~~~~Printed: %1~~~~~~~~~~~", _printCount]; diff --git a/addons/frag/functions/fnc_dev_drawTrace.sqf b/addons/frag/functions/fnc_dev_drawTrace.sqf new file mode 100644 index 00000000000..525149b1c88 --- /dev/null +++ b/addons/frag/functions/fnc_dev_drawTrace.sqf @@ -0,0 +1,45 @@ +#include "..\script_component.hpp" +/* + * Author: Lambda.Tiger + * This function draws all development traces and is intended to be called on each frame. + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * call ace_frag_fnc_dev_drawTrace + * + * Public: No + */ + +#define HITBOX_DRAW_PATH [[3, 2, 1, 5, 6, 7, 3, 0, 4, 5], [0, 1], [2, 6], [7, 4]] + +if (!GVAR(debugOptions)) exitWith {}; + +{ + _y params ["_posArray", "_color"]; + if (count _posArray > 1) then { + for "_j" from 1 to count _posArray - 1 do { + drawLine3D [_posArray#(_j-1), _posArray#_j, _color]; + }; + }; +} forEach GVAR(dev_trackLines); + +if (GVAR(drawHitBox)) then { + { + _y params ["_object", "_boxPoints", "_color"]; + if (!alive _object) then { + GVAR(dev_hitBoxes) deleteAt _x; + continue; + }; + + { + for "_i" from 1 to count _x -1 do { + drawLine3D [_object modelToWorld (_boxPoints#(_x#_i)), _object modelToWorld (_boxPoints#(_x#(_i-1))), _color]; + }; + } forEach HITBOX_DRAW_PATH; + } forEach GVAR(dev_hitBoxes); +}; diff --git a/addons/frag/functions/fnc_dev_drawTraces.sqf b/addons/frag/functions/fnc_dev_drawTraces.sqf deleted file mode 100644 index 7fcca3c48fe..00000000000 --- a/addons/frag/functions/fnc_dev_drawTraces.sqf +++ /dev/null @@ -1,37 +0,0 @@ -#include "..\script_component.hpp" -/* - * Author: ACE-Team - * - * - * Arguments: - * None - * - * Return Value: - * None - * - * Example: - * call ace_frag_fnc_dev_drawTraces - * - * Public: No - */ - -{ - _x params ["", "", "", "", "_positions", "_color"]; - private _index = 0; - private _max = count _positions; - // private _lastSpd = []; - private _lastPos = []; - while {_index < _max} do { - _data1 = _positions select _index; - _data2 = _positions select ([_index + ACE_TRACE_DRAW_INC, _max - 1] select (_index + ACE_TRACE_DRAW_INC >= _max)); - - _pos1 = _data1 select 0; - _pos2 = _data2 select 0; - ADD(_index,ACE_TRACE_DRAW_INC); - - drawLine3D [_pos1, _pos2, _color]; - _lastPos = _pos2; - // _lastSpd = _data1 select 1; - }; - // drawIcon3D ["", [1,0,0,1], _lastPos, 0, 0, 0, format ["%1m/s", _lastSpd], 1, 0.05, "RobotoCondensed"]; -} forEach GVAR(traces); diff --git a/addons/frag/functions/fnc_dev_fragCalcDump.sqf b/addons/frag/functions/fnc_dev_fragCalcDump.sqf new file mode 100644 index 00000000000..3812273b3cf --- /dev/null +++ b/addons/frag/functions/fnc_dev_fragCalcDump.sqf @@ -0,0 +1,58 @@ +#include "..\script_component.hpp" +/* + * Author: Lambda.Tiger, based on fnc_dev_debugAmmo by "ACE-Team" + * Dumps all ammo types to see if there's any reason to spawn fragments given hit power and distance. + * Good for grasping the values used in shouldFrag to cull non-fragmenting rounds. + * + * Arguments: + * 0: Display rounds that will never frag (default: false) + * + * Return Value: + * None + * + * Example: + * false call ace_frag_fnc_dev_fragCalcDump + * + * Public: No + */ + +params [["_logAll", false, [false]]]; + +private _allAmmoConfigs = configProperties [configFile >> "CfgAmmo", "isClass _x && !('ace_frag' in configName _x)", true]; +private _processedCfgAmmos = []; + +private _numberPrinted = 0; + +diag_log text "//****************** fragCalcDump Beg ******************//"; +{ // Begin _allAmmoConfigs forEach + private _ammo = configName _x; + + if (_ammo isEqualTo "" || {_ammo in _processedCfgAmmos}) then { + continue; + }; + + _ammo call FUNC(shouldFrag) params ["_shouldFrag"]; + if (_shouldFrag || _logAll) then { + private _fragInfo = _ammo call FUNC(getFragInfo); + _fragInfo params ["_fragRange", "_fragMaxVelocity", "", "_modifiedFragCount"]; + private _fragCount = 4 * pi * _modifiedFragCount; + private _ammoConfig = configFile >> "CfgAmmo" >> _ammo; + private _indirectHitRange = getNumber (_ammoConfig >> "indirectHitRange"); + private _indirectHit = getNumber (_ammoConfig >> "indirectHit"); + private _fragPowerSpeedRange = [0.5, 1] vectorMultiply _fragMaxVelocity; + + diag_log text format ["Ammo type: %1 | Should frag: %2", _ammo, _shouldFrag]; + diag_log text format [" Indirect hit range: %1", _indirectHitRange]; + diag_log text format [" Indirect hit: %1", _indirectHit]; + diag_log text format [" Frag sqrtPower: %1", _fragPowerSqrt]; + diag_log text format [" Frag range: %1", _fragRange]; + diag_log text format [" Frag speed range: %1", _fragPowerSpeedRange]; + diag_log text format [" Number frags: %1", _fragCount]; + INC(_numberPrinted); + }; + + _processedCfgAmmos pushBack _ammo; +} forEach _allAmmoConfigs; + +diag_log text "//****************** fragCalcDump End ******************//"; +diag_log text format ["//********************** printed %1 *********************//", _numberPrinted]; diff --git a/addons/frag/functions/fnc_dev_sphereDraw.sqf b/addons/frag/functions/fnc_dev_sphereDraw.sqf new file mode 100644 index 00000000000..270514f0a1a --- /dev/null +++ b/addons/frag/functions/fnc_dev_sphereDraw.sqf @@ -0,0 +1,40 @@ +#include "..\script_component.hpp" +/* + * Author: Lambda.Tiger + * Add a colored sphere at a specified point. + * + * Arguments: + * 0: Position (posASL) to add sphere + * 1: Color of sphere (default: "blue") + * + * Return Value: + * The created sphere object + * + * Example: + * [getPosASL player, "red"] call ace_frag_fnc_dev_sphereDraw + * + * Public: No + */ + +params ["_posASL", ["_color", "blue"]]; + +if (!isServer) exitWith {}; + +if (_color select [0,1] != "(") then { + _color = switch (toLowerANSI _color) do { + case "blue": {"(0,0,0.8,0.5)"}; + case "black": {"(1,1,1,0.5)"}; + case "white": {"(0,0,0,0.5)"}; + case "red": {"(0.8,0,0,0.5)"}; + case "green": {"(0,0.8,0,0.5)"}; + case "yellow": {"(0.8,0.8,0,0.5)"}; + case "orange": {"(0.8,0.518,0,0.5)"}; + default {"(0.8,0.8,0,0.5)"}; + }; +}; +private _colorString = "#(argb,8,8,3)color" + _color; + +private _sphere = createVehicle ["Sign_Sphere10cm_F", ASLtoATL _posASL, [], 0, "CAN_COLLIDE"]; +_sphere setObjectTextureGlobal [0, _colorString]; +GVAR(dev_eventSpheres) pushBack _sphere; +_sphere diff --git a/addons/frag/functions/fnc_dev_startTracing.sqf b/addons/frag/functions/fnc_dev_startTracing.sqf deleted file mode 100644 index 897dde3011e..00000000000 --- a/addons/frag/functions/fnc_dev_startTracing.sqf +++ /dev/null @@ -1,23 +0,0 @@ -#include "..\script_component.hpp" -/* - * Author: ACE-Team - * - * - * Arguments: - * None - * - * Return Value: - * None - * - * Example: - * call ace_frag_fnc_dev_startTracing - * - * Public: No - */ - -if (GVAR(tracesStarted)) exitWith {}; - -INFO("Starting Trace Drawing"); - -GVAR(tracesStarted) = true; -GVAR(traceID) = [LINKFUNC(dev_drawTraces), 0, []] call CBA_fnc_addPerFrameHandler; diff --git a/addons/frag/functions/fnc_dev_stopTracing.sqf b/addons/frag/functions/fnc_dev_stopTracing.sqf deleted file mode 100644 index 949d3cd55d6..00000000000 --- a/addons/frag/functions/fnc_dev_stopTracing.sqf +++ /dev/null @@ -1,23 +0,0 @@ -#include "..\script_component.hpp" -/* - * Author: ACE-Team - * Dev things - * - * Arguments: - * None - * - * Return Value: - * None - * - * Example: - * None - * - * Public: No - */ - -if (!GVAR(tracesStarted)) exitWith {}; - -INFO("Ending Trace Drawing"); - -GVAR(tracesStarted) = false; -[GVAR(traceID)] call CBA_fnc_removePerFrameHandler; diff --git a/addons/frag/functions/fnc_dev_trackHitBox.sqf b/addons/frag/functions/fnc_dev_trackHitBox.sqf new file mode 100644 index 00000000000..327e45cf0d3 --- /dev/null +++ b/addons/frag/functions/fnc_dev_trackHitBox.sqf @@ -0,0 +1,75 @@ +#include "..\script_component.hpp" +/* + * Author: Lambda.Tiger + * Add a hit box outline to an object. + * + * Arguments: + * 0: Object that should have it's hit box drawn (default: objNull) + * 1: Add sphere at object origin (default: true) + * + * Return Value: + * None + * + * Example: + * player call ace_frag_fnc_dev_trackHitBox + * + * Public: No + */ + +params [ + ["_object", objNull], + ["_addSphere", true] +]; +TRACE_2("Adding hitbox",_object,_addSphere); + +if (isNull _object) exitWith {}; + +// Grab the right hitBox +private _boundingBox = []; +if (_object isKindOf "CAManBase") then { + if (isNull objectParent _object) then { + _boundingBox = 0 boundingBox _object; + } else { + _boundingBox = boundingBoxReal [_object, "Geometry"]; + }; +} else { + _boundingBox = boundingBoxReal [_object, "FireGeometry"]; +}; +_boundingBox params ["_lowerPoint", "_upperPoint"]; + +// adjust with stance +switch (stance _object) do { + case "STAND": {_upperPoint set [2, 1.9];}; + case "CROUCH": {_upperPoint set [2, 1.3];}; + case "PRONE": {_upperPoint set [2, 0.8];}; +}; +private _centerPoint = ASLToAGL getPosASL _object; + +if (GVAR(dbgSphere) && _addSphere && {isNull objectParent _object}) then { + private _centerSphere = [getPosASL _object, "yellow"] call FUNC(dev_sphereDraw); + _centerSphere attachTo [_object, _object worldToModel _centerPoint]; +}; + +// create an optimized outline +_upperPoint params ["_x1","_y1","_z1"]; +_lowerPoint params ["_x2","_y2","_z2"]; +private _p1 = _upperPoint; +private _p7 = _lowerPoint; +private _points = [ + _upperPoint, + [_x1, _y2, _z1], + [_x2, _y2, _z1], + [_x2, _y1, _z1], + [_x1, _y1, _z2], + [_x1, _y2, _z2], + _lowerPoint, + [_x2, _y1, _z2] +]; + +private _color = switch (side _object) do { + case east: {[0.8, 0, 0, 1]}; + case resistance: {[0, 0.8, 0, 1]}; + default {[0, 0, 0.8, 1]}; +}; + +GVAR(dev_hitBoxes) set [getObjectID _object, [_object, _points, _color]]; diff --git a/addons/frag/functions/fnc_dev_trackObj.sqf b/addons/frag/functions/fnc_dev_trackObj.sqf new file mode 100644 index 00000000000..dca9cc49eaa --- /dev/null +++ b/addons/frag/functions/fnc_dev_trackObj.sqf @@ -0,0 +1,100 @@ +#include "..\script_component.hpp" +/* + * Author: Lambda.Tiger + * This function adds an object to have its course tracked (every frame). + * + * Arguments: + * 0: Object to draw track (default: objNull) + * 1: Color of trace (default: "blue") + * 2: Whether the object is a projectile or whether to add projectile EHs (default: false) + * + * Return Value: + * None + * + * Example: + * player call ace_frag_fnc_dev_trackObj + * + * Public: No + */ + +params [ + ["_object", objNull], + ["_color", "blue"], + ["_isProj", false] +]; +TRACE_3("devDraw",_object,_color,_isProj); + +// pick color and add it to the array +private _colorArray = switch (toLowerANSI _color) do { + case "purple": {[0.8, 0, 0.8, 1]}; + case "blue": {[0, 0, 0.8, 1]}; + case "green": {[0, 0.8, 0, 1]}; + case "orange": {[0.8, 0.518, 0, 1]}; + case "yellow": {[0.8, 0.8, 0, 1]}; + case "red": {[0.8, 0, 0, 1]}; + case "black": {[1, 1, 1, 1]}; + case "white": {[0, 0, 0, 1]}; + default {[0, 0.8, 0.8, 1]}; +}; +GVAR(dev_trackLines) set [getObjectID _object, [[getPosATL _object], _colorArray]]; + +// event handler to track round and cleanup when round is "dead" +[ + { + if (isGamePaused || accTime == 0) exitWith {}; + params ["_object", "_handle"]; + + if (!alive _object) exitWith { + _handle call CBA_fnc_removePerFrameHandler; + }; + + private _objectArray = GVAR(dev_trackLines) get (getObjectID _object); + + if (isNil "_objectArray") exitWith { + _handle call CBA_fnc_removePerFrameHandler; + }; + + (_objectArray#0) pushBack getPosATL _object; + }, + 0, + _object +] call CBA_fnc_addPerFrameHandler; + +// Projectile event handlers that add spheres and points for more accurate round tracking +if (!_isProj) exitWith {}; + +_object addEventHandler [ + "HitPart", + { + params ["_projectile", "", "", "_posASL"]; + private _posArr = (GVAR(dev_trackLines) get (getObjectID _projectile))#0; + _posArr pushBack ASLtoATL _posASL; + if (GVAR(dbgSphere)) then { + [_posASL, "green"] call FUNC(dev_sphereDraw); + }; + } +]; + +_object addEventHandler [ + "Explode", + { + params ["_projectile", "_posASL"]; + private _posArr = (GVAR(dev_trackLines) get (getObjectID _projectile))#0; + _posArr pushBack ASLtoATL _posASL; + if (GVAR(dbgSphere)) then { + [_posASL, "red"] call FUNC(dev_sphereDraw); + }; + } +]; + +_object addEventHandler [ + "Deflected", + { + params ["_projectile", "_posASL"]; + private _posArr = (GVAR(dev_trackLines) get (getObjectID _projectile))#0; + _posArr pushBack ASLtoATL _posASL; + if (GVAR(dbgSphere)) then { + [_posASL, "blue"] call FUNC(dev_sphereDraw); + }; + } +]; diff --git a/addons/frag/functions/fnc_dev_trackTrace.sqf b/addons/frag/functions/fnc_dev_trackTrace.sqf deleted file mode 100644 index 6c010bdb631..00000000000 --- a/addons/frag/functions/fnc_dev_trackTrace.sqf +++ /dev/null @@ -1,27 +0,0 @@ -#include "..\script_component.hpp" -/* - * Author: ACE-Team - * Dev things - * - * Arguments: - * None - * - * Return Value: - * None - * - * Example: - * call ace_frag_fnc_dev_trackTrace - * - * Public: No - */ - -params ["_args", "_pfhID"]; -_args params ["_tracerObj", "_index"]; - -if (alive _tracerObj && {GVAR(traces) isNotEqualTo []}) then { - private _data = GVAR(traces) select _index; - private _positions = _data select 4; - _positions pushBack [getPos _tracerObj, vectorMagnitude (velocity _tracerObj)]; -} else { - [_pfhID] call CBA_fnc_removePerFrameHandler; -}; diff --git a/addons/frag/functions/fnc_doFrag.sqf b/addons/frag/functions/fnc_doFrag.sqf new file mode 100644 index 00000000000..4898c751134 --- /dev/null +++ b/addons/frag/functions/fnc_doFrag.sqf @@ -0,0 +1,65 @@ +#include "..\script_component.hpp" +/* + * Author: Jaynus, NouberNou, Lambda.Tiger + * This function handles creating both random and targeted fragments as well as handling some of the performance optimizations. + * + * Arguments: + * 0: Position (posASL) of projectile + * 1: Velocity of projectile + * 2: Projectile CfgAmmo classname + * 3: getShotParents of projectile at EH + * + * Return Value: + * None + * + * Example: + * [getPosASL _projectile, velocity _projectile, typeOf _projectile, getShotParents _projectile] call ace_frag_fnc_doFrag + * + * Public: No + */ + +#define ACE_FRAG_MIN_FRAG_BUDGET_FOR_RANDOM 3 +#define ACE_FRAG_NEGATIVE_AGL_OFFSET 0.1 + +params ["_posASL", "_projectileVelocity", "_ammo", "_shotParents"]; +TRACE_4("doFrag",_posASL,_projectileVelocity,_ammo,_shotParents); + +// Don't let a single object cause all fragmentation events +_shotParents params ["_shotParentVehicle"]; +if (_shotParentVehicle getVariable [QGVAR(obj_nextFragTime), -1] > CBA_missionTime) exitWith { + TRACE_1("vehicleTimeExit",_shotParentVehicle); +}; + +// Check normal round timeout and adjust _max frags +private _timeSinceLastFrag = CBA_missionTime - GVAR(lastFragTime); +if (_timeSinceLastFrag < ACE_FRAG_HOLDOFF || {_posASL isEqualTo [0, 0, 0]} || {_ammo isEqualTo ""}) exitWith { + TRACE_3("timeExit",_timeSinceLastFrag,CBA_missionTime,GVAR(lastFragTime)); +}; +TRACE_3("willFrag",_timeSinceLastFrag,CBA_missionTime,_maxFragCount); +_shotParentVehicle setVariable [QGVAR(obj_nextFragTime), CBA_missionTime + ACE_FRAG_HOLDOFF_VEHICLE]; +private _maxFragCount = round linearConversion [ACE_FRAG_COUNT_MIN_TIME, ACE_FRAG_COUNT_MAX_TIME, _timeSinceLastFrag, ACE_FRAG_COUNT_MIN, ACE_FRAG_COUNT_MAX, true]; + +_ammo call FUNC(getFragInfo) params ["_fragRange", "_fragVelocity", "_fragTypes", "_modFragCount"]; +// For low frag rounds limit the # of frags created +if (_modFragCount < ACE_FRAG_LOW_FRAG_MOD_COUNT) then { + _maxFragCount = _modFragCount * ACE_FRAG_LOW_FRAG_COEFF; + GVAR(lastFragTime) = CBA_missionTime - ACE_FRAG_LOW_FRAG_HOLDOFF_REDUCTION; +} else { + GVAR(lastFragTime) = CBA_missionTime; +}; + +// Double check if round is below, or too close to terrain for +private _posAGL = ASLToAGL _posASL; +if (_posAGL#2 < ACE_FRAG_NEGATIVE_AGL_OFFSET) then { + TRACE_1("below 0 AGL",_posAGL); + _posAGL set [2, ACE_FRAG_NEGATIVE_AGL_OFFSET]; +}; + +TRACE_3("doFrag choices",_maxFragCount,_fragRange,GVAR(fragSimComplexity)); +if (GVAR(fragSimComplexity) != 1 && _fragRange > 3) then { + _maxFragCount = _maxFragCount - ([_posAGL, _fragVelocity, _fragRange, _maxFragCount, _fragTypes, _modFragCount, _shotParents] call FUNC(doFragTargeted)); +}; + +if (GVAR(fragSimComplexity) > 0 && _maxFragCount >= ACE_FRAG_MIN_FRAG_BUDGET_FOR_RANDOM) then { + [_posAGL, _fragVelocity, _projectileVelocity, _fragTypes, _maxFragCount, _shotParents] call FUNC(doFragRandom); +}; diff --git a/addons/frag/functions/fnc_doFragRandom.sqf b/addons/frag/functions/fnc_doFragRandom.sqf new file mode 100644 index 00000000000..c0f2c355af7 --- /dev/null +++ b/addons/frag/functions/fnc_doFragRandom.sqf @@ -0,0 +1,62 @@ +#include "..\script_component.hpp" +/* + * Author: Jaynus, NouberNou, Lambda.Tiger + * This function creates fragments randomly spreading out from an explosion. + * This function will spawn 5, 10, or 15 fragments. + * + * Arguments: + * 0: Position (posAGL) of fragmenting projectile + * 1: Initial fragment velocity from Gurney equation + * 2: Velocity of the fragmenting projectile + * 3: Type of fragments to generate + * 4: Remaining fragment budget + * 5: Shot parents + * + * Return Value: + * None + * + * Example: + * [ASLtoAGL (getPosASL _projectile), 800, 50, 50, [], 1, [player, player]] call ace_frag_fnc_doFragRandom + * + * Public: No + */ + +params ["_posAGL", "_fragVelocity", "_projectileVelocity", "_fragType", "_maxFragCount", "_shotParents"]; +TRACE_6("doFragRandom",_posAGL,_fragVelocity,_projectileVelocity,_fragType,_maxFragCount,_shotParents); + +// See CfgAmmoFragSpawner for different frag types +private _heightAGL = _posAGL#2; +private _hMode = switch (true) do { + case (_heightAGL > 10): {"_top"}; + case (_heightAGL > 4): {"_high"}; + default {"_mid"}; +}; + +private _type = [QGVAR(random_small_), QGVAR(random_tiny_)] select (_fragType isNotEqualTo [] && {"ace_frag_tiny" == (_fragType#0)}); + +_maxFragCount = switch (true) do { + case (_maxFragCount <= 5): {"5"}; + case (_maxFragCount <= 10): {"10"}; + default {"15"}; +}; + +// Spawn the fragment spawner +private _fragSpawner = createVehicle [_type + _maxFragCount + _hMode, _posAGL, [], 0, "CAN_COLLIDE"]; +private _randDir = random 360; +_fragSpawner setVectorDirandUp [[0,0,-1], [cos _randDir, sin _randDir, 0]]; +_fragSpawner setVelocity (_projectileVelocity vectorAdd [0, 0, -_fragVelocity]); +_fragSpawner setShotParents _shotParents; + +TRACE_2("spawnedRandomFragmenter",typeOf _fragSpawner,getObjectID _fragSpawner); +#ifdef DEBUG_MODE_DRAW +_fragSpawner addEventHandler [ + "SubmunitionCreated", + { + params ["","_subProj"]; + [_subProj, "green", true] call FUNC(dev_trackObj); + } +]; +if (GVAR(dbgSphere)) then { + [AGLtoASL _posAGL] call FUNC(dev_sphereDraw); +}; +#endif diff --git a/addons/frag/functions/fnc_doFragTargeted.sqf b/addons/frag/functions/fnc_doFragTargeted.sqf new file mode 100644 index 00000000000..83b4cfbe2a0 --- /dev/null +++ b/addons/frag/functions/fnc_doFragTargeted.sqf @@ -0,0 +1,184 @@ +#include "..\script_component.hpp" +/* + * Author: Jaynus, NouberNou, Lambda.Tiger + * This function creates fragments targeted at specific entities, up to _maxFrags. + * + * Arguments: + * 0: Position of fragmenting projectile AGL + * 1: Velocity of the fragmenting projectile + * 2: Maximum range of fragments to calculate + * 3: Maximum number of fragments to produce + * 4: Types of fragments + * 5: A modified parameter used to calculate whether a fragment hits + * 6: Shot parent + * + * Return Value: + * Number of fragments created + * + * Example: + * [ASLtoAGL (getPosASL _projectile), velocity _projectile, 50, 50, [], 1, [player, player]] call ace_frag_fnc_doFragTargeted + * + * Public: No + */ + +#define ACE_FRAG_DEFAULT_HEIGHT 0.5 +#define ACE_FRAG_DEFAULT_CROSS_AREA 0.75 +#define ACE_FRAG_MIN_TARGET_AREA 0.5 + +params [ "_posAGL", "_fragVelocity", "_fragRange", "_maxFrags", "_fragTypes", "_modFragCount", "_shotParents"]; +TRACE_5("fnc_doFragTargeted",_posAGL,_fragRange,_maxFrags,_fragTypes,_modFragCount); + +if (_fragTypes isEqualTo []) then { + _fragTypes = [ + QGVAR(tiny), QGVAR(tiny), QGVAR(tiny), + QGVAR(tiny_HD), QGVAR(tiny_HD), QGVAR(tiny_HD), + QGVAR(small), QGVAR(small), QGVAR(small), QGVAR(small), + QGVAR(small_HD), QGVAR(small_HD), QGVAR(small_HD), QGVAR(small_HD), + QGVAR(medium_HD), QGVAR(medium_HD), QGVAR(medium_HD), QGVAR(medium_HD), QGVAR(medium_HD) + ]; +}; + +// Post 2.18 change - uncomment line 41, and remove lines 43, 50-55, 64-66 +// private _targets = [_posAGL, _fragRange, _fragRange, 0, false, _fragRange] nearEntities [["Car", "Motorcycle", "Tank", "StaticWeapon", "CAManBase", "Air", "Ship"], false, true, true]; +private _objects = _posAGL nearEntities [["Car", "Motorcycle", "Tank", "StaticWeapon", "CAManBase", "Air", "Ship"], _fragRange]; +if (_objects isEqualTo []) exitWith { + TRACE_2("No nearby targets",_posAGL,_fragRange); + 0 +}; + +// grab crews and add them in so that targets stay approx. sorted by distance +private _targets = []; +{ + private _crew = crew _x; + _crew pushBackUnique _x; + _targets append _crew; +} forEach _objects; + +TRACE_3("Targets found",_posAGL,_fragRange,count _targets); + +// limit number of fragments per direction (2D) to _fragsPerFragArc using _fragArcs +private _fragArcs = createHashMap; +private _fragsPerFragArc = _modFragCount * ACE_FRAG_FRAGS_PER_ARC_CONSTANT; +private _totalFragCount = 0; +{ // Begin of forEach iterating on _targets + // Ignore dead units, curators and spectators + if (!alive _x || {getNumber ((configOf _x) >> "isPlayableLogic") == 1}) then { + TRACE_1("dead or logic",_x); + continue; + }; + private _target = _x; + + #ifdef DEBUG_MODE_DRAW + [_target, false] call FUNC(dev_trackHitBox); + #endif + + // Estimate volume and height of target + private _height = ACE_FRAG_DEFAULT_HEIGHT; + private _crossSectionArea = ACE_FRAG_DEFAULT_CROSS_AREA; + private _isPerson = _target isKindOf "CAManBase"; + if (_isPerson) then { + switch (stance _target) do { + case "STAND": { + _height = 1.9; + _crossSectionArea = 1.5; + }; + case "CROUCH": { + _height = 1.2; + _crossSectionArea = 1; + }; + }; + } else { + private _boxParams = boundingBoxReal [_target, "FireGeometry"]; + _boxParams params ["_pointA", "_pointB"]; + private _dims = _pointB vectorDiff _pointA; + if (_dims#0 * _dims#1 * _dims#2 <= ACE_FRAG_MIN_TARGET_AREA) then { + continue; + }; + _crossSectionArea = _dims#1 * _dims#2; + _height = _dims#2; + }; + + private _distance = _target distance _posAGL; + + // calculate chance to be hit by a fragment + private _fragChance = _crossSectionArea * _modFragCount / _distance^2; + private _fragCount = if (_fragChance > 1) then { + 3 min (floor _fragChance); + } else { + parseNumber (GVAR(atLeastOne) || {random 1 < _fragChance}); + }; + if (_fragCount == 0) then { + TRACE_2("no fragments",_fragChance,_fragCount); + continue; + }; + + // handle limiting fragments per degree arc + private _dir = floor (_posAGL getDir _target); + private _fragPerArc = _fragArcs getOrDefault [_dir, 0]; + if (_fragPerArc > _fragsPerFragArc) then { + continue; + } else { + _fragArcs set [_dir, _fragPerArc + _fragCount]; + }; + + // Approximate offset to hit including speed & gravity + private _locFragVel = _fragVelocity * (1 - random 0.5); + private _timeOfFlight = _distance / _locFragVel; + + // target pos for fragment to hit + private _targetPos = (velocity _target vectorMultiply _timeOfFlight) vectorAdd [0, 0, ACE_FRAG_HALF_GRAVITY_APPROX * _timeOfFlight ^ 2]; + _targetPos = if (_isPerson) then { + private _hitPoint = selectRandom ACE_FRAG_HITPOINTS; + private _hitPointPos = _target selectionPosition [_hitPoint, "HitPoints", "AveragePoint"]; + _target modelToWorld _hitPointPos vectorAdd _targetPos; + } else { + ASLtoAGL (_targetPos vectorAdd getPosASL _target vectorAdd [ + -0.5 + random 1, + -0.5 + random 1, + (0.1 + random 0.4) * _height + ]); + }; + + // select a fragment / submunition frag spawner + private _fragSpawner = selectRandom _fragTypes; + if (_fragCount > 1) then { + _fragSpawner = _fragSpawner + "_spawner_" + str _fragCount + (switch (true) do { + case (_distance < 10): {"_short"}; + case (_distance < 20): {"_mid"}; + default {"_far"}; + }); + }; + TRACE_4("fragments",_fragSpawner,_fragChance,_distance,_locFragVel); + + // Create fragment + private _vectorDir = _posAGL vectorFromTo _targetPos; + private _fragObj = createVehicle [_fragSpawner, _posAGL, [], 0, "CAN_COLLIDE"]; + _fragObj setVectorDirAndUp [_vectorDir, _vectorDir vectorAdd [0, -1, 0]] ; + _fragObj setVelocity (_vectorDir vectorMultiply _locFragVel); + _fragObj setShotParents _shotParents; + #ifdef DEBUG_MODE_DRAW + [_fragObj, "purple", true] call FUNC(dev_trackObj); + _fragObj addEventHandler [ + "SubmunitionCreated", + { + params ["","_subProj"]; + [_subProj, "purple", true] call FUNC(dev_trackObj); + } + ]; + if (GVAR(dbgSphere)) then { + [AGLtoASL _targetPos, "orange"] call FUNC(dev_sphereDraw); + }; + #endif + + _totalFragCount = _totalFragCount + _fragCount; + if (_totalFragCount >= _maxFrags) then { + TRACE_2("maxFrags",_totalFragCount,_maxFrags); + break; + }; +} forEach _targets; + +#ifdef DEBUG_MODE_FULL +systemChat ("targeted frag count: " + str _totalFragCount); +TRACE_1("targeted frag count",_totalFragCount); +#endif +_totalFragCount diff --git a/addons/frag/functions/fnc_doSpall.sqf b/addons/frag/functions/fnc_doSpall.sqf deleted file mode 100644 index b206c701b21..00000000000 --- a/addons/frag/functions/fnc_doSpall.sqf +++ /dev/null @@ -1,139 +0,0 @@ -#include "..\script_component.hpp" -/* - * Author: ACE-Team - * Dev things - * - * Arguments: - * None - * - * Return Value: - * None - * - * Example: - * call ace_frag_fnc_doSpall - * - * Public: No - */ - -#define WEIGHTED_SIZE [QGVAR(spall_small), 4, QGVAR(spall_medium), 3, QGVAR(spall_large), 2, QGVAR(spall_huge), 1] - -params ["_hitData", "_hitPartDataIndex"]; -private _initialData = GVAR(spallHPData) select (_hitData select 0); -_initialData params ["_hpId", "_object", "_roundType", "_round", "_curPos", "_velocity"]; - -private _hpData = (_hitData select 1) select _hitPartDataIndex; -private _objectHit = _hpData param [0, objNull]; -TRACE_1("",_objectHit); -if ((isNil "_objectHit") || {isNull _objectHit}) exitWith {WARNING_1("Problem with hitPart data - bad object [%1]",_objectHit);}; -_objectHit removeEventHandler ["HitPart", _hpId]; - -private _caliber = getNumber (configFile >> "CfgAmmo" >> _roundType >> "caliber"); -private _explosive = getNumber (configFile >> "CfgAmmo" >> _roundType >> "explosive"); -private _idh = getNumber (configFile >> "CfgAmmo" >> _roundType >> "indirectHitRange"); - -if !(_caliber >= 2.5 || {(_explosive > 0 && {_idh >= 1})}) exitWith {}; -// ACE_player sideChat format ["BBBB"]; -private _exit = false; -private _vm = 1; - -private _oldVelocity = vectorMagnitude _velocity; -private _curVelocity = vectorMagnitude (velocity _round); - -if (alive _round) then { - private _diff = _velocity vectorDiff (velocity _round); - private _polar = _diff call CBA_fnc_vect2polar; - // ACE_player sideChat format ["polar: %1", _polar]; - if (abs (_polar select 1) > 45 || {abs (_polar select 2) > 45}) then { - if (_caliber < 2.5) then { - // ACE_player sideChat format ["exit!"]; - _exit = true; - } else { - SUB(_vm,_curVelocity / _oldVelocity); - }; - }; -}; -if (_exit) exitWith {}; - -private _unitDir = vectorNormalized _velocity; -private _pos = _hpData select 3; -private _spallPos = []; -if ((isNil "_pos") || {!(_pos isEqualTypeArray [0,0,0])}) exitWith {WARNING_1("Problem with hitPart data - bad pos [%1]",_pos);}; -for "_i" from 0 to 100 do { - private _pos1 = _pos vectorAdd (_unitDir vectorMultiply (0.01 * _i)); - private _pos2 = _pos vectorAdd (_unitDir vectorMultiply (0.01 * (_i + 1))); - // _data = [nil, nil, nil, 1, [[ASLtoATL _pos1, 1], [ASLtoATL _pos2, 1]]]; - // NOU_TRACES pushBack _data; - - if (!lineIntersects [_pos1, _pos2]) exitWith { - // ACE_player sideChat format ["FOUND!"]; - _spallPos = _pos2; - }; -}; -if (_spallPos isEqualTo []) exitWith {}; -private _spallPolar = _velocity call CBA_fnc_vect2polar; - -if (_explosive > 0) then { - // ACE_player sideChat format ["EXPLOSIVE!"]; - private _warn = false; - private _c = getNumber (configFile >> "CfgAmmo" >> _roundType >> QGVAR(CHARGE)); - if (_c == 0) then {_c = 1; _warn = true;}; - private _m = getNumber (configFile >> "CfgAmmo" >> _roundType >> QGVAR(METAL)); - if (_m == 0) then {_m = 2; _warn = true;}; - private _k = getNumber (configFile >> "CfgAmmo" >> _roundType >> QGVAR(GURNEY_K)); - if (_k == 0) then {_k = 1 / 2; _warn = true;}; - private _gC = getNumber (configFile >> "CfgAmmo" >> _roundType >> QGVAR(GURNEY_C)); - if (_gC == 0) then {_gC = 2440; _warn = true;}; - - // if (_warn) then { - // WARNING_1("Ammo class %1 lacks proper explosive properties definitions for frag!",_roundType); //TODO: turn this off when we get closer to release - // }; - - private _fragPower = (((_m / _c) + _k) ^ - (1 / 2)) * _gC; - _spallPolar set [0, _fragPower * 0.66]; -}; - -// diag_log text format ["SPALL POWER: %1", _spallPolar select 0]; -private _spread = 15 + (random 25); -private _spallCount = 5 + (random 10); -TRACE_1("",_spallCount); -for "_i" from 1 to _spallCount do { - private _elev = ((_spallPolar select 2) - _spread) + (random (_spread * 2)); - private _dir = ((_spallPolar select 1) - _spread) + (random (_spread * 2)); - if (abs _elev > 90) then { - ADD(_dir,180); - }; - _dir = _dir % 360; - private _vel = (_spallPolar select 0) * 0.33 * _vm; - _vel = (_vel - (_vel * 0.25)) + (random (_vel * 0.5)); - - private _spallFragVect = [_vel, _dir, _elev] call CBA_fnc_polar2vect; - private _fragment = (selectRandomWeighted WEIGHTED_SIZE) createVehicleLocal [0,0,10000]; - _fragment setPosASL _spallPos; - _fragment setVelocity _spallFragVect; - - #ifdef DRAW_FRAG_INFO - [ACE_player, _fragment, [1, 0.5, 0, 1]] call FUNC(dev_addTrack); - #endif -}; - -_spread = 5 + (random 5); -_spallCount = 3 + (random 5); -for "_i" from 1 to _spallCount do { - private _elev = ((_spallPolar select 2) - _spread) + (random (_spread * 2)); - private _dir = ((_spallPolar select 1) - _spread) + (random (_spread * 2)); - if (abs _elev > 90) then { - ADD(_dir,180); - }; - _dir = _dir % 360; - private _vel = (_spallPolar select 0) * 0.55 * _vm; - _vel = (_vel - (_vel * 0.25)) + (random (_vel * 0.5)); - - private _spallFragVect = [_vel, _dir, _elev] call CBA_fnc_polar2vect; - private _fragment = (selectRandomWeighted WEIGHTED_SIZE) createVehicleLocal [0, 0, 10000]; - _fragment setPosASL _spallPos; - _fragment setVelocity _spallFragVect; - - #ifdef DRAW_FRAG_INFO - [ACE_player, _fragment, [1, 0, 0, 1]] call FUNC(dev_addTrack); - #endif -}; diff --git a/addons/frag/functions/fnc_doSpallLocal.sqf b/addons/frag/functions/fnc_doSpallLocal.sqf new file mode 100644 index 00000000000..17224cb36f7 --- /dev/null +++ b/addons/frag/functions/fnc_doSpallLocal.sqf @@ -0,0 +1,133 @@ +#include "..\script_component.hpp" +/* + * Author: Jaynus, NouberNou, Lambda.Tiger + * This function runs on every client and determines if a spall event happened. + * It is intended to be called a frame after the projectile has triggered a hit event, + * where information such as the object hit, the projectiles velocity and position on impact + * should be kept and passed to this function.If a spall event should happen, this function + * calculates the spalling parameters and then calls a server event to create the desired spall effect. + * + * Arguments: + * 0: The projectile that may be creating spall + * 1: The object the projectile hit + * 2: The 3D position (ASL) of the projectile when it hit the object + * 3: The velocity of the projectile when it hit the object + * 4: The normal of the surface of the object hit + * 5: The class name of the surface, or the bisurf path of the surface hit + * 6: The class name of the projectile that may be spalling + * 7: The spalling projectile's shot parents + * 8: The "up" vector of the projectile when it hit the object + * + * Return Value: + * None + * + * Example: + * [_projectile, _hitObject, _lastPosASLProjectile, _lastVelocityProjectile, _surfaceNorm, "a3\data_f\penetration\armour_plate.bisurf", "Sh_125mm_APFSDS", [0, 0, 1]] call ace_frag_fnc_doSpallLocal + * + * Public: No + */ + +#define GLUE(g1,g2) g1##g2 + +if (CBA_missionTime < GVAR(nextSpallAllowTime)) exitWith { + TRACE_2("time exit",CBA_missionTime,GVAR(nextSpallAllowTime)); +}; + +params ["_projectile", "_objectHit", "_lastPosASL", "_lastVelocity", "_surfaceNorm", "_surfaceType", "_ammo", "_shotParents", "_vectorUp"]; +TRACE_9("doSpall",_projectile,_objectHit,_lastPosASL,_lastVelocity,_surfaceNorm,_surfaceType,_ammo,_shotParents,_vectorUp); + +if (_ammo == "" || {_objectHit isKindOf "CAManBase"}) exitWith { + TRACE_4("invalid round or hit",CBA_missionTime,GVAR(nextSpallAllowTime),_objectHit,_lastPosASL); +}; + +private _material = _surfaceType call FUNC(getMaterialInfo); +if (_material == "ground") exitWith { + TRACE_1("ground",_surfaceType); +}; + +// Find spall speed / fragment info +_ammo call FUNC(getSpallInfo) params ["_caliber", "_explosive", "_indirectHit"]; +private _vel = if (alive _projectile) then { + _explosive = 0; // didn't explode since it's alive a frame later + velocity _projectile +} else { + [0, 0, 0] +}; + +private _speedChange = 0 max (vectorMagnitude _lastVelocity - vectorMagnitude _vel); +/* + * This is all fudge factor since real spalling is too complex for calculation. + * There are two terms. The first is from round impact, taking a quasi scale + * of sqrt(2)/50 * round caliber * srqt(change in speed). The second term is + * explosive * indirect hit, for any explosive contribution + */ +private _spallPower = (ACE_FRAG_SPALL_CALIBER_COEF * _caliber * sqrt _speedChange + _explosive * _indirectHit) * GVAR(spallIntensity); +TRACE_3("found speed",_speedChange,_caliber,_spallPower); + +if (_spallPower < ACE_FRAG_SPALL_POWER_MIN) exitWith { + TRACE_1("lowImpulse",_ammo); +}; + +private _lastVelocityNorm = vectorNormalized _lastVelocity; +private _deltaStep = _lastVelocityNorm vectorMultiply 0.05; + +if (terrainIntersectASL [_lastPosASL vectorAdd _deltaStep, _lastPosASL]) exitWith { + TRACE_2("terrainIntersect",_lastPosASL,_deltaStep); +}; + +#ifdef DEBUG_MODE_DRAW +if GVAR(dbgSphere) then { + [_lastPosASL vectorAdd _lastVelocityNorm, "orange"] call FUNC(dev_sphereDraw); + [_lastPosASL, "yellow"] call FUNC(dev_sphereDraw); +}; +#endif + +/* + * Improve performance of finding otherside of object on shallow angle + * impacts. 120 degrees due to 90 degree offset with _lastVelocityNorm into object. + */ +private _spallPosASL = _lastPosASL vectorAdd _deltaStep; +if (120 > acos (_lastVelocityNorm vectorDotProduct _surfaceNorm)) then { + _spallPosASL = _spallPosASL vectorAdd (_deltaStep vectorMultiply 5); +}; +private _insideObject = true; +for "_i" from 2 to 21 do +{ + private _nextPos = _spallPosASL vectorAdd _deltaStep; + if (!lineIntersects [_spallPosASL, _nextPos]) then { + _spallPosASL = _nextPos vectorAdd (_deltaStep vectorMultiply 2); + _insideObject = false; + break + }; + _spallPosASL = _nextPos; +}; + +if (_insideObject) exitWith { + TRACE_3("insideObj",_lastPosASL,_spallPosASL,alive _projectile); +}; +// Passed all exitWiths +GVAR(nextSpallAllowTime) = CBA_missionTime + ACE_FRAG_SPALL_HOLDOFF; + +#ifdef DEBUG_MODE_DRAW +if GVAR(dbgSphere) then { + [_spallPosASL, "green"] call FUNC(dev_sphereDraw); +}; +#endif + +private _spawnSize = switch (true) do { + case (_spallPower < ACE_FRAG_SPALL_POWER_TINY_MAX): {"_spall_tiny"}; + case (_spallPower < ACE_FRAG_SPALL_POWER_SMALL_MAX): {"_spall_small"}; + case (_spallPower < ACE_FRAG_SPALL_POWER_MEDIUM_MAX): {"_spall_medium"}; + case (_spallPower < ACE_FRAG_SPALL_POWER_LARGE_MAX): {"_spall_large"}; + default {"_spall_huge"}; +}; + +#ifdef DEBUG_MODE_FULL +systemChat ("spd: " + str (_speedChange * ACE_FRAG_SPALL_VELOCITY_INHERIT_COEFF) + ", spallPow: " + str _spallPower); +#endif + +TRACE_5("Calling event:",QUOTE(GLUE(ADDON,_)) + _material + _spawnSize,_lastVelocityNorm,_vectorUp,_speedChange,_shotParents); +[ + QGVAR(spallEvent), + [QUOTE(GLUE(ADDON,_)) + _material + _spawnSize, _lastVelocityNorm, _vectorUp, _speedChange, _shotParents] +] call CBA_fnc_serverEvent; diff --git a/addons/frag/functions/fnc_doSpallServer.sqf b/addons/frag/functions/fnc_doSpallServer.sqf new file mode 100644 index 00000000000..bf4c43ce721 --- /dev/null +++ b/addons/frag/functions/fnc_doSpallServer.sqf @@ -0,0 +1,45 @@ +#include "..\script_component.hpp" +/* + * Author: Jaynus, NouberNou, Lambda.Tiger, + * This function creates the requested spalling submunition spawner at a speed and direction. + * The function is intended to create the spalling on a server via an event call by + * ace_frag_fnc_doSpallLocal. The "local" version determines whether an event has occured + * and triggers this event if it has. + * + * Arguments: + * 0: Class name of the spall spawner + * 1: Normalized 3D vector direction of the spall spawner + * 2: "Up" vector for the projectile, required for the spawner to aim out of the 2D plane + * 3: The change in velocity that spalling projectile experienced + * 4: Shot parents array for the projectile that creates spall + * + * Return Value: + * None + * + * Example: + * [QGVAR(rock_spall_tiny), [1,0,0], [0,0,1], 300, [objNull, ace_player]] call ace_frag_fnc_doSpallServer + * + * Public: No + */ +params ["_spallSpawnerName", "_lastVelocityNorm", "_vectorUp", "_speedChange", "_shotParents"]; + +private _spallSpawner = createVehicle [ + _spallSpawnerName, + ASLToATL _spallPosASL, + [], + 0, + "CAN_COLLIDE" +]; +_spallSpawner setVectorDirandUp [_lastVelocityNorm, _vectorUp]; +_spallSpawner setVelocityModelSpace [0, _speedChange * ACE_FRAG_SPALL_VELOCITY_INHERIT_COEFF, 0]; +_spallSpawner setShotParents _shotParents; + +#ifdef DEBUG_MODE_DRAW +_spallSpawner addEventHandler [ + "SubmunitionCreated", + { + params ["", "_submunitionProjectile"]; + _submunitionProjectile call FUNC(dev_addRound); + } +]; +#endif diff --git a/addons/frag/functions/fnc_fired.sqf b/addons/frag/functions/fnc_fired.sqf index 03d2fab6096..2c5b61d349e 100644 --- a/addons/frag/functions/fnc_fired.sqf +++ b/addons/frag/functions/fnc_fired.sqf @@ -1,14 +1,13 @@ #include "..\script_component.hpp" /* - * Author: nou, jaynus, PabstMirror - * Called from the unified fired EH for all. - * If spall is not enabled (default), then cache and only track those that will actually trigger fragmentation. + * Author: Lambda.Tiger + * Add eventhandlers to rounds as needed * * Arguments: - * None. Parameters inherited from EFUNC(common,firedEH) + * Parameters inherited from EFUNC(common,firedEH) * * Return Value: - * None + * Nothing Useful * * Example: * [clientFiredBIS-XEH] call ace_frag_fnc_fired @@ -19,40 +18,48 @@ //IGNORE_PRIVATE_WARNING ["_unit", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle", "_gunner", "_turret"]; TRACE_10("firedEH:",_unit,_weapon,_muzzle,_mode,_ammo,_magazine,_projectile,_vehicle,_gunner,_turret); -private _shouldAdd = GVAR(cacheRoundsTypesToTrack) get _ammo; -if (isNil "_shouldAdd") then { - TRACE_1("no cache for round",_ammo); - - //Read configs and test if it would actually cause a frag, using same logic as FUNC(pfhRound) - private _skip = getNumber (configFile >> "CfgAmmo" >> _ammo >> QGVAR(skip)); - private _explosive = getNumber (configFile >> "CfgAmmo" >> _ammo >> "explosive"); - private _indirectRange = getNumber (configFile >> "CfgAmmo" >> _ammo >> "indirectHitRange"); - private _force = getNumber (configFile >> "CfgAmmo" >> _ammo >> QGVAR(force)); - private _fragPower = getNumber (configFile >> "CfgAmmo" >> _ammo >> "indirecthit") * (sqrt (getNumber (configFile >> "CfgAmmo" >> _ammo >> "indirectHitRange"))); - - _shouldAdd = (_skip == 0) && {(_force == 1) || {_explosive > 0.5 && {_indirectRange >= 4.5} && {_fragPower >= 35}}}; +if (_ammo isEqualTo "" || {isNull _projectile} || + {_projectile getVariable [QGVAR(blacklisted), false]}) exitWith { + TRACE_2("bad ammo or projectile, or blackList",_ammo,_projectile); +}; - if (GVAR(spallEnabled) && {!_shouldAdd}) then { - private _caliber = getNumber (configFile >> "CfgAmmo" >> _ammo >> "caliber"); - if !(_caliber >= 2.5 || {(_explosive > 0 && {_indirectRange >= 1})}) exitWith {}; // from check in doSpall: line 34 - TRACE_1("Won't frag, but will spall",_caliber); - _shouldAdd = true; - }; - TRACE_6("Setting Cache",_skip,_explosive,_indirectRange,_force,_fragPower,_shouldAdd); - GVAR(cacheRoundsTypesToTrack) set [_ammo, _shouldAdd]; +#ifdef DEBUG_MODE_DRAW +if (GVAR(debugOptions) && {true in (_ammo call FUNC(shouldFrag)) || {_ammo call FUNC(shouldSpall)}}) then { + [_projectile, "red", true] call FUNC(dev_trackObj); }; +#endif -if (_shouldAdd) then { - // firedMan will have nil "_gunner", so just check _unit; for firedVehicle we want to check _gunner - private _localShooter = if (isNil "_gunner") then {local _unit} else {local _gunner}; - TRACE_4("",_localShooter,_unit,_ammo,_projectile); - if (!_localShooter) exitWith {}; - if (_weapon == "Put") exitWith {}; // Ignore explosives placed without ace_explosives +if (GVAR(spallEnabled) && {_ammo call FUNC(shouldSpall)}) then { + _projectile addEventHandler [ + "HitPart", + { + params ["_projectile", "_hitObject", "", "_posASL", "_velocity", "_surfNorm", "", "", "_surfType"]; - // Skip if less than 0.5 second from last shot - if ((CBA_missionTime - (_unit getVariable [QGVAR(lastTrack), -1])) < 0.5) exitWith {}; - _unit setVariable [QGVAR(lastTrack), CBA_missionTime]; + if (_projectile getVariable [QGVAR(blacklisted), false]) exitWith { + TRACE_2("projectile blackisted",typeOf _projectile,_projectile); + }; - [_unit, _ammo, _projectile] call FUNC(addPfhRound); + // starting v2.18 it may be faster to use the instigator parameter, the same as the second entry shotParents, to recreate _shotParent + // The "explode" EH does not get the same parameter + private _shotParent = getShotParents _projectile; + private _ammo = typeOf _projectile; + private _vectorUp = vectorUp _projectile; + /* + * Wait a frame to see what happens to the round, may result in + * multiple hits / slowdowns getting shunted to the first hit + */ + [ + FUNC(doSpallLocal), + [_projectile, _hitObject, _posASL, _velocity, _surfNorm, _surfType, _ammo, _shotParent, _vectorUp] + ] call CBA_fnc_execNextFrame; + } + ]; }; +if !(GVAR(reflectionsEnabled) || GVAR(enabled)) exitWith { + TRACE_1("firedExit No frag/reflections",_ammo); +}; + +[_ammo, _projectile] call FUNC(roundInitFrag); + +TRACE_1("firedExit",_ammo); diff --git a/addons/frag/functions/fnc_frago.sqf b/addons/frag/functions/fnc_frago.sqf deleted file mode 100644 index 8fd77dbc739..00000000000 --- a/addons/frag/functions/fnc_frago.sqf +++ /dev/null @@ -1,194 +0,0 @@ -#include "..\script_component.hpp" -/* - * Author: Jaynus, NouberNou - * Server func to create the fragmentation for a round. - * - * Arguments: - * 0: Last Position (ASL) - * 1: Velocity - * 2: Ammo Classname - * - * Return Value: - * None - * - * Example: - * [[], [], "handGrenade"] call ace_frag_fnc_frago - * - * Public: No - */ - -#define FRAG_VEC_VAR 0.004 -#define MAX_FRAG_COUNT 50 - -BEGIN_COUNTER(frago); - -params ["_lastPos", "_lastVel", "_shellType"]; -TRACE_3("frago",_lastPos,_lastVel,_shellType); - -// Limit max frag count if there was a recent frag -private _maxFrags = round (MAX_FRAG_COUNT * linearConversion [0.1, 1.5, (CBA_missionTime - GVAR(lastFragTime)), 0.1, 1, true]); -TRACE_2("",_maxFrags,CBA_missionTime - GVAR(lastFragTime)); -GVAR(lastFragTime) = CBA_missionTime; - -private _fragTypes = [ - QGVAR(tiny), QGVAR(tiny), QGVAR(tiny), - QGVAR(tiny_HD), QGVAR(tiny_HD), QGVAR(tiny_HD), - QGVAR(small), QGVAR(small), QGVAR(small), QGVAR(small), - QGVAR(small_HD), QGVAR(small_HD), QGVAR(small_HD), QGVAR(small_HD), - QGVAR(medium_HD), QGVAR(medium_HD), QGVAR(medium_HD), QGVAR(medium_HD), QGVAR(medium_HD) -]; - -private _warn = false; -if (isArray (configFile >> "CfgAmmo" >> _shellType >> QGVAR(CLASSES))) then { - _fragTypes = getArray (configFile >> "CfgAmmo" >> _shellType >> QGVAR(CLASSES)); -} else { - _warn = true; -}; - -private _indirectHitRange = getNumber(configFile >> "CfgAmmo" >> _shellType >> "indirecthitrange"); -private _fragRange = 20 * _indirectHitRange * 4; -// _c = 185; // grams of comp-b -// _m = 210; // grams of fragmentating metal -// _k = 3/5; // spherical K factor -// _gC = 2843; // Gurney constant of comp-b in /ms - -// _c = 429; // grams of tritonal -// _m = 496; // grams of fragmentating metal -// _k = 1/2; // spherical K factor -// _gC = 2320; // Gurney constant of tritonal in /ms - -private _c = getNumber (configFile >> "CfgAmmo" >> _shellType >> QGVAR(CHARGE)); -if (_c == 0) then {_c = 1; _warn = true;}; -private _m = getNumber (configFile >> "CfgAmmo" >> _shellType >> QGVAR(METAL)); -if (_m == 0) then {_m = 2; _warn = true;}; -private _k = getNumber (configFile >> "CfgAmmo" >> _shellType >> QGVAR(GURNEY_K)); -if (_k == 0) then {_k = 0.5; _warn = true;}; -private _gC = getNumber (configFile >> "CfgAmmo" >> _shellType >> QGVAR(GURNEY_C)); -if (_gC == 0) then {_gC = 2440; _warn = true;}; - -if (_warn) then { - INFO_1("Ammo class %1 lacks proper explosive properties definitions for frag!",_shellType); -}; - -// Gunery equation is for a non-fragmenting metal, imperical value of 80% represents fragmentation -private _fragPower = 0.8 * (((_m / _c) + _k) ^ - (1 / 2)) * _gC; - -private _atlPos = ASLtoATL _lastPos; - -private _fragPowerRandom = _fragPower * 0.5; -if ((_atlPos select 2) < 0.5) then { - _lastPos vectorAdd [0, 0, 0.5]; -}; - -private _objects = _atlPos nearEntities [["Car", "Motorcycle", "Tank", "StaticWeapon", "CAManBase", "Air", "Ship"], _fragRange]; -// Add unique crews in faster way -{ - { - _objects pushBackUnique _x; - } forEach (crew _x); -} forEach _objects; -TRACE_2("",_fragRange,count _objects); - -private _fragCount = 0; - -private _fragArcs = []; -_fragArcs set [360, 0]; - -private _doRandom = true; -if (_objects isNotEqualTo []) then { - if (GVAR(reflectionsEnabled)) then { - [_lastPos, _shellType] call FUNC(doReflections); - }; - { - private _target = _x; - if (alive _target) then { - (boundingBox _target) params ["_boundingBoxA", "_boundingBoxB"]; - - private _cubic = ((abs (_boundingBoxA select 0)) + (_boundingBoxB select 0)) * ((abs (_boundingBoxA select 1)) + (_boundingBoxB select 1)) * ((abs (_boundingBoxA select 2)) + (_boundingBoxB select 2)); - - if (_cubic <= 1) exitWith {}; - // _doRandom = true; - - private _targetVel = velocity _target; - private _targetPos = getPosASL _target; - private _distance = _targetPos vectorDistance _lastPos; - private _add = ((_boundingBoxB select 2) / 2) + ((((_distance - (_fragpower / 8)) max 0) / _fragPower) * 10); - - _targetPos = _targetPos vectorAdd [ - (_targetVel select 0) * (_distance / _fragPower), - (_targetVel select 1) * (_distance / _fragPower), - _add - ]; - - private _baseVec = _lastPos vectorFromTo _targetPos; - - private _dir = floor (_baseVec call CBA_fnc_vectDir); - private _currentCount = RETDEF(_fragArcs select _dir,0); - if (_currentCount < 10) then { - private _count = ceil (random (sqrt (_m / 1000))); - private _vecVar = FRAG_VEC_VAR; - if !(_target isKindOf "Man") then { - ADD(_vecVar,(sqrt _cubic) / 2000); - if ((crew _target) isEqualTo [] && {_count > 0}) then { - _count = 0 max (_count / 2); - }; - }; - for "_i" from 1 to _count do { - private _vec = _baseVec vectorDiff [ - (_vecVar / 2) + (random _vecVar), - (_vecVar / 2) + (random _vecVar), - (_vecVar / 2) + (random _vecVar) - ]; - - private _fp = _fragPower - (random (_fragPowerRandom)); - private _vel = _vec vectorMultiply _fp; - - private _fragObj = (selectRandom _fragTypes) createVehicleLocal [0,0,10000]; - // TRACE_4("targeted",_fp,typeOf _fragObj,_lastPos vectorDistance _targetPos,typeOf _x); - _fragObj setPosASL _lastPos; - _fragObj setVectorDir _vec; - _fragObj setVelocity _vel; - #ifdef DRAW_FRAG_INFO - [ACE_player, _fragObj, [1,0,0,1]] call FUNC(dev_addTrack); - #endif - INC(_fragCount); - INC(_currentCount); - }; - _fragArcs set [_dir, _currentCount]; - }; - }; - if (_fragCount > _maxFrags) exitWith {}; - } forEach _objects; - TRACE_1("targeted",_fragCount); - if (_fragCount > _maxFrags) exitWith {}; - private _randomCount = ceil ((_maxFrags - _fragCount) * 0.35); - TRACE_1("",_randomCount); - private _sectorSize = 360 / (_randomCount max 1); - - if (_doRandom) then { - for "_i" from 1 to _randomCount do { - // Distribute evenly - private _sectorOffset = 360 * (_i - 1) / (_randomCount max 1); - private _randomDir = random (_sectorSize); - _vec = [cos (_sectorOffset + _randomDir), sin (_sectorOffset + _randomDir), sin (30 - (random 45))]; - - _fp = (_fragPower - (random (_fragPowerRandom))); - - _vel = _vec vectorMultiply _fp; - - _fragObj = (selectRandom _fragTypes) createVehicleLocal [0, 0, 10000]; - _fragObj setPosASL _lastPos; - _fragObj setVectorDir _vec; - _fragObj setVelocity _vel; - - #ifdef DRAW_FRAG_INFO - [ACE_player, _fragObj, [1,0.5,0,1]] call FUNC(dev_addTrack); - #endif - INC(_fragCount); - }; - }; -}; - -TRACE_1("total created",_fragCount); - -END_COUNTER(frago); diff --git a/addons/frag/functions/fnc_getFragInfo.sqf b/addons/frag/functions/fnc_getFragInfo.sqf new file mode 100644 index 00000000000..fa1a50df411 --- /dev/null +++ b/addons/frag/functions/fnc_getFragInfo.sqf @@ -0,0 +1,107 @@ +#include "..\script_component.hpp" +/* + * Author: Jaynus, NouberNou, Lambda.Tiger + * This function returns fragmentation parameters for a specific ammo type. + * + * Arguments: + * 0: Ammo classname + * + * Return Value: + * _ammoInfo + * 0: Search range for fragments in meters + * 1: Gurney equation calculated speed + * 2: Array of fragment types + * 3: Modified frag count under assumptions of spherical fragmentation + * + * Example: + * "B_556x45_Ball" call ace_frag_fnc_getFragInfo + * + * Public: No + */ + +params ["_ammo"]; + +private _ammoInfo = GVAR(fragInfoCache) get _ammo; + +if (!isNil "_ammoInfo") exitWith {_ammoInfo}; + +private _ammoConfig = configFile >> "CfgAmmo" >> _ammo; +private _fragTypes = []; +private _notifyMissingEntries = false; +if (isArray (_ammoConfig >> QGVAR(classes))) then { + _fragTypes = getArray (_ammoConfig >> QGVAR(classes)); +} else { + _notifyMissingEntries = true; +}; + +/************ Gurney equation notes *****************//* + * see https://en.wikipedia.org/wiki/Gurney_equations + * + * gurney_k is the geometry constant added to _metalMass/_chargeMass + * gurney_c = sqrt(2E) + * + * _chargeMass = 185; - grams of comp-b + * _metalMass = 210; - grams of metal are accelerated by explosion + * _geometryCoefficient = 3/5; - spherical K factor + * _gurneyConstant = 2843; - Gurney constant of comp-b in /ms + * + * _chargeMass = 429; - grams of tritonal + * _metalMass = 496; - grams of metal are accelerated by explosion + * _geometryCoefficient = 1/2; - cylindrical K factor + * _gurneyConstant = 2320; - Gurney constant of tritonal in m/s + * Equation - 0.8 for empirical 80% speed + * 0.8 * (((_metalMass / _chargeMass) + _geometryCoefficient) ^ - (1 / 2)) * _gurneyConstant; + * or 0.8 * _gurneyConstant * sqrt (_chargeMass /(_metalMass + _chargeMass * _geometryCoefficient)); (slightly faster to compute) + */ + +private _chargeMass = getNumber (_ammoConfig >> QGVAR(charge)); +if (_chargeMass == 0) then { + _chargeMass = 1; + _notifyMissingEntries = true; +}; + +private _metalMass = getNumber (_ammoConfig >> QGVAR(metal)); +if (_metalMass == 0) then { + _metalMass = 2; + _notifyMissingEntries = true; +}; + +private _geometryCoefficient = getNumber (_ammoConfig >> QGVAR(gurney_k)); +if (_geometryCoefficient == 0) then { + _geometryCoefficient = 0.5; + _notifyMissingEntries = true; +}; + +private _gurneyConstant = getNumber (_ammoConfig >> QGVAR(gurney_c)); +if (_gurneyConstant == 0) then { + _gurneyConstant = 2440; + _notifyMissingEntries = true; +}; + +private _fragCount = getNumber (_ammoConfig >> QGVAR(fragCount)); +if (_fragCount == 0) then { + private _indirectHitRange = getNumber (_ammoConfig >> "indirectHitRange"); + _fragCount = 4 * pi * ACE_FRAG_MIN_FRAG_HIT_CHANCE * (20 *_indirectHitRange)^2; + _fragCount = _fragCount max 250; + _notifyMissingEntries = true; +}; + +if (_notifyMissingEntries) then { + INFO_1("Ammo class %1 lacks proper explosive properties definitions for frag!",_ammo); +}; + +/********************** _ammoInfo format *************************//* + * 0: _fragRange - search range for fragments, calculated with the minimum chance to hit as defined + * 1: _fragVelocity - gurney equation calculated velocity + * 2: _fragTypes - array of fragment types + * 3: _fragCount - modified frag count used under assumptions of spherical fragmentation + */ +_ammoInfo = [ + ACE_FRAG_MAX_FRAG_RANGE min sqrt (_fragCount / (4 * pi * ACE_FRAG_MIN_FRAG_HIT_CHANCE)), + ACE_FRAG_IMPERIC_VELOCITY_CONSTANT * _gurneyConstant * sqrt (_chargeMass / (_metalMass + _chargeMass * _geometryCoefficient)), + _fragTypes, + _fragCount / 4 / pi +]; + +GVAR(fragInfoCache) set [_ammo, _ammoInfo]; +_ammoInfo diff --git a/addons/frag/functions/fnc_getMaterialInfo.sqf b/addons/frag/functions/fnc_getMaterialInfo.sqf new file mode 100644 index 00000000000..199953b237b --- /dev/null +++ b/addons/frag/functions/fnc_getMaterialInfo.sqf @@ -0,0 +1,62 @@ +#include "..\script_component.hpp" +/* + * Author: Lambda.Tiger + * This function returns a classification of material type based on the surface hit. + * + * Arguments: + * 0: Surface type given as either a CfgSurfaces path or .bisurf filepath, same format as "HitPart" projectile parameter + * + * Return Value: + * Material categories as expanded on in line 44 below + * + * Example: + * "a3\data_f\penetration\concrete.bisurf" call ace_frag_fnc_getMaterialInfo + * + * Public: No + */ + +#define ACE_FRAG_SOUNDENVIRON_STR_LEN 12 +#define ACE_FRAG_SOUNDHIT_STR_LEN 8 +#define ACE_FRAG_MATERIAL_SEARCH_LEN 10 + +params ["_surfType"]; + +private _material = GVAR(spallMaterialCache) get _surfType; + +TRACE_2("materialCache",_surfType,_material); +if (!isNil "_material") exitWith {_material}; +// Use 'soundEnviron' or 'soundHit' to extract approx material +private _surfaceConfig = configFile >> "CfgSurfaces" >> _surfType; +if (isClass _surfaceConfig) then { + _material = getText (_surfaceConfig >> "soundEnviron"); + if (_material == "" || {_material == "empty"}) then { + _material = getText (_surfaceConfig >> "soundhit"); + }; +} else { // Messy way when a surface isn't added to CfgSurfaces + private _surfFileText = toLowerANSI preprocessFile _surfType; + _surfFileText = _surfFileText regexReplace ["[^a-z0-9]", ""]; + private _idx = ACE_FRAG_SOUNDENVIRON_STR_LEN + (_surfFileText find "soundenviron"); + if (_surfFileText select [_idx, 5] isEqualTo "empty") then { + _idx = ACE_FRAG_SOUNDHIT_STR_LEN + (_surfFileText find "soundhit"); + }; + _material = _surfFileText select [_idx, ACE_FRAG_MATERIAL_SEARCH_LEN]; +}; +TRACE_1("materialSubString",_material); + +_material = switch (true) do { + case ("dirt" in _material); + case ("grass" in _material): { "ground" }; + case ("gravel" in _material); + case ("rock" in _material): { "rock" }; + case ("wood" in _material): { "wood" }; + case ("lino" in _material); + case ("building" in _material); + case ("concrete" in _material): { "concrete" }; + case ("metal" in _material): { "metal" }; + default { "ground" }; +}; + +GVAR(spallMaterialCache) set [_surfType, _material]; +TRACE_2("materialCacheSet",_surfType,_material); + +_material diff --git a/addons/frag/functions/fnc_getSpallInfo.sqf b/addons/frag/functions/fnc_getSpallInfo.sqf new file mode 100644 index 00000000000..bc988887054 --- /dev/null +++ b/addons/frag/functions/fnc_getSpallInfo.sqf @@ -0,0 +1,30 @@ +#include "..\script_component.hpp" +/* + * Author: Lambda.Tiger + * This function returns spalling parameters for a specific ammo type. + * + * Arguments: + * 0: Ammo classname + * + * Return Value: + * _ammoInfo + * 0: Caliber + * 1: What part of the hit damage is from ballistic vs explosive energy (1 for all explosive) + * 2: Indirect hit damage + * + * Example: + * "B_556x45_Ball" call ace_frag_fnc_getSpallInfo + * + * Public: No + */ + +params ["_ammo"]; + +GVAR(spallInfoCache) getOrDefaultCall [_ammo, { + private _ammoConfig = configFile >> "CfgAmmo" >> _ammo; + private _caliber = getNumber (_ammoConfig >> "caliber"); + private _explosive = 1 min getNumber (_ammoConfig >> "explosive"); + private _indirectHit = getNumber (_ammoConfig >> "indirectHitRange"); + + [_caliber, _explosive, _indirectHit] +}, true] diff --git a/addons/frag/functions/fnc_initMaterialCache.sqf b/addons/frag/functions/fnc_initMaterialCache.sqf new file mode 100644 index 00000000000..fd7a84a72d0 --- /dev/null +++ b/addons/frag/functions/fnc_initMaterialCache.sqf @@ -0,0 +1,96 @@ +#include "..\script_component.hpp" +/* + * Author: Lambda.Tiger + * For performance, we load a bunch of vanilla materials preemptively into the spall material cache. + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * call ace_frag_fnc_initMaterialCache + * + * Public: No + */ + +GVAR(spallMaterialCache) = createHashMapFromArray [ + ["a3\data_f\penetration\armour.bisurf","metal"], + ["a3\data_f\penetration\armour_plate.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_100mm.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_12mm.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_16mm.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_1mm.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_20mm.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_23mm.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_250mm.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_30mm.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_3mm.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_40mm.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_5mm.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_60mm.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_7mm.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_80mm.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_heavy.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_medium.bisurf","metal"], + ["a3\data_f\penetration\armour_plate_thin.bisurf","metal"], + ["a3\data_f\penetration\bell.bisurf","ground"], + ["a3\data_f\penetration\body.bisurf","ground"], + ["a3\data_f\penetration\building.bisurf","concrete"], + ["a3\data_f\penetration\building_dust_particle.bisurf","concrete"], + ["a3\data_f\penetration\building_dust_soft.bisurf","concrete"], + ["a3\data_f\penetration\building_plate.bisurf","concrete"], + ["a3\data_f\penetration\building_wood_particle.bisurf","wood"], + ["a3\data_f\penetration\cactus.bisurf","ground"], + ["a3\data_f\penetration\cloth.bisurf","ground"], + ["a3\data_f\penetration\cloth_plate.bisurf","ground"], + ["a3\data_f\penetration\concrete.bisurf","concrete"], + ["a3\data_f\penetration\concrete_plate.bisurf","concrete"], + ["a3\data_f\penetration\default.bisurf","ground"], + ["a3\data_f\penetration\engine.bisurf","metal"], + ["a3\data_f\penetration\foliage.bisurf","ground"], + ["a3\data_f\penetration\foliage_dead.bisurf","ground"], + ["a3\data_f\penetration\foliage_dead_plate.bisurf","ground"], + ["a3\data_f\penetration\foliage_green.bisurf","ground"], + ["a3\data_f\penetration\foliage_green_big.bisurf","ground"], + ["a3\data_f\penetration\foliage_green_big_plate.bisurf","ground"], + ["a3\data_f\penetration\foliage_green_plate.bisurf","ground"], + ["a3\data_f\penetration\foliage_palm.bisurf","ground"], + ["a3\data_f\penetration\foliage_palm_plate.bisurf","ground"], + ["a3\data_f\penetration\foliage_pine.bisurf","ground"], + ["a3\data_f\penetration\foliage_pine_plate.bisurf","ground"], + ["a3\data_f\penetration\foliage_plate.bisurf","ground"], + ["a3\data_f\penetration\fueltank.bisurf","metal"], + ["a3\data_f\penetration\glass.bisurf","ground"], + ["a3\data_f\penetration\glass_armored.bisurf","ground"], + ["a3\data_f\penetration\glass_armored_plate.bisurf","ground"], + ["a3\data_f\penetration\glass_plate.bisurf","ground"], + ["a3\data_f\penetration\granite.bisurf","ground"], + ["a3\data_f\penetration\granite_plate.bisurf","ground"], + ["a3\data_f\penetration\hard_ground.bisurf","ground"], + ["a3\data_f\penetration\hay.bisurf","ground"], + ["a3\data_f\penetration\iron_cast.bisurf","metal"], + ["a3\data_f\penetration\iron_cast_plate.bisurf","metal"], + ["a3\data_f\penetration\leather.bisurf","ground"], + ["a3\data_f\penetration\meat.bisurf","ground"], + ["a3\data_f\penetration\meatbones.bisurf","ground"], + ["a3\data_f\penetration\medium_ground.bisurf","ground"], + ["a3\data_f\penetration\metal.bisurf","metal"], + ["a3\data_f\penetration\metal_plate.bisurf","metal"], + ["a3\data_f\penetration\metal_plate_thin.bisurf","metal"], + ["a3\data_f\penetration\plastic.bisurf","ground"], + ["a3\data_f\penetration\plastic_plate.bisurf","ground"], + ["a3\data_f\penetration\plexiglass.bisurf","ground"], + ["a3\data_f\penetration\plexiglass_plate.bisurf","ground"], + ["a3\data_f\penetration\rubber.bisurf","ground"], + ["a3\data_f\penetration\soft_ground.bisurf","ground"], + ["a3\data_f\penetration\tyre.bisurf","ground"], + ["a3\data_f\penetration\tyre_armored.bisurf","ground"], + ["a3\data_f\penetration\vehicle_interior.bisurf","metal"], + ["a3\data_f\penetration\void.bisurf","ground"], + ["a3\data_f\penetration\water.bisurf","ground"], + ["a3\data_f\penetration\weapon_plate.bisurf","metal"], + ["a3\data_f\penetration\wood.bisurf","wood"], + ["a3\data_f\penetration\wood_plate.bisurf","wood"] +]; diff --git a/addons/frag/functions/fnc_masterPFH.sqf b/addons/frag/functions/fnc_masterPFH.sqf deleted file mode 100644 index 004af9a9ce5..00000000000 --- a/addons/frag/functions/fnc_masterPFH.sqf +++ /dev/null @@ -1,56 +0,0 @@ -#include "..\script_component.hpp" -/* - * Author: jaynus - * Master single PFH abstraction for all rounds being tracked by frag/spall. - * - * Arguments: - * None - * - * Return Value: - * None - * - * Example: - * call ace_frag_fnc_masterPFH - * - * Public: No - */ - -BEGIN_COUNTER(PFH); - -// Fast exit if nothing to do -if (GVAR(objects) isEqualTo []) exitWith {END_COUNTER(PFH);}; - -private _gcIndex = []; - -private _iter = 0; -private _objectCount = count GVAR(objects); -while {_objectCount > 0 && {_iter < (GVAR(maxTrackPerFrame) min _objectCount)}} do { - - if (GVAR(lastIterationIndex) >= _objectCount) then { - GVAR(lastIterationIndex) = 0; - }; - private _object = GVAR(objects) select GVAR(lastIterationIndex); - - if (!isNil "_object") then { - private _args = GVAR(arguments) select GVAR(lastIterationIndex); - - if !(_args call FUNC(pfhRound)) then { - _gcIndex pushBack GVAR(lastIterationIndex); // Add it to the GC if it returns false - }; - }; - INC(_iter); - INC(GVAR(lastIterationIndex)); -}; - -// Clean up dead object references -private _deletionCount = 0; -{ - TRACE_1("GC Projectile",_x); - private _deleteIndex = _x - _deletionCount; - GVAR(objects) deleteAt _deleteIndex; - GVAR(arguments) deleteAt _deleteIndex; - - INC(_deletionCount); -} forEach _gcIndex; - -END_COUNTER(PFH); diff --git a/addons/frag/functions/fnc_pfhRound.sqf b/addons/frag/functions/fnc_pfhRound.sqf deleted file mode 100644 index 0c261dcffba..00000000000 --- a/addons/frag/functions/fnc_pfhRound.sqf +++ /dev/null @@ -1,59 +0,0 @@ -#include "..\script_component.hpp" -/* - * Author: ACE-Team - * - * - * Arguments: - * None - * - * Return Value: - * None - * - * Example: - * call ace_frag_fnc_pfhRound - * - * Public: No - */ - -params ["_round", "_lastPos", "_lastVel", "_shellType", "_firedFrame", "_firedPos", "_doSpall", "_spallTrack", "_foundObjectHPIds", "_skip", "_explosive", "_indirectRange", "_force", "_fragPower"]; - -if (_round in GVAR(blackList)) exitWith { - false -}; - -if (!alive _round) exitWith { - if ((diag_frameno - _firedFrame) > 1) then { //skip if deleted within a single frame - if (_skip == 0) then { - if ((_explosive > 0.5 && {_indirectRange >= 4.5} && {_fragPower >= 35}) || {_force == 1}) then { - // shotbullet, shotShell don't seem to explode when touching water, so don't create frags - if ((surfaceIsWater _lastPos) && {(toLowerANSI getText (configFile >> "CfgAmmo" >> _shellType >> "simulation")) in ["shotbullet", "shotshell"]}) exitWith {}; - private _fuseDist = getNumber(configFile >> "CfgAmmo" >> _shellType >> "fuseDistance"); - private _isArmed = _firedPos vectorDistance _lastPos >= _fuseDist; // rounds explode at exactly fuseDistance, so check inclusive - TRACE_2("",_fuseDist,_isArmed); - if (!_isArmed) exitWith {TRACE_1("round not armed",_this);}; - TRACE_3("Sending frag event to server",_lastPos,_lastVel,_shellType); - [QGVAR(frag_eh), [_lastPos,_lastVel,_shellType]] call CBA_fnc_serverEvent; - }; - }; - }; - if (_doSpall) then { - DEC(GVAR(spallIsTrackingCount)); - TRACE_1("doSpall",_foundObjectHPIds); - { - if (!isNil "_x") then { - _x removeEventHandler ["HitPart", _foundObjectHPIds select _forEachIndex]; - }; - } forEach _spallTrack; - }; - false -}; - -_this set [1, getPosASL _round]; -_this set [2, velocity _round]; - -if (_doSpall) then { - private _scale = ((count GVAR(objects)) / GVAR(maxTrackPerFrame)) max 0.1; - [_round, _scale, _spallTrack, _foundObjectHPIds] call FUNC(spallTrack); -}; - -true diff --git a/addons/frag/functions/fnc_roundInitFrag.sqf b/addons/frag/functions/fnc_roundInitFrag.sqf new file mode 100644 index 00000000000..15c4ac63552 --- /dev/null +++ b/addons/frag/functions/fnc_roundInitFrag.sqf @@ -0,0 +1,54 @@ +#include "..\script_component.hpp" +/* + * Author: Lambda.Tiger + * Adds fragmentation event handlers to projectiles as needed. + * + * Arguments: + * 0: class name of projectile being initialized + * 1: Projectile being initialized + * + * Return Value: + * Nothing + * + * Example: + * [clientFiredBIS-XEH] call ace_frag_fnc_fired + * + * Public: No + */ +params ["_ammo", "_projectile"]; + +_ammo call FUNC(shouldFrag) params ["_shouldFrag", "_submunitionShouldFrag"]; +if (_shouldFrag) then { + _projectile addEventHandler [ + "Explode", + { + params ["_projectile", "_posASL", "_velocity"]; + + if (GVAR(reflectionsEnabled)) then { + [_posASL, _ammo] call FUNC(doReflections); + }; + + if (_projectile getVariable [QGVAR(blacklisted), false]) exitWith { + TRACE_2("projectile blackisted",typeOf _projectile,_projectile); + }; + + private _shotParents = getShotParents _projectile; + private _ammo = typeOf _projectile; + // Wait a frame to make sure it doesn't + [ + { [QGVAR(explosionEvent), _this] call CBA_fnc_serverEvent; }, + [_posASL, _velocity, _ammo, _shotParents] + ] call CBA_fnc_execNextFrame; + } + ]; +}; + +if (_submunitionShouldFrag) then { + _projectile addEventHandler [ + "SubmunitionCreated", + { + params ["", "_submunitionProjectile"]; + [typeOf _submunitionProjectile, _submunitionProjectile] call FUNC(roundInitFrag); + } + ]; +}; diff --git a/addons/frag/functions/fnc_shouldFrag.sqf b/addons/frag/functions/fnc_shouldFrag.sqf new file mode 100644 index 00000000000..04e1d489752 --- /dev/null +++ b/addons/frag/functions/fnc_shouldFrag.sqf @@ -0,0 +1,73 @@ +#include "..\script_component.hpp" +/* + * Author: Lambda.Tiger + * This function checks whether an ammunition type should create fragments. + * + * Arguments: + * 0: Ammo classname + * + * Return Value: + * An array containing + * 0: Should the ammo class generate fragments + * 1: Should the ammo class create submunitions that may fragment + * + * Example: + * "B_556x45_Ball" call ace_frag_fnc_shouldFrag + * + * Public: No + */ + +params ["_ammo"]; + +private _shouldFrag = GVAR(shouldFragCache) get _ammo; + +if (!isNil "_shouldFrag") exitWith {_shouldFrag}; + +_shouldFrag = [true, false]; + +private _ammoConfig = configFile >> "CfgAmmo" >> _ammo; +private _skip = getNumber (_ammoConfig >> QGVAR(skip)); +if (_skip == 1) then { + _shouldFrag set [0, false]; + TRACE_1("No frag: skip",_skip); +}; + +private _force = getNumber (_ammoConfig >> QGVAR(force)); +if (_shouldFrag#0 && _force == 0) then { + private _explosive = getNumber (_ammoConfig >> "explosive"); + if (_explosive < 0.4) exitWith { + _shouldFrag set [0, false]; + TRACE_3("No frag: _explosive",_skip,_force,_explosive); + }; + private _indirectHit = getNumber (_ammoConfig >> "indirectHit"); + private _indirectRange = getNumber (_ammoConfig >> "indirectHitRange"); + if (_indirectHit < 3 || + {_indirectRange < 5 && _indirectHit < _indirectRange}) then { + _shouldFrag set [0, false]; + TRACE_5("No frag",_ammo,_skip,_explosive,_indirectRange,_indirectHit); + }; +}; + +private _ammoSubmunitionConfigPath = _ammoConfig >> "submunitionAmmo"; +if (isText _ammoSubmunitionConfigPath) then { + private _submunitionAmmo = getText _ammoSubmunitionConfigPath; + TRACE_1("Submunition ammo text: ",_submunitionAmmo); + if (_submunitionAmmo isNotEqualTo "") then { + private _shouldSubmunitionFrag = _submunitionAmmo call FUNC(shouldFrag); + _shouldFrag set [1, _shouldSubmunitionFrag#0 || _shouldSubmunitionFrag#1]; + }; +} else { + private _submunitionArray = getArray _ammoSubmunitionConfigPath; + TRACE_1("Submunition ammo array: ",_submunitionArray); + for "_i" from 0 to count _submunitionArray - 1 step 2 do { + private _submunitionAmmo = _submunitionArray#_i; + if (_submunitionAmmo isNotEqualTo "") then { + private _shouldSubmunitionFrag = _submunitionAmmo call FUNC(shouldFrag); + _shouldFrag set [1, _shouldSubmunitionFrag#0 || _shouldSubmunitionFrag#1]; + }; + }; +}; + +GVAR(shouldFragCache) set [_ammo, _shouldFrag]; + +_shouldFrag diff --git a/addons/frag/functions/fnc_shouldSpall.sqf b/addons/frag/functions/fnc_shouldSpall.sqf new file mode 100644 index 00000000000..e3225c73c9d --- /dev/null +++ b/addons/frag/functions/fnc_shouldSpall.sqf @@ -0,0 +1,24 @@ +#include "..\script_component.hpp" +/* + * Author: Lambda.Tiger + * This function checks whether an ammunition type should cause spalling. + * + * Arguments: + * 0: Ammo classname + * + * Return Value: + * Whether the round type would spall when hitting an object + * + * Example: + * "B_556x45_Ball" call ace_frag_fnc_shouldSpall + * + * Public: No + */ + +params ["_ammo"]; + +GVAR(shouldSpallCache) getOrDefaultCall [_ammo, { + (_ammo call FUNC(getSpallInfo)) params ["_caliber", "_explosive", "_indirectHit"]; + + (_caliber * GVAR(spallIntensity) >= 1.25 * ACE_FRAG_SPALL_POWER_MIN) || {_explosive >= 0.5 && {_indirectHit * GVAR(spallIntensity) >= 2 * ACE_FRAG_SPALL_POWER_MIN}} +}, true] diff --git a/addons/frag/functions/fnc_spallHP.sqf b/addons/frag/functions/fnc_spallHP.sqf deleted file mode 100644 index 367bea76449..00000000000 --- a/addons/frag/functions/fnc_spallHP.sqf +++ /dev/null @@ -1,42 +0,0 @@ -#include "..\script_component.hpp" -/* - * Author: ACE-Team - * Handles the HitPart event - * - * Arguments: - * None - * - * Return Value: - * None - * - * Example: - * call ace_frag_fnc_spallHP - * - * Public: No - */ - -//player sideChat format ["f: %1 c: %2", (_this select 0), (count GVAR(spallHPData))]; - -params ["_index", "_hitPartData"]; - -private _initialData = GVAR(spallHPData) param [_index, []]; -if (_initialData isEqualTo []) exitWith {}; - -private _hpRound = (_hitPartData select 0) select 2; -private _round = _initialData select 3; -private _hpDirect = (_hitPartData select 0) select 10; - -if (_hpDirect && {_round == _hpRound}) then { - { - // diag_log text format ["HPDUMP-------------------------------------"]; - // { - // _hp = _x; - // diag_log text format ["%1 --", _forEachIndex]; - // { - // diag_log text format ["%1: %2", _forEachIndex, _x]; - // } forEach _hp; - // } forEach (_this select 1); - [DFUNC(doSpall), [_this, _forEachIndex]] call CBA_fnc_execNextFrame; - // player sideChat "WEEE"; - } forEach _hitPartData; -}; diff --git a/addons/frag/functions/fnc_spallTrack.sqf b/addons/frag/functions/fnc_spallTrack.sqf deleted file mode 100644 index 50ca64b6ec4..00000000000 --- a/addons/frag/functions/fnc_spallTrack.sqf +++ /dev/null @@ -1,39 +0,0 @@ -#include "..\script_component.hpp" -/* - * Author: ACE-Team - * Add HitPart EventHandler to objects in the projectile's path - * - * Arguments: - * None - * - * Return Value: - * None - * - * Example: - * call ace_frag_fnc_spallTrack - * - * Public: No - */ - -params ["_round", "_multiplier", "_foundObjects", "_foundObjectHPIds"]; - -private _delta = (1 / diag_fps) * _multiplier; -private _curPos = getPosASL _round; -private _velocity = velocity _round; - -private _velocityStep = _velocity vectorMultiply _delta; -private _forwardPos = _curPos vectorAdd _velocityStep; - - -private _intersectsWith = lineIntersectsWith [_curPos, _forwardPos]; - -if (_intersectsWith isEqualTo []) exitWith {}; -{ - // diag_log text format ["Adding HP: %1", _x]; - private _index = count GVAR(spallHPData); - private _hpId = _x addEventHandler ["HitPart", compile format ["[%1, _this] call " + QFUNC(spallHP), _index]]; - _foundObjects pushBack _x; - _foundObjectHPIds pushBack _hpId; - private _data = [_hpId, _x, typeOf _round, _round, _curPos, _velocity, 0, _foundObjects, _foundObjectHPIds]; - GVAR(spallHPData) pushBack _data; -} forEach (_intersectsWith select {!(_x in _foundObjects)}); diff --git a/addons/frag/initSettings.inc.sqf b/addons/frag/initSettings.inc.sqf index 421d5d45664..73cc70e6b47 100644 --- a/addons/frag/initSettings.inc.sqf +++ b/addons/frag/initSettings.inc.sqf @@ -1,40 +1,58 @@ -private _category = format ["ACE %1", localize LSTRING(Module_DisplayName)]; +private _category = format ["ACE %1", LLSTRING(Module_DisplayName)]; [ - QGVAR(enabled), "CHECKBOX", + QGVAR(enabled), + "CHECKBOX", [LSTRING(EnableFrag), LSTRING(EnableFrag_Desc)], - _category, + [_category, LSTRING(Frag)], true, 1 ] call CBA_fnc_addSetting; [ - QGVAR(spallEnabled), "CHECKBOX", + QGVAR(spallEnabled), + "CHECKBOX", [LSTRING(EnableSpall), LSTRING(EnableSpall_Desc)], - _category, + [_category, LSTRING(Spall)], false, 1 ] call CBA_fnc_addSetting; + [ - QGVAR(reflectionsEnabled), "CHECKBOX", + QGVAR(reflectionsEnabled), + "CHECKBOX", [LSTRING(EnableReflections), LSTRING(EnableReflections_Desc)], - _category, + [_category, LSTRING(Reflections)], false, 1 ] call CBA_fnc_addSetting; [ - QGVAR(maxTrack), "SLIDER", - [LSTRING(MaxTrack), LSTRING(MaxTrack_Desc)], - _category, - [0, 50, 10, -1], + QGVAR(fragSimComplexity), + "LIST", + [LSTRING(FragMode), LSTRING(FragMode_Desc)], + [_category, LSTRING(Frag)], + [[2, 1, 0], [LSTRING(FragMode_Opt2),LSTRING(FragMode_Opt1),LSTRING(FragMode_Opt0)], 2], 1 ] call CBA_fnc_addSetting; [ - QGVAR(maxTrackPerFrame), "SLIDER", - [LSTRING(MaxTrackPerFrame), LSTRING(MaxTrackPerFrame_Desc)], - _category, - [0, 50, 10, -1], + QGVAR(atLeastOne), + "CHECKBOX", + [LSTRING(MinFrag), LSTRING(MinFrag_Desc)], + [_category, LSTRING(Frag)], + false, 1 ] call CBA_fnc_addSetting; + +[ + QGVAR(spallIntensity), + "SLIDER", + [LSTRING(SpallIntensity), LSTRING(SpallIntensity_Desc)], + [_category, LSTRING(Spall)], + [0.1, 2, 1, 1], + 1, + { + GVAR(shouldSpallCache) = createHashMap; + } +] call CBA_fnc_addSetting; diff --git a/addons/frag/initSettingsDebug.inc.sqf b/addons/frag/initSettingsDebug.inc.sqf new file mode 100644 index 00000000000..a1528a446cf --- /dev/null +++ b/addons/frag/initSettingsDebug.inc.sqf @@ -0,0 +1,25 @@ +private _category = format ["ACE %1", LLSTRING(Module_DisplayName)]; + +[ + QGVAR(debugOptions), + "CHECKBOX", + [LSTRING(EnableDebugTrace), LSTRING(EnableDebugTrace_Desc)], + [_category, LSTRING(Debug)], + true +] call CBA_fnc_addSetting; + +[ + QGVAR(dbgSphere), + "CHECKBOX", + [LSTRING(HitSphereEnable), LSTRING(HitSphereEnable_Desc)], + [_category, LSTRING(Debug)], + false +] call CBA_fnc_addSetting; + +[ + QGVAR(drawHitBox), + "CHECKBOX", + [LSTRING(DrawHitBox), LSTRING(DrawHitBox_Desc)], + [_category, LSTRING(Debug)], + true +] call CBA_fnc_addSetting; diff --git a/addons/frag/script_component.hpp b/addons/frag/script_component.hpp index 0215e9f4d74..8c2a614d3ce 100644 --- a/addons/frag/script_component.hpp +++ b/addons/frag/script_component.hpp @@ -2,8 +2,9 @@ #define COMPONENT_BEAUTIFIED Frag #include "\z\ace\addons\main\script_mod.hpp" -// #define DRAW_FRAG_INFO +// #define LOG_FRAG_INFO // #define DEBUG_MODE_FULL +// #define DEBUG_MODE_DRAW // #define DISABLE_COMPILE_CACHE // #define ENABLE_PERFORMANCE_COUNTERS @@ -17,4 +18,38 @@ #include "\z\ace\addons\main\script_macros.hpp" -#define ACE_TRACE_DRAW_INC 1 +// Mimimum hold-off time between frag events globally or per vehicle +#define ACE_FRAG_HOLDOFF 0.25 +#define ACE_FRAG_HOLDOFF_VEHICLE 1 +// Scaling for the min/max # of fragments since last frag event +#define ACE_FRAG_COUNT_MIN_TIME 0.1 +#define ACE_FRAG_COUNT_MIN 5 +#define ACE_FRAG_COUNT_MAX_TIME 1 +#define ACE_FRAG_COUNT_MAX 50 +// Default hitpoint targets +#define ACE_FRAG_HITPOINTS ["spine1", "spine1", "spine1", "spine2", "spine2", "spine2", "spine3", "spine3", "spine3", "pelvis", "pelvis", "pelvis", "head", "leftarm", "leftarmroll", "leftforearm", "rightarm", "rightarmroll", "rightforearm", "leftupleg", "leftuplegroll", "leftlegroll", "leftfoot", "rightupleg", "rightuplegroll", "rightleg", "rightlegroll", "rightfoot", "neck"] +// half of gravity approx 9.81/2 +#define ACE_FRAG_HALF_GRAVITY_APPROX 4.905 +// Lowest chance to hit of 0.5% +#define ACE_FRAG_MIN_FRAG_HIT_CHANCE 0.005 +#define ACE_FRAG_IMPERIC_VELOCITY_CONSTANT 0.8 +// Make frag hold-off time shortger for low frag +// value of 150/4/pi ~= 11.93662 +#define ACE_FRAG_LOW_FRAG_MOD_COUNT 11.93662 +#define ACE_FRAG_LOW_FRAG_COEFF 4 +#define ACE_FRAG_LOW_FRAG_HOLDOFF_REDUCTION 4 +// Max frag distance - correlates to a a fragCount of 20k & hit chance of 0.5% +#define ACE_FRAG_MAX_FRAG_RANGE 565 +// 4*pi/360 = 0.034906585 +#define ACE_FRAG_FRAGS_PER_ARC_CONSTANT 0.03491 + +// Spall values +#define ACE_FRAG_SPALL_HOLDOFF 0.25 +#define ACE_FRAG_SPALL_VELOCITY_INHERIT_COEFF 0.5 +// sqrt(2)/50 +#define ACE_FRAG_SPALL_CALIBER_COEF 0.02828427 +#define ACE_FRAG_SPALL_POWER_MIN 2 +#define ACE_FRAG_SPALL_POWER_TINY_MAX 5 +#define ACE_FRAG_SPALL_POWER_SMALL_MAX 8 +#define ACE_FRAG_SPALL_POWER_MEDIUM_MAX 11 +#define ACE_FRAG_SPALL_POWER_LARGE_MAX 15 diff --git a/addons/frag/stringtable.xml b/addons/frag/stringtable.xml index f88877448d8..72442335ef4 100644 --- a/addons/frag/stringtable.xml +++ b/addons/frag/stringtable.xml @@ -17,6 +17,18 @@ 破片模拟 模擬碎片 + + Fragmentation + + + Spalling + + + Explosion Reflections + + + Debug + Fragmentation Simulation Symulacja fragmentacji @@ -111,101 +123,52 @@ 啟用ACE模擬爆炸反射 Povolit ACE simulaci odrazu exploze - - Maximum Projectiles Tracked - Máximos proyectiles rastreados - Maks. liczba śledzonych pocisków - Maximalzahl der verfolgten Projektile - Maximální počet sledovaných projektilů - Máximo de projéteis rastreados - Nombre maximum de projectiles suivis - Maximum követett repeszek - Макс. количество отслеживаемых снарядов - Numero massimo di Proiettili Tracciati - 飛翔体最大追跡数 - 최대 발사체 추적수 - 最大破片粒子追踪数量 - 最大碎片/剝落粒子追蹤數量 - - - This setting controls the maximum amount of projectiles the fragmentation and spalling system will track at any given time. If more projectiles are fired, they will not be tracked. Lower this setting if you do not want FPS drops at high-count projectile scenarios ( >200 rounds in the air at once) - Este ajuste controla la cantidad máxima de proyectiles del sistema de fragmentación y astillamiento de los que se hará un seguimiento en cualquier momento dado. Si se disparan más proyectiles, no serán rastreados. Baja esta opción si no deseas una bajada de FPS en escenarios con muchos proyectiles (>200 proyectiles en el aire a la vez) - To ustawienie kontroluje maksymalną ilość pocisków, jakie fragmentacja i odpryski symulują w danym momencie. Jeżeli więcej pocisków będzie wystrzelonych, wtedy nie będą one śledzone. Zmniejsz tą opcję jeżeli nie chcesz odczuwać spadków FPS podczas ciężkiej wymiany ognia (więcej niż 200 pocisków w powietrzu na raz). - Diese Einstellung steuert die maximale Anzahl an Projektilen, die das Splitter- und Explosionssystem gleichzeitig verfolgen wird. Wenn mehr Projektile abgefeuert werden, werden sie nicht verfolgt werden. Diese Einstellung zu verringern, kann FPS-Einbrüche bei Szenarien mit vielen Projektilen verhindern (>200 Objekte gleichzeitig in der Luft) - Toto nastavení kontroluje maximální množství projektilů z fragmentace a úlomků, která jsou sledována v dané době. Pokud je vystřeleno více projektilů, tak nebudou sledovány. Snižte toto nastavení pokud si nepřejete propady FPS v situacích, kde je velké množství projektilů ( >200 nábojů najednou ve vzduchu) - Esta definição controla a quantidade máxima de projéteis que o sistema de fragmentação e estilhaçamento irá acompanhar em qualquer momento. Se mais projéteis são disparados, eles não serão rastreados. Diminua essa configuração se você não quiser que o FPS caia em cenários com alta contagem de projéteis (> 200 projéteis no ar ao mesmo tempo) - Ce paramètre contrôle le nombre maximum de projectiles et d'éclats résultant de la fragmentation, que le système peut suivre à chaque instant.\nSi plus de projectiles sont générés, ils ne seront pas pris en compte. Baissez ce réglage si vous ne voulez pas de chute de FPS en cas de nombre important de projectiles (>200 éclats en même temps). - Ez a beállítás szabályozza a repeszeződés és pattogzás által kilőtt objektumok követett számát. Ha több ez a szám, ezek az objektumok nem lesznek követve. Csökkentsd ezt a beállítást, ha nem akarsz lassulásokat magas-törmelékmennyiségű helyzetekben (200+ repesz a levegőben egyszerre) - Эта настройка контролирует максимальное количество снарядов, которок отслеживает система осколков и обломков в каждый момент времени. /nСнаряды, выстреленные сверх этого числа, отслеживаться не будут. Уменьшите это значение, если вы не хотите падения FPS при большом количестве снарядов в одной перестрелке (> 200 одновременно летящих снарядов) - Questo parametro controlla il numero massimo di proiettili che la frammentazione e il sistema di spalling tracciano in ogni momento. Se vengono sparati ulteriori proiettili, non verranno tracciati. Abbassa questo parametro se non vuoi cali di FPS in scenari con molti proiettili (>200 proiettili in aria contemporaneamente) - この設定では、断片化および剥離システムが常に追跡する飛翔体の最大量を制御します。 この値より多くの飛翔体が発射された場合、それらは追跡されません。 弾数が多いシナリオでFPSを低下させたくない場合は、この設定を下げてください。 (一度に200発以上が空中に発射されます) - 이 설정은 탄환파편 및 파편 시스템으로 인해 생긴 발사체의 수를 결정합니다. 만약 더 많은 발사체가 나올 경우 정해진 수 이외에는 추적하지 않습니다. 이 설정을 낮춤으로써 파편이 많은 시나리오를 실행할때 더욱 원활히 진행할 수 있습니다 (한 번에 200개 이하) - 设定在指定时间内,系统最大可追踪的破片粒子数量。如有更多的碎片在这之后产生,这些粒子将不会被追踪。如果你想要维持好的帧数,此设定勿调的过高。( >一次200颗粒子) - 設定在指定時間內,系統最大可追蹤的碎片/剝落粒子數量。如有更多的碎片在這之後產生,這些粒子將不會被追蹤。如果你想要維持好的幀數,此設定勿調的過高。( >一次200顆粒子) - - - Maximum Projectiles Per Frame - Máximos proyectiles por cuadro - Maximale Anzahl an Projektilen pro Frame - Maks. liczba pocisków na klatkę - Maximální počet projektilů za jeden snímek - Projéteis máximos por quadro - Nombre maximal de projectiles par image - Maximum repesz/képkocka - Макс. количество снарядов за кадр - Numero massimo di proiettili per Frame - フレームごとの飛翔体最大数 - 프레임 당 최대 발사체 수 - 每帧最大破片粒子数量 - 每一幀數(FPS)最大碎片/剝落粒子數量 - - - The number of spall track calculations to perform in any given frame. This helps spread the FPS impact of tracking spall rounds across multiple frames, limiting its impact even further. - Ilość obliczeń wykonywanych przez symulację odprysków w danej klatce. Ta opcja pomaga rozprzestrzenić obliczenia odprysków na więcej klatek, zmniejszając spadek FPS jeszcze bardziej. - Gibt die Anzahl der Explosionverfolgungsberechnungen an, die gleichzeitig ausgeführt werden. Das kann dabei helfen den FPS-Einfluss abzuschwächen, wenn Teile über mehrere Frames hinweg verfolgt werden. - El número de cálculos de esquirlas que se hará en cualquier cuadro. Esto ayuda a dispersar el impacto en FPS del seguimiento de esquirlas de balas a través de múltiples cuadros, lo que limita aún más su impacto. - Počet úlomků v daném snímku. Toto pomáhá rozšířit FPS dopad sledovaného úlomku napříč více snímky, omezuje jeho vliv ještě více. - O número de cálculos por estilhaço rastreado para executar em qualquer quadro. Isso ajuda a distribuir o impacto no FPS do rastreamento de estilhaço em vários quadros, o que limita o seu impacto ainda mais. - Le nombre de calculs de suivi à effectuer pour chaque image. Cela aide à répartir l'impact des calculs sur plusieurs images, limitant ainsi encore davantage l'impact sur les FPS. - A lepattogzási útvonalak számításának darabjai képkockánként. Ez eloszlatja az FPS-megszakadást több képkockára, ezzel csökkentve a súlyosságát. - Число обрабатываемых осколков за кадр. Это позволяет распределить нагрузку по отслеживанию осколков между несколькими кадрами, чтобы предотвратить падение FPS. - Il numero di calcoli per tracciamento di spalling ad ogni frame. Questo aiuta a distribuire l'impatto del tracciamento dello spalling su più frame, riducendolo ulteriormente. - 任意のフレームごとに追跡される剥離飛翔体の数。剥離による飛翔体を追跡することによるFPSへの影響を複数フレームに分散させ抑えることが出来ます。 - 가능한 프레임마다 파편을 추적 및 계산합니다. 여러 프레임에 걸쳐 파편난 발사체를 추적하여 FPS에 도움을 줍니다. 이를 제한함으로써 더욱 큰 효과를 볼 수 있습니다. - 设定在每一帧数内,系统最大可追踪的破片粒子数量。此设定可有效帮助系统减低计算压力。 - 設定在每一幀數內,系統最大可追蹤的碎片/剝落粒子數量。此設定可有效幫助系統減低計算壓力 + + Simulation Mode + + + Sets the fragmentation simulation mode, which can be targeted, random or both. + + + Unit targeted fragmentation + + + Random fragmentation + + + Random and targeted fragmentation + + + Disable Fragment Misses + + + Create a fragment for each unit that a fragment could be shot towards. This setting has no effect when "Random fragmentation" is selected. + + + Spalling Intensity + + + Modifier to increase or decrease the number and intensity of spalling events. Increasing this value may cause performance degradation. - (SP Only) Frag/Spall Debug Tracing - (Solo SP) Seguimiento de depuración de Fragmentación/Astillamiento - (Tylko SP) Wizualny debug odł./odpr. - (Pouze SP) Debug sledování Frag/Úlomků - (nur SP) Splitter-/Explosions-Debug-Verfolgung - (Somente SP) Depuração de fragmentação e estilhaços traçantes - (SP uniquement) Fragmentation/éclat debug - (Csak SP) Repesz/Pattogzás debug követés - (Только для одиночной игры) Отслеживаение/отладка осколков - (Solo SP) Debug Tracciamento Frag/Spall - (SP のみ) 破片/剥離のデバッグ用表示 - (싱글플레이 전용) 탄환파편/파편 디버그 추적화 - (仅单人)追踪显示破片粒子 - (僅在單人模式) 碎片/剝落除錯追蹤 + Frag/Spall Debug Tracing + Splitter-/Explosions-Debug-Verfolgung - (SP Only) Requires a mission/editor restart. Enables visual tracing of fragmentation and spalling rounds in SP game mode only. - (Solo SP) Requiere un reinicio misión/editor. Permite el seguimiento visual de la fragmentación y astillamientos de los proyectiles en modo SP. - (Tylko SP) Wymaga restartu misji/edytora. Aktywuje wizualne śledzenie odłamków oraz odprysków w trybie gry Single Player. - (nur SP) Splitter-/Explosions-Debugging - (Pouze SP) Vyžaduje restart mise/editoru. Aktivuje vizuální stopování fragmentace a úlomů pouze v režimu jednoho hráče. - (Somente SP) Requer um reinício de missão / editor. Habilita o rastreamento visual de projéteis de fragmentação e estilhaçamento apenas no modo de jogo SP. - (SP seulement) Requiert un redémarrage de mission ou de l'éditeur. Active les traceurs visuels de fragmentation et d'éclats en mode solo seulement. - (Csak SP) Küldetés/Editor újraindítás szükséges. Engedélyezi a repeszek és pattogzó lövedékek vizuális nyomkövetését, csak egyjátékos módok alatt. - (Только для одиночной игры) Требует перезапуска миссии/редактора. Включает визуальные следы от осколков и обломков в режиме одиночной игры. - (Solo SP) Richiede un restart editor/missione. Abilita il tracciamento visivo di schegge da frammentazione/spalling in modalità Giocatore Singolo. - (SP のみ) ミッションとエディタの再起動が必要です。有効化すると、シングルプレイでのみ破片と剥離の飛翔体が見えるようになります。 - (仅单人)激活后,只有在单人模式下才可观察到破片粒子的移动轨迹。 - (僅在單人模式) 讓你在單人模式下可觀察到碎片/剝落粒子的移動軌跡 - (SP 전용) 임무 / 편집자가 다시 시작해야합니다. SP 게임 모드에서만 파편화 및 탄환파편의 시각적 추적을 가능하게 합니다. + Enables visual tracing of fragmentation and spalling rounds. + Splitter-/Explosions-Debugging + + + Draw Event Spheres + + + Draw color coded spheres at any event for tracked rounds. + + + Draw Hitboxes + + + Draw hitboxes on objects that were targeted. diff --git a/docs/wiki/framework/frag-framework.md b/docs/wiki/framework/frag-framework.md index d05f5068f69..92a2e3025c3 100644 --- a/docs/wiki/framework/frag-framework.md +++ b/docs/wiki/framework/frag-framework.md @@ -14,7 +14,7 @@ version: ## 1. Overview -The fragmentation system in ACE3 is a significant improvement over the fragmentation system in ACE2. Previously the system relied on fuzzy math from the values of `indirectHit` and `indirectHitRange` in `CfgAmmo` to calculate roughly the velocity and range of fragmentation. This had some serious drawbacks, especially in the case of smaller explosives such as hand grenades and 40mm grenades where casualty production was lower than desired. +The fragmentation system in ACE3 is a significant improvement over the fragmentation system in ACE2. Previously the system relied on fuzzy math from the values of `indirectHit` and `indirectHitRange` in `CfgAmmo` to calculate roughly the velocity and range of fragmentation. This had some serious drawbacks, especially in the case of smaller explosives such as hand grenades and 40mm grenades where lethality was lower than desired. In ACE3 the system has moved away from what "feels" right to actual explosive engineering equations, primarily the [Gurney equations](http://en.wikipedia.org/wiki/Gurney_equations). This allows us to get close to the actual fragmentation velocities that would be produced by an explosive configuration similar to type of ammo we are simulating. @@ -26,7 +26,8 @@ The system for the end-developer is easy to use, and only requires minimal resea ```cpp class CfgAmmo { class MyGrenade { - ace_frag_enabled = 1; // Enable fragmentation (0-disabled, 1-enabled) + ace_frag_enabled = 1; // Deprecated + ace_frag_fragCount = 1200; // Approximate number of fragments the explosive makes - information below ace_frag_metal = 210; // Amount of metal being fragmented (grams) - information below ace_frag_charge = 185; // Amount of explosive filler (grams) - information below ace_frag_gurney_c = 2843; // Gurney velocity constant for explosive type - information below @@ -38,27 +39,37 @@ class CfgAmmo { }; ``` -### 1.1 Metal amount +### 2.1 Fragment count + +`ace_frag_fragCount` + +The maximum number of fragments that the real munition would create. Frag count affects the chance of being hit by a fragment and the maximum range in which a fragment may hit a unit. Frag count may be found online, inferred from similar munitions, or derived based on desired hit distance. For a desired distance, the frag count ($$N_{frag}$$) is given by, +$$N_{frag} = 4\pi\cdot P_{hit,min}\cdot distance_{max}^2 = 0.02\pi\cdot distance_{max}^2$$ +where $$P_{hit,min}$$ is 0.5%, the minimum chance to hit that is considered when generating fragments. Therefore, calculating the frag count for a frag grenade with a wounding radius of 15 meters, calculating for a distance of 80 meters may be desired instead. + +Dimensionless value, count of number of fragments. + +### 2.2 Metal amount `ace_frag_metal` -Amount of metal being fragmented (generally taken as the entire weight of the warhead, though in some cases you might want to only include the fragmentation jacket or body. +The amount of metal being fragmented. Generally taken as the entire weight of the warhead, though in some cases you might want to only include the fragmentation jacket or body. -Dimensionless value, as long as same unit as `ace_frag_charge` (for example `kg/kg` or `g/g` or `lbs/lbs`). +As long as the units match `ace_frag_charge`, the total mass of fragmenting metal given in any unit of mass (i.e., both use `kg`, `g`, or `lbs`). -### 1.2 Explosives filler amount +### 2.3 Explosives filler amount `ace_frag_charge` -Amount of explosive filler in the warhead. `ace_frag_metal` and `ace_frag_charge` are dimensionless values, as long as they are both in the same unit (for example kg/kg g/g lbs/lbs). +The mass of explosive filler in the warhead. This may include any detonation/ignition charges, but usually such charges are relatively small. -Dimensionless value, as long as same unit as `ace_frag_metal` (for example `kg/kg` or `g/g` or `lbs/lbs`). +As long as the units match `ace_frag_metal`, the total mass of explosive filler given in any unit of mass (i.e., both use `kg`, `g`, or `lbs`). -### 1.3 Gurney velocity constant +### 2.4 Gurney velocity constant `ace_frag_gurney_c` -Gurney constant for explosive force. You can find a list of common explosive types below. If you can not find it here, or want more accurate numbers, just google the type of explosive and Gurney constant and you can find substantial information. This is **not** the detonation velocity of the explosive, do not confuse them! +The Gurney constant for explosive force. You can find a list of common explosive types below. If you can not find it here, or want more accurate numbers, just google the type of explosive and Gurney constant and you can find substantial information. This is **not** the detonation velocity of the explosive, do not confuse them†! | Type | Speed | | --------------- | -------- | @@ -77,11 +88,13 @@ Gurney constant for explosive force. You can find a list of common explosive typ | TNT | 2440 m/s | | Tritonal | 2320 m/s | -### 1.4 Gurney shape factor +†A rule of thumb from literature is that the Gurney constant is given as 0.338 times the detonation velocity. + +### 2.5 Gurney shape factor `ace_frag_gurney_k` -Shape factor for the explosive configuration. You should choose it based on the general configuration of explosives/metal in the warhead. Most grenades for example are a sphere. Artillery and aircraft bombs are a cylinder. Mines generally a flat plate. Below is a list of the three common shapes and their factors. +The shape factor for the explosive configuration. You should choose it based on the general configuration of explosives/metal in the warhead. Most grenades for example are a sphere. Artillery and aircraft bombs are a cylinder. Mines generally a flat plate. Below is a list of the three common shapes and their factors. | Shape | Factor | | -------- | ------ | @@ -91,7 +104,7 @@ Shape factor for the explosive configuration. You should choose it based on the There are other configurations but these are the most common. If you are interested in others check out the wikipedia link given above. Most of these will not correctly function in ACE3 though due to additional variables for the equation. -### 1.5 Fragments type +### 2.6 Fragments type `ace_frag_classes[]` @@ -110,16 +123,16 @@ There are different types of fragmentation fragments to choose from, and they ca | ACE_frag_huge | | ACE_frag_huge_HD | -The tinier the piece of fragmentation the shorter the distance of travel. The `_HD` variants are all even higher drag versions. Grenades generally should use the `_HD` variants. Experimentation here is important. +Tinier fragments do less damage, and generally correlate to lower mass fragments. The `_HD` variants are all higher drag versions. Higher drag version are useful for fragments that are irregular or would not fly very far. Experimentation here is important. -### 1.6 Ignore fragmentation +### 2.7 Ignore fragmentation `ace_frag_skip` -Setting this to `1` will skip fragmentation for ammo of this type. This is useful for things that might cause high network load, such as FFAR rockets, or possibly even 40mm grenades from AGLs. Experimentation under network conditions is required. +When `1`, the ammunition type will not produce fragments. `ace_frag_skip` does not stop submunitions of the ammo type from producing fragments. `ace_frag_skip` may be helpful for ammunition types that might cause high network load or for explosives that do not produce fragments. Experimentation under network conditions may be required. `ace_frag_skip` takes a higher priority than `ace_frag_force`. -### 1.7 Force fragmentation +### 2.8 Force fragmentation `ace_frag_force` -Settings this to `1` will force the fragmentation system to use frag on this ammo, ignoring internal qualifications based on hit values. +When `1`, the ammunition type will fragment, ignoring internal hit value-based qualifications. `ace_frag_force` takes a lower priority than `ace_frag_skip`.