--[[

    09 March 2025 (Stevey666) - 3.0
      - Added ordinance protection gives a few options - stop the additional larger_explosion that tends to blow up your own bombs if theyre dropped at the same place if its within x m
	  - Additional ordnance protection option that will cause a snap to ground larger_explosion if its within x meters of a recent larger explosion and within x seconds (can set in options)
      - Added vehicle scanning around a weapon to allow for..
	  - Cook offs - you can set vehicles that will cook off i.e ammo trucks, number of explosions, debris explosions, power adjustable
	  - Fuel/Tanker explosion and flames - when a fuel tanker blows it will through up a big flame - adjustable in the scripts
	  - Added section for vehicles for the above
	  - Added radio commands for everything
	  - Added in cluster munitions changes (note: barely tested, its not particularly accurate or that useful at this point so leaving disabled)
	  - Potential bug - testing, stacking too many units together may cause a MIST error if you're using mist
	  
	  - Setting this as 3.0 as I'd like to be responsive to requests, updates etc - creating a new fork to track this
	

    10 Feb 2025 (Stevey666) - 2.0.7
      - Fixed AGM 154/Adjusted weapons
      - Added overall damage scaling 
      - Added modifier for shaped charges (i.e. Mavericks), adjusted weapon list accordingly
      - Adjusted blast radius and damage calculations, created option for dynamic blast radius
      - Adjusted cascading explosions, added additional "cascade_scaling" modifier and cascade explode threshold modifier. Units wont explode on initial impact unless health drops under threshold
      - Added always_cascade_explode option so you can set it to the old ways of making everything in the blast wave go kaboom
      - Added in game radio commands to change the new options ingame without having to reload everything in mission editor to test it out

    12 November 2024 (by JGi | Quéton 1-1)
    - Tweak down radius 100>90 (Thanks Arhibeau)
    - Tweak down some values

    20 January 2024 (by JGi | Quéton 1-1)
    - Added missing weapons to explTable
    - Sort weapons in explTable by type
    - Added aircraft type in log when missing

    03 May 2023 (KERV)
      Correction AGM 154 (https://forum.dcs.world/topic/289290-splash-damage-20-script-make-explosions-better/page/5/#comment-5207760)
  
    06 March 2023 (Kerv)
    - Add some data for new ammunition

    16 April 2022
      spencershepard (GRIMM):
      - Added new/missing weapons to explTable
      - Added new option rocket_multiplier

    31 December 2021
      spencershepard (GRIMM):
      - Added many new weapons
      - Added filter for weapons.shells events
      - Fixed mission weapon message option
      - Changed default for damage_model option
  
    21 December 2021
      spencershepard (GRIMM):
      SPLASH DAMAGE 2.0:
      - Added blast wave effect to add timed and scaled secondary explosions on top of game objects
      - Object geometry within blast wave changes damage intensity
      - Damage boost for structures since they are hard to kill, even if very close to large explosions
      - Increased some rocket values in explTable
      - Missing weapons from explTable will display message to user and log to DCS.log so that we can add what's missing
      - Damage model for ground units that will disable their weapons and ability to move with partial damage before they are killed
      - Added options table to allow easy adjustments before release
      - General refactoring and restructure

    28 October 2020
      FrozenDroid: 
      - Uncommented error logging, actually made it an error log which shows a message box on error.
      - Fixed the too restrictive weapon filter (took out the HE warhead requirement)

    2 October 2020
      FrozenDroid:
      - Added error handling to all event handler and scheduled functions. Lua script errors can no longer bring the server down.
      - Added some extra checks to which weapons to handle, make sure they actually have a warhead (how come S-8KOM's don't have a warhead field...?)
--]]

----[[ ##### SCRIPT CONFIGURATION ##### ]]----
splash_damage_options = {
    --debug options
    ["game_messages"] = false, --enable some messages on screen
    ["debug"] = false,  --enable debugging messages 
    ["weapon_missing_message"] = false, --false disables messages alerting you to weapons missing from the explTable
    
	["track_pre_explosion_debug"] = false, --Toggle to enable/disable pre-explosion tracking debugging
	
    ["enable_radio_menu"] = false, --enables the in-game radio menu for modifying settings
    
    ["static_damage_boost"] = 2000, --apply extra damage to Unit.Category.STRUCTUREs with wave explosions
    ["wave_explosions"] = true, --secondary explosions on top of game objects, radiating outward from the impact point and scaled based on size of object and distance from weapon impact point
    ["larger_explosions"] = true, --secondary explosions on top of weapon impact points, dictated by the values in the explTable
    ["damage_model"] = true, --allow blast wave to affect ground unit movement and weapons
    ["blast_search_radius"] = 90, --this is the max size of any blast wave radius, since we will only find objects within this zone
    ["cascade_damage_threshold"] = 0.1, --if the calculated blast damage doesn't exceed this value, there will be no secondary explosion damage on the unit. If this value is too small, the appearance of explosions far outside of an expected radius looks incorrect.
    ["blast_stun"] = false, --not implemented
    ["unit_disabled_health"] = 30, --if health is below this value after our explosions, disable its movement 
    ["unit_cant_fire_health"] = 40, --if health is below this value after our explosions, set ROE to HOLD to simulate damage weapon systems
    ["infantry_cant_fire_health"] = 60,  --if health is below this value after our explosions, set ROE to HOLD to simulate severe injury
	
    ["rocket_multiplier"] = 1.3, --multiplied by the explTable value for rockets
    ["overall_scaling"] = 1,    --overall scaling for explosive power
    
    ["apply_shaped_charge_effects"] = true, --apply reduction in blastwave etc for shaped charge munitions
    ["shaped_charge_multiplier"] = 0.2,  --multiplier that reduces blast radius and explosion power for shaped charge munitions.
    
    ["use_dynamic_blast_radius"] = true,   --if true, blast radius is calculated from explosion power; if false, blast_search_radius (90) is used
    ["dynamic_blast_radius_modifier"] = 2,  --multiplier for the blast radius
    
    ["cascade_scaling"] = 1,    --multiplier for secondary (cascade) blast damage, 1 damage fades out too soon, 3 damage seems a good balance
    ["cascade_explode_threshold"] = 60,   --only trigger cascade explosion if the unit's current health is <= this percent of its maximum, setting can help blow nearby jeeps but not tanks
    ["always_cascade_explode"] = true, --switch if you want everything to explode like with the original script
    
	
    --track_pre_explosion/enable_cargo_effects should both be the same value
    ["track_pre_explosion"] = true, --Toggle to enable/disable pre-explosion tracking
    ["enable_cargo_effects"] = true, --Toggle for enabling/disabling cargo explosions and cook-offs  
    ["cargo_damage_threshold"] = 60, --Health % below which cargo explodes (0 = destroyed only)
    ["debris_effects"] = true, --Enable debris from cargo cook-offs
    ["debris_power"] = 1, --Power of each debris explosion
    ["debris_count_min"] = 6, --Minimum debris pieces per cook-off
    ["debris_count_max"] = 12, --Maximum debris pieces per cook-off
    ["debris_max_distance"] = 10, --Max distance debris can travel (meters), the min distance from the vehicle will be 10% of this
	
    ["ordnance_protection"] = true, --Toggle ordinance protection features
    ["ordnance_protection_radius"] = 10, --Distance in meters to protect nearby bombs
    ["detect_ordnance_destruction"] = true, --Toggle detection of ordnance destroyed by large explosions
    ["snap_to_ground_if_destroyed_by_large_explosion"] = true, --If the ordnance protection fails or is disabled we can snap larger_explosions to the ground (if enabled - power as set in weapon list) - so an explosion still does hit the ground
    ["recent_large_explosion_snap"] = true, --enable looking for a recent large_explosion generated by the script
    ["recent_large_explosion_range"] = 200, --range its looking for in meters for a recent large_explosion generated by the script
    ["recent_large_explosion_time"] = 4, --in seconds how long ago there was a recent large_explosion generated by the script

    -- Cluster bomb settings
    ["cluster_enabled"] = true,
    ["cluster_base_length"] = 150,           -- Base forward spread (meters)
    ["cluster_base_width"] = 200,            -- Base lateral spread (meters)
    ["cluster_max_length"] = 300,            -- Max forward spread (meters)
    ["cluster_max_width"] = 400,             -- Max lateral spread (meters)
    ["cluster_min_length"] = 100,            -- Min forward spread
    ["cluster_min_width"] = 150,             -- Min lateral spread
    ["cluster_bomblet_reductionmodifier"] = true, -- Use equation to reduce number of bomblets (to make it look better)
    ["cluster_bomblet_damage_modifier"] = 1,  -- Adjustable global modifier for bomblet explosive power
}

local script_enable = 1
refreshRate = 0.1
----[[ ##### End of SCRIPT CONFIGURATION ##### ]]----

--Helper function: Trim whitespace.
local function trim(s)
    return s:match("^%s*(.-)%s*$")
end
 
cargoUnits = {

--[[
flamesize:

 1 = small smoke and fire
 2 = medium smoke and fire
 3 = large smoke and fire
 4 = huge smoke and fire
 5 = small smoke
 6 = medium smoke 
 7 = large smoke
 8 = huge smoke 
]]--	

    --1) M92 R11 Volvo driveable (Fuel Truck Tanker)
    ["r11_volvo_drivable"] = { 
        cargoExplosion = true,
        cargoExplosionMult = 2.0,
		cargoExplosionPower = 200,
        cargoCookOff = false,
        cookOffCount = 0,
        cookOffPower = 0,
        cookOffDuration = 0,
        cookOffRandomTiming = false,
        cookOffPowerRandom = 50,
        isTanker = true,
        flameSize = 3,
        flameDuration = 5,
    },

    --2) Refueler ATMZ-5
    ["ATMZ-5"] = {
        cargoExplosion = true,
        cargoExplosionMult = 2.0,
		cargoExplosionPower = 200,
        cargoCookOff = false,
        cookOffCount = 0,
        cookOffPower = 0,
        cookOffDuration = 0,
        cookOffRandomTiming = false,
        cookOffPowerRandom = 50,
        isTanker = true,
        flameSize = 3,
        flameDuration = 5,
    },

    --3) Refueler ATZ-10
    ["ATZ-10"] = {
        cargoExplosion = true,
        cargoExplosionMult = 1.8,
		cargoExplosionPower = 200,
        cargoCookOff = false,
        cookOffCount = 0,
        cookOffPower = 0,
        cookOffDuration = 0,
        cookOffRandomTiming = false,
        cookOffPowerRandom = 50,
        isTanker = true,
        flameSize = 3,
        flameDuration = 5,
    },

    --4) Refueler ATZ-5 
    ["ATZ-5"] = {
        cargoExplosion = true,
        cargoExplosionMult = 1.8,
		cargoExplosionPower = 200,
        cargoCookOff = false,
        cookOffCount = 0,
        cookOffPower = 0,
        cookOffDuration = 0,
        cookOffRandomTiming = false,
        cookOffPowerRandom = 50,
        isTanker = true,
        flameSize = 3,
        flameDuration = 5,
    },

    --5) Refueler M978 HEMTT (Fuel truck tanker)
    ["M978 HEMTT Tanker"] = {
        cargoExplosion = true,
        cargoExplosionMult = 2.0,
		cargoExplosionPower = 200,
        cargoCookOff = false,
        cookOffCount = 0,
        cookOffPower = 0,
        cookOffDuration = 0,
        cookOffRandomTiming = false,
        cookOffPowerRandom = 50,
        isTanker = true,
        flameSize = 3,
        flameDuration = 5,
    },

    --##### AMMO CARRIERS #####
    ["GAZ-66"] = {
        cargoExplosion = true,
        cargoExplosionMult = 1,
		cargoExplosionPower = 200,
        cargoCookOff = true,
        cookOffCount = 4,
        cookOffPower = 1,
        cookOffDuration = 20,
        cookOffRandomTiming = true,
        cookOffPowerRandom = 50,
        isTanker = false,
        flameSize = 1,
        flameDuration = 30,
    },

    ["Ural-4320"] = {
        cargoExplosion = true,
        cargoExplosionMult = 1,
		cargoExplosionPower = 200,
        cargoCookOff = true,
        cookOffCount = 4,
        cookOffPower = 1,
        cookOffDuration = 20,
        cookOffRandomTiming = true,
        cookOffPowerRandom = 50,
        isTanker = false,
        flameSize = 1,
        flameDuration = 30,
    },

    ["ZIL-135"] = {
        cargoExplosion = true,
        cargoExplosionMult = 1,
		cargoExplosionPower = 200,
        cargoCookOff = true,
        cookOffCount = 5,
        cookOffPower = 1,
        cookOffDuration = 20,
        cookOffRandomTiming = true,
        cookOffPowerRandom = 50,
        isTanker = false,
        flameSize = 1,
        flameDuration = 30,
    },
}

--Weapon Explosive Table
explTable = {
    --*** WWII BOMBS ***
    ["British_GP_250LB_Bomb_Mk1"] = { explosive = 100, shaped_charge = false },
    ["British_GP_250LB_Bomb_Mk4"] = { explosive = 100, shaped_charge = false },
    ["British_GP_250LB_Bomb_Mk5"] = { explosive = 100, shaped_charge = false },
    ["British_GP_500LB_Bomb_Mk1"] = { explosive = 213, shaped_charge = false },
    ["British_GP_500LB_Bomb_Mk4"] = { explosive = 213, shaped_charge = false },
    ["British_GP_500LB_Bomb_Mk4_Short"] = { explosive = 213, shaped_charge = false },
    ["British_GP_500LB_Bomb_Mk5"] = { explosive = 213, shaped_charge = false },
    ["British_MC_250LB_Bomb_Mk1"] = { explosive = 100, shaped_charge = false },
    ["British_MC_250LB_Bomb_Mk2"] = { explosive = 100, shaped_charge = false },
    ["British_MC_500LB_Bomb_Mk1_Short"] = { explosive = 213, shaped_charge = false },
    ["British_MC_500LB_Bomb_Mk2"] = { explosive = 213, shaped_charge = false },
    ["British_SAP_250LB_Bomb_Mk5"] = { explosive = 100, shaped_charge = false },
    ["British_SAP_500LB_Bomb_Mk5"] = { explosive = 213, shaped_charge = false },
    ["British_AP_25LBNo1_3INCHNo1"] = { explosive = 4, shaped_charge = false },
    ["British_HE_60LBSAPNo2_3INCHNo1"] = { explosive = 4, shaped_charge = false },
    ["British_HE_60LBFNo1_3INCHNo1"] = { explosive = 4, shaped_charge = false },
  
    ["SC_50"] = { explosive = 20, shaped_charge = false },
    ["ER_4_SC50"] = { explosive = 20, shaped_charge = false },
    ["SC_250_T1_L2"] = { explosive = 100, shaped_charge = false },
    ["SC_501_SC250"] = { explosive = 100, shaped_charge = false },
    ["Schloss500XIIC1_SC_250_T3_J"] = { explosive = 100, shaped_charge = false },
    ["SC_501_SC500"] = { explosive = 213, shaped_charge = false },
    ["SC_500_L2"] = { explosive = 213, shaped_charge = false },
    ["SD_250_Stg"] = { explosive = 100, shaped_charge = false },
    ["SD_500_A"] = { explosive = 213, shaped_charge = false },
  
    --*** WWII CBU ***
    ["AB_250_2_SD_2"] = { explosive = 100, shaped_charge = false },
    ["AB_250_2_SD_10A"] = { explosive = 100, shaped_charge = false },
    ["AB_500_1_SD_10A"] = { explosive = 213, shaped_charge = false },
  
    --*** WWII ROCKETS ***
    ["3xM8_ROCKETS_IN_TUBES"] = { explosive = 4, shaped_charge = false },
    ["WGr21"] = { explosive = 4, shaped_charge = false },
  
    --*** UNGUIDED BOMBS (UGB) ***
    ["M_117"] = { explosive = 201, shaped_charge = false },
    ["AN_M30A1"] = { explosive = 45, shaped_charge = false },
    ["AN_M57"] = { explosive = 100, shaped_charge = false },
    ["AN_M64"] = { explosive = 121, shaped_charge = false },
    ["AN_M65"] = { explosive = 400, shaped_charge = false },
    ["AN_M66"] = { explosive = 800, shaped_charge = false },
    ["AN-M66A2"] = { explosive = 536, shaped_charge = false },
    ["AN-M81"] = { explosive = 100, shaped_charge = false },
    ["AN-M88"] = { explosive = 100, shaped_charge = false },
  
    ["Mk_81"] = { explosive = 60, shaped_charge = false },
    ["MK-81SE"] = { explosive = 60, shaped_charge = false },
    ["Mk_82"] = { explosive = 100, shaped_charge = false },
    ["MK_82AIR"] = { explosive = 100, shaped_charge = false },
    ["MK_82SNAKEYE"] = { explosive = 100, shaped_charge = false },
    ["Mk_83"] = { explosive = 274, shaped_charge = false },
    ["Mk_84"] = { explosive = 582, shaped_charge = false },
  
    ["HEBOMB"] = { explosive = 40, shaped_charge = false },
    ["HEBOMBD"] = { explosive = 40, shaped_charge = false },
  
    ["SAMP125LD"] = { explosive = 60, shaped_charge = false },
    ["SAMP250LD"] = { explosive = 118, shaped_charge = false },
    ["SAMP250HD"] = { explosive = 118, shaped_charge = false },
    ["SAMP400LD"] = { explosive = 274, shaped_charge = false },
    ["SAMP400HD"] = { explosive = 274, shaped_charge = false },
  
    ["BR_250"] = { explosive = 100, shaped_charge = false },
    ["BR_500"] = { explosive = 100, shaped_charge = false },
  
    ["FAB_100"] = { explosive = 45, shaped_charge = false },
    ["FAB_250"] = { explosive = 118, shaped_charge = false },
    ["FAB_250M54TU"] = { explosive = 118, shaped_charge = false },
    ["FAB-250-M62"] = { explosive = 118, shaped_charge = false },
    ["FAB_500"] = { explosive = 213, shaped_charge = false },
    ["FAB_1500"] = { explosive = 675, shaped_charge = false },
  
    --*** UNGUIDED BOMBS WITH PENETRATOR / ANTI-RUNWAY ***
    ["Durandal"] = { explosive = 64, shaped_charge = false },
    ["BLU107B_DURANDAL"] = { explosive = 64, shaped_charge = false },
    ["BAP_100"] = { explosive = 32, shaped_charge = false },
    ["BAP-100"] = { explosive = 32, shaped_charge = false },
    ["BAT-120"] = { explosive = 32, shaped_charge = false },
    ["TYPE-200A"] = { explosive = 107, shaped_charge = false },
    ["BetAB_500"] = { explosive = 98, shaped_charge = false },
    ["BetAB_500ShP"] = { explosive = 107, shaped_charge = false },
    
    --*** GUIDED BOMBS (GBU) ***
    ["GBU_10"] = { explosive = 582, shaped_charge = false },
    ["GBU_12"] = { explosive = 100, shaped_charge = false },
    ["GBU_16"] = { explosive = 274, shaped_charge = false },
    ["GBU_24"] = { explosive = 582, shaped_charge = false },
    ["KAB_1500Kr"] = { explosive = 675, shaped_charge = false },
    ["KAB_500Kr"] = { explosive = 213, shaped_charge = false },
    ["KAB_500"] = { explosive = 213, shaped_charge = false },
  
    --*** CLUSTER BOMBS (CBU) ***
	--I don't have most of these so can't test them with debug on
    ["CBU_99"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 247, submunition_explosive = 2, submunition_name = "Mk 118" }, --Mk 20 Rockeye variant, confirmed 247 Mk 118 bomblets
    ["ROCKEYE"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 247, submunition_explosive = 2, submunition_name = "Mk 118" }, --Mk 20 Rockeye, confirmed 247 Mk 118 bomblets
    ["BLU_3B_GROUP"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 19, submunition_explosive = 0.2, submunition_name = "BLU_3B" }, --Not in datamine, possibly custom or outdated; submunition name guessed
    ["MK77mod0-WPN"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 132, submunition_explosive = 0.1, submunition_name = "BLU_1B" }, --Not in datamine, possibly custom; submunition name guessed
    ["MK77mod1-WPN"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 132, submunition_explosive = 0.1, submunition_name = "BLU_1B" }, --Not in datamine, possibly custom; submunition name guessed
    ["CBU_87"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 202, submunition_explosive = 0.5, submunition_name = "BLU_97B" }, --Confirmed 202 BLU-97/B bomblets
    ["CBU_103"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 202, submunition_explosive = 0.5, submunition_name = "BLU_97B" }, --WCMD variant of CBU-87, confirmed 202 BLU-97/B bomblets
    ["CBU_97"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 10, submunition_explosive = 15, submunition_name = "BLU_108" }, --Confirmed 10 BLU-108 submunitions, each with 4 skeets
    ["CBU_105"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 10, submunition_explosive = 15, submunition_name = "BLU_108" }, --WCMD variant of CBU-97, confirmed 10 BLU-108 submunitions
    ["BELOUGA"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 151, submunition_explosive = 0.3, submunition_name = "grenade_AC" }, --Confirmed 151 grenade_AC bomblets (French BLG-66)
    ["BLG66_BELOUGA"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 151, submunition_explosive = 0.3, submunition_name = "grenade_AC" }, --Alias for BELOUGA, confirmed 151 grenade_AC bomblets
    ["BL_755"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 147, submunition_explosive = 0.4, submunition_name = "BL_755_bomblet" }, --Confirmed 147 bomblets, submunition name from your table
    ["RBK_250"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 60, submunition_explosive = 0.5, submunition_name = "PTAB_25M" }, --Confirmed 60 PTAB-2.5M anti-tank bomblets
    ["RBK_250_275_AO_1SCH"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 150, submunition_explosive = 0.2, submunition_name = "AO_1SCh" }, --Confirmed 150 AO-1SCh fragmentation bomblets
    ["RBK_500"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 108, submunition_explosive = 0.5, submunition_name = "PTAB_10_5" }, --Confirmed 108 PTAB-10-5 anti-tank bomblets
    ["RBK_500U"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 352, submunition_explosive = 0.2, submunition_name = "OAB_25RT" }, --Confirmed 352 OAB-2.5RT fragmentation bomblets
    ["RBK_500AO"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 108, submunition_explosive = 0.5, submunition_name = "AO_25RT" }, --Confirmed 108 AO-2.5RT fragmentation bomblets
    ["RBK_500U_OAB_2_5RT"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 352, submunition_explosive = 0.2, submunition_name = "OAB_25RT" }, --Confirmed 352 OAB-2.5RT fragmentation bomblets
	["RBK_500_255_PTO_1M"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 126, submunition_explosive = 0.5, submunition_name = "PTO_1M" },
	["RBK_500_255_ShO"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 565, submunition_explosive = 0.1, submunition_name = "ShO" },  
    --*** INS/GPS BOMBS (JDAM) ***
    ["GBU_31"] = { explosive = 582, shaped_charge = false },
    ["GBU_31_V_3B"] = { explosive = 582, shaped_charge = false },
    ["GBU_31_V_2B"] = { explosive = 582, shaped_charge = false },
    ["GBU_31_V_4B"] = { explosive = 582, shaped_charge = false },
    ["GBU_32_V_2B"] = { explosive = 202, shaped_charge = false },
    ["GBU_38"] = { explosive = 100, shaped_charge = false },
    ["GBU_54_V_1B"] = { explosive = 100, shaped_charge = false },
  
    --*** GLIDE BOMBS (JSOW) ***
    ["AGM_154A"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 145, submunition_explosive = 2, submunition_name = "BLU-97/B" }, --JSOW-A, confirmed 145 BLU-97 bomblets from datamine
    ["AGM_154C"] = { explosive = 305, shaped_charge = false },
    ["AGM_154"] = { explosive = 305, shaped_charge = false },
    ["BK90_MJ1"] = { explosive = 0, shaped_charge = false },
    ["BK90_MJ1_MJ2"] = { explosive = 0, shaped_charge = false },
    ["BK90_MJ2"] = { explosive = 0, shaped_charge = false },
  
    ["LS-6-100"] = { explosive = 45, shaped_charge = false },
    ["LS-6-250"] = { explosive = 100, shaped_charge = false },
    ["LS-6-500"] = { explosive = 274, shaped_charge = false },
    ["GB-6"] = { explosive = 0, shaped_charge = false },
    ["GB-6-HE"] = { explosive = 0, shaped_charge = false },
    ["GB-6-SFW"] = { explosive = 0, shaped_charge = false },
  
    --*** AIR GROUND MISSILE (AGM) ***
    ["AGM_62"] = { explosive = 400, shaped_charge = false },
    ["AGM_65D"] = { explosive = 38, shaped_charge = true },
    ["AGM_65E"] = { explosive = 80, shaped_charge = true },
    ["AGM_65F"] = { explosive = 80, shaped_charge = true },
    ["AGM_65G"] = { explosive = 80, shaped_charge = true },
    ["AGM_65H"] = { explosive = 38, shaped_charge = true },
    ["AGM_65K"] = { explosive = 80, shaped_charge = true },
    ["AGM_65L"] = { explosive = 80, shaped_charge = true },
    ["AGM_123"] = { explosive = 274, shaped_charge = false },
    ["AGM_130"] = { explosive = 582, shaped_charge = false },
    ["AGM_119"] = { explosive = 176, shaped_charge = false },
    ["AGM_114"] = { explosive = 10, shaped_charge = true },
    ["AGM_114K"] = { explosive = 10, shaped_charge = true },
  
    ["Rb 05A"] = { explosive = 217, shaped_charge = false },
    ["RB75"] = { explosive = 38, shaped_charge = false },
    ["RB75A"] = { explosive = 38, shaped_charge = false },
    ["RB75B"] = { explosive = 38, shaped_charge = false },
    ["RB75T"] = { explosive = 80, shaped_charge = false },
    ["HOT3_MBDA"] = { explosive = 15, shaped_charge = false },
    ["C-701T"] = { explosive = 38, shaped_charge = false },
    ["C-701IR"] = { explosive = 38, shaped_charge = false },
  
    ["Vikhr_M"] = { explosive = 11, shaped_charge = false },
    ["Vikhr_9M127_1"] = { explosive = 11, shaped_charge = false },
    ["AT_6"] = { explosive = 11, shaped_charge = false },
    ["Ataka_9M120"] = { explosive = 11, shaped_charge = false },
    ["Ataka_9M120F"] = { explosive = 11, shaped_charge = false },
    ["P_9M117"] = { explosive = 0, shaped_charge = false },
    
    ["KH-66_Grom"] = { explosive = 108, shaped_charge = false },
    ["X_23"] = { explosive = 111, shaped_charge = false },
    ["X_23L"] = { explosive = 111, shaped_charge = false },
    ["X_28"] = { explosive = 160, shaped_charge = false },
    ["X_25ML"] = { explosive = 89, shaped_charge = false },
    ["X_25MR"] = { explosive = 140, shaped_charge = false },
    ["X_29L"] = { explosive = 320, shaped_charge = false },
    ["X_29T"] = { explosive = 320, shaped_charge = false },
    ["X_29TE"] = { explosive = 320, shaped_charge = false },
  
    --*** ANTI-RADAR MISSILE (ARM) ***
    ["AGM_88C"] = { explosive = 89, shaped_charge = false },
    ["AGM_88"] = { explosive = 89, shaped_charge = false },
    ["AGM_122"] = { explosive = 15, shaped_charge = false },
    ["LD-10"] = { explosive = 89, shaped_charge = false },
    ["AGM_45A"] = { explosive = 38, shaped_charge = false },
    ["X_58"] = { explosive = 140, shaped_charge = false },
    ["X_25MP"] = { explosive = 89, shaped_charge = false },
  
    --*** ANTI-SHIP MISSILE (ASh) ***
    ["AGM_84D"] = { explosive = 488, shaped_charge = false },
    ["Rb 15F"] = { explosive = 500, shaped_charge = false },
    ["C-802AK"] = { explosive = 500, shaped_charge = false },
  
    --*** CRUISE MISSILE ***
    ["CM-802AKG"] = { explosive = 488, shaped_charge = false },
    ["AGM_84E"] = { explosive = 488, shaped_charge = false },
    ["AGM_84H"] = { explosive = 488, shaped_charge = false },
    ["X_59M"] = { explosive = 488, shaped_charge = false },
  
    --*** ROCKETS ***
    ["HYDRA_70M15"] = { explosive = 5, shaped_charge = false },
    ["HYDRA_70_MK1"] = { explosive = 5, shaped_charge = false },
    ["HYDRA_70_MK5"] = { explosive = 8, shaped_charge = false },
    ["HYDRA_70_M151"] = { explosive = 5, shaped_charge = false },
    ["HYDRA_70_M151_M433"] = { explosive = 5, shaped_charge = false },
    ["HYDRA_70_M229"] = { explosive = 5, shaped_charge = false },
    ["FFAR Mk1 HE"] = { explosive = 5, shaped_charge = false },
    ["FFAR Mk5 HEAT"] = { explosive = 8, shaped_charge = false },
    ["HVAR"] = { explosive = 5, shaped_charge = false },
    ["Zuni_127"] = { explosive = 8, shaped_charge = false },
    ["ARAKM70BHE"] = { explosive = 5, shaped_charge = false },
    ["ARAKM70BAP"] = { explosive = 8, shaped_charge = false },
    ["SNEB_TYPE251_F1B"] = { explosive = 4, shaped_charge = false },
    ["SNEB_TYPE252_F1B"] = { explosive = 4, shaped_charge = false },
    ["SNEB_TYPE253_F1B"] = { explosive = 5, shaped_charge = false },
    ["SNEB_TYPE256_F1B"] = { explosive = 6, shaped_charge = false },
    ["SNEB_TYPE257_F1B"] = { explosive = 8, shaped_charge = false },
    ["SNEB_TYPE251_F4B"] = { explosive = 4, shaped_charge = false },
    ["SNEB_TYPE252_F4B"] = { explosive = 4, shaped_charge = false },
    ["SNEB_TYPE253_F4B"] = { explosive = 5, shaped_charge = false },
    ["SNEB_TYPE256_F4B"] = { explosive = 6, shaped_charge = false },
    ["SNEB_TYPE257_F4B"] = { explosive = 8, shaped_charge = false },
    ["SNEB_TYPE251_H1"] = { explosive = 4, shaped_charge = false },
    ["SNEB_TYPE252_H1"] = { explosive = 4, shaped_charge = false },
    ["SNEB_TYPE253_H1"] = { explosive = 5, shaped_charge = false },
    ["SNEB_TYPE256_H1"] = { explosive = 6, shaped_charge = false },
    ["SNEB_TYPE257_H1"] = { explosive = 8, shaped_charge = false },
    ["MATRA_F4_SNEBT251"] = { explosive = 8, shaped_charge = false },
    ["MATRA_F4_SNEBT253"] = { explosive = 8, shaped_charge = false },
    ["MATRA_F4_SNEBT256"] = { explosive = 8, shaped_charge = false },
    ["MATRA_F1_SNEBT253"] = { explosive = 8, shaped_charge = false },
    ["MATRA_F1_SNEBT256"] = { explosive = 8, shaped_charge = false },
    ["TELSON8_SNEBT251"] = { explosive = 4, shaped_charge = false },
    ["TELSON8_SNEBT253"] = { explosive = 8, shaped_charge = false },
    ["TELSON8_SNEBT256"] = { explosive = 4, shaped_charge = false },
    ["TELSON8_SNEBT257"] = { explosive = 6, shaped_charge = false },
    ["ARF8M3API"] = { explosive = 8, shaped_charge = false },
    ["UG_90MM"] = { explosive = 8, shaped_charge = false },
    ["S-24A"] = { explosive = 24, shaped_charge = false },
    ["S-25OF"] = { explosive = 194, shaped_charge = false },
    ["S-25OFM"] = { explosive = 150, shaped_charge = false },
    ["S-25O"] = { explosive = 150, shaped_charge = false },
    ["S-25-O"] = { explosive = 150, shaped_charge = false },
    ["S_25L"] = { explosive = 190, shaped_charge = false },
    ["S-5M"] = { explosive = 1, shaped_charge = false },
    ["C_5"] = { explosive = 8, shaped_charge = false },
    ["C5"] = { explosive = 5, shaped_charge = false },
    ["C_8"] = { explosive = 4, shaped_charge = false },
    ["C_8OFP2"] = { explosive = 3, shaped_charge = false },
    ["C_13"] = { explosive = 21, shaped_charge = false },
    ["C_24"] = { explosive = 123, shaped_charge = false },
    ["C_25"] = { explosive = 151, shaped_charge = false },
  
    --*** LASER ROCKETS ***
    ["AGR_20"] = { explosive = 8, shaped_charge = false },
    ["AGR_20A"] = { explosive = 8, shaped_charge = false },
    ["AGR_20_M282"] = { explosive = 8, shaped_charge = false },
    ["Hydra_70_M282_MPP"] = { explosive = 8, shaped_charge = false },
    ["BRM-1_90MM"] = { explosive = 8, shaped_charge = false },
}






local effectSmokeId = 1

----[[ ##### HELPER/UTILITY FUNCTIONS ##### ]]----

local function tableHasKey(table, key)
    return table[key] ~= nil
end
  
local function debugMsg(str)
    if splash_damage_options.debug == true then
        debugCounter = (debugCounter or 0) + 1
        local uniqueStr = str .. " [" .. timer.getTime() .. " - " .. debugCounter .. "]"
        trigger.action.outText(uniqueStr, 5)
        env.info("DEBUG: " .. uniqueStr)
    end
end
  
local function gameMsg(str)
    if splash_damage_options.game_messages == true then
        trigger.action.outText(str, 5)
    end
end
  
local function getDistance(point1, point2)
    local x1 = point1.x
    local y1 = point1.y
    local z1 = point1.z
    local x2 = point2.x
    local y2 = point2.y
    local z2 = point2.z
    local dX = math.abs(x1 - x2)
    local dZ = math.abs(z1 - z2)
    local distance = math.sqrt(dX * dX + dZ * dZ)
    return distance
end
  
local function getDistance3D(point1, point2)
    local x1 = point1.x
    local y1 = point1.y
    local z1 = point1.z
    local x2 = point2.x
    local y2 = point2.y
    local z2 = point2.z
    local dX = math.abs(x1 - x2)
    local dY = math.abs(y1 - y2)
    local dZ = math.abs(z1 - z2)
    local distance = math.sqrt(dX * dX + dZ * dZ + dY * dY)
    return distance
end
  
local function vec3Mag(speedVec)
    return math.sqrt(speedVec.x^2 + speedVec.y^2 + speedVec.z^2)
end
  
local function lookahead(speedVec)
    local speed = vec3Mag(speedVec)
    local dist = speed * refreshRate * 1.5 
    return dist
end
  
--Cluster-specific helper functions from Rockeye script
local function normalizeVector(vec)
    local mag = math.sqrt(vec.x^2 + vec.z^2)
    if mag > 0 then
        return { x = vec.x / mag, z = vec.z / mag }
    else
        return { x = 1, z = 0 }
    end
end
local function calculate_drop_angle(velocity)
    local horizontal_speed = math.sqrt((velocity.x or 0)^2 + (velocity.z or 0)^2)
    local vertical_speed = math.abs(velocity.y or 0)
    if horizontal_speed == 0 then return 90 end
    local angle_rad = math.atan(vertical_speed / horizontal_speed)
    return math.deg(angle_rad)
end
local function calculate_dispersion(velocity, burst_altitude)
    local velocity_magnitude = math.sqrt((velocity.x or 0)^2 + (velocity.z or 0)^2)
    local drop_angle = calculate_drop_angle(velocity)
    local length = splash_damage_options.cluster_base_length * (1 + velocity_magnitude / 200)
    local width = splash_damage_options.cluster_base_width * (1 + burst_altitude / 6000)
    local length_jitter = length * (0.85 + math.random() * 0.3)
    local width_jitter = width * (0.85 + math.random() * 0.3)
    return math.max(splash_damage_options.cluster_min_length, math.min(splash_damage_options.cluster_max_length, length_jitter)),
           math.max(splash_damage_options.cluster_min_width, math.min(splash_damage_options.cluster_max_width, width_jitter))
end
----[[ ##### End of HELPER/UTILITY FUNCTIONS ##### ]]----

cargoEffectsQueue = {}
WpnHandler = {}
tracked_weapons = {}
local processedUnitsGlobal = {}
  
function getWeaponExplosive(name)
    local weaponData = explTable[name]
    if weaponData then
        return weaponData.explosive, weaponData.shaped_charge
    else
        return 0, false
    end
end
  
function track_wpns_cluster_scan(args)
    local parentPos = args[1]
    local parentDir = args[2]
    local parentName = args[3]
    local subName = args[4]
    local subCount = args[5]
    local subPower = args[6]
    local parentVel = args[7]
    local attempt = args[8] or 1
    local maxAttempts = 3
    local scanVol = {
        id = world.VolumeType.SPHERE,
        params = { point = parentPos, radius = 400 }
    }
    local bombletsFound = {}
    local allWeaponsFound = {}
    -- General scan for all weapons
    world.searchObjects(Object.Category.WEAPON, scanVol, function(wpn)
        if wpn:isExist() then
            local wpnId = wpn.id_
            local wpnType = wpn:getTypeName()
            local wpnPos = wpn:getPosition().p
            table.insert(allWeaponsFound, { id = wpnId, type = wpnType, x = wpnPos.x, y = wpnPos.y, z = wpnPos.z })
            if wpnType == subName and not tracked_weapons[wpnId] then
                tracked_weapons[wpnId] = {
                    wpn = wpn,
                    pos = wpnPos,
                    speed = wpn:getVelocity(),
                    name = wpnType,
                    parent = parentName,
                    parentVelocity = parentVel
                }
                table.insert(bombletsFound, wpnId)
                debugMsg("Detected expected submunition '" .. wpnType .. "' from '" .. parentName .. "' at X: " .. string.format("%.0f", wpnPos.x) .. ", Y: " .. string.format("%.0f", wpnPos.y) .. ", Z: " .. string.format("%.0f", wpnPos.z) .. " (Attempt " .. attempt .. ")")
            end
        end
        return true
    end)
    -- Log results
    debugMsg("Scanned for submunition '" .. subName .. "' bomblets from '" .. parentName .. "': " .. #bombletsFound .. " found (Attempt " .. attempt .. ")")
    if #allWeaponsFound > 0 then
        local msg = "General scan for '" .. parentName .. "': " .. #allWeaponsFound .. " bomblets released, expected " .. subCount .. " '" .. subName .. "'"
        local typeMismatch = false
        for _, wpn in ipairs(allWeaponsFound) do
            if wpn.type ~= subName then
                typeMismatch = true
                break
            end
        end
        if typeMismatch then
            msg = msg .. " - Mismatch detected! Actual bomblets: "
            for _, wpn in ipairs(allWeaponsFound) do
                msg = msg .. "'" .. wpn.type .. "' (X: " .. string.format("%.0f", wpn.x) .. ", Y: " .. string.format("%.0f", wpn.y) .. ", Z: " .. string.format("%.0f", wpn.z) .. ") "
            end
            msg = msg .. "Script may need changing."
        end
        debugMsg(msg)
    elseif #bombletsFound == 0 and #allWeaponsFound == 0 then
        debugMsg("No bomblets of any type detected for '" .. parentName .. "' (Attempt " .. attempt .. ")")
    end
    -- Retry if no expected submunitions found
    if #bombletsFound == 0 and attempt < maxAttempts then
        debugMsg("No expected submunition '" .. subName .. "' found on attempt " .. attempt .. ", retrying in 0.5s")
        timer.scheduleFunction(track_wpns_cluster_scan, {parentPos, parentDir, parentName, subName, subCount, subPower, parentVel, attempt + 1}, timer.getTime() + 0.5)
    elseif #bombletsFound == 0 and attempt == maxAttempts then
        debugMsg("No submunition '" .. subName .. "' spawned by DCS for '" .. parentName .. "' after " .. maxAttempts .. " attempts - skipping additional explosions")
    end
end

----[[ ##### Updated track_wpns() Function ##### ]]----
local recentExplosions = {}
function track_wpns()
    local weaponsToRemove = {} --Delay removal to ensure all weapons are checked
    for wpn_id_, wpnData in pairs(tracked_weapons) do   
        local status, err = pcall(function()
        if wpnData.wpn:isExist() then
            --Update position, direction, speed
            wpnData.pos = wpnData.wpn:getPosition().p
            wpnData.dir = wpnData.wpn:getPosition().x
            wpnData.speed = wpnData.wpn:getVelocity()
                --[[
				
		
            --Tick-by-tick tracking from weapon's actual position
	local tickVol = {
    id = world.VolumeType.SPHERE,
    params = {
                    point = wpnData.pos, --Real weapon position
                    radius = 150 --150m radius
    }
}
local tickTargets = {}
world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, function(obj)
    if obj:isExist() then
        table.insert(tickTargets, {
            name = obj:getTypeName(),
                        distance = getDistance3D(wpnData.pos, obj:getPoint()), --3D distance
            position = obj:getPoint(),
            health = obj:getLife() or 0
        })
                end
                return true
            end)
            debugMsg("Tick Track for " .. wpnData.name .. " at X: " .. string.format("%.0f", wpnData.pos.x) .. ", Y: " .. string.format("%.0f", wpnData.pos.y) .. ", Z: " .. string.format("%.0f", wpnData.pos.z) .. " - " .. #tickTargets .. " targets")
            for i, target in ipairs(tickTargets) do
                debugMsg("Tick Target #" .. i .. ": " .. target.name .. " at X: " .. string.format("%.0f", target.position.x) .. ", Y: " .. string.format("%.0f", target.position.y) .. ", Z: " .. string.format("%.0f", target.position.z) .. ", Dist: " .. string.format("%.1f", target.distance) .. "m, Health: " .. target.health)
            end
   
				
				]]--

            --Scan potential blast zone in the last frame before impact
            if splash_damage_options.track_pre_explosion then
                local ip = land.getIP(wpnData.pos, wpnData.dir, lookahead(wpnData.speed))
                local predictedImpact = ip or wpnData.pos

                local base_explosive, isShapedCharge = getWeaponExplosive(wpnData.name)
                base_explosive = base_explosive * splash_damage_options.overall_scaling
                if splash_damage_options.rocket_multiplier and wpnData.cat == Weapon.Category.ROCKET then
                    base_explosive = base_explosive * splash_damage_options.rocket_multiplier
                end

                local explosionPower = base_explosive
                if splash_damage_options.apply_shaped_charge_effects and isShapedCharge then
                    explosionPower = explosionPower * splash_damage_options.shaped_charge_multiplier
                end

                    local blastRadius = splash_damage_options.blast_search_radius * 2 --Wider post-scan (180m default)
                if splash_damage_options.use_dynamic_blast_radius then
                    blastRadius = math.pow(explosionPower, 1/3) * 10 * splash_damage_options.dynamic_blast_radius_modifier 
                end

                --Tight scan while weapon exists
                --local tightRadius = 50
                --if splash_damage_options.use_dynamic_blast_radius then
                -- tightRadius = math.pow(explosionPower, 1/3) * 5 * splash_damage_options.dynamic_blast_radius_modifier
                --end
				local tightRadius = blastRadius --Use already calculated blastRadius
                local volS = {
                    id = world.VolumeType.SPHERE,
                    params = { 
                        point = wpnData.pos, --Use current pos
                        radius = tightRadius 
                    }
                }
                local tightTargets = {}
                local ifFound = function(foundObject, targets, center)
                    if foundObject:isExist() then
                        local category = foundObject:getCategory()
                        if (category == Object.Category.UNIT and (foundObject:getDesc().category == Unit.Category.GROUND_UNIT or foundObject:getDesc().category == Unit.Category.AIRPLANE)) or
                               category == Object.Category.STATIC then
                                table.insert(targets, {
                                    name = foundObject:getTypeName(),
                                    distance = getDistance(center, foundObject:getPoint()),
                                    health = foundObject:getLife() or 0,
                                    position = foundObject:getPoint(),
                                    maxHealth = (category == Object.Category.UNIT and foundObject:getDesc().life) or foundObject:getLife() or 0,
                                    unit = foundObject
                                })
                        end
                    end
                    return true
                end
                    if splash_damage_options.track_pre_explosion_debug then
                debugMsg("Scanning tight radius " .. tightRadius .. "m at current pos while weapon exists")
                    end
                world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, volS, function(obj) ifFound(obj, tightTargets, wpnData.pos) end)
                wpnData.tightTargets = tightTargets --Store for impact

                --Wider scan for lastKnownTargets
                volS.params.point = predictedImpact
                volS.params.radius = blastRadius
                local foundTargets = {}
                world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, volS, function(obj) ifFound(obj, foundTargets, predictedImpact) end)
                wpnData.lastKnownTargets = foundTargets
            end
                --Submunition impact handling
                local weaponData = explTable[wpnData.parent or wpnData.name] or { submunition_name = "unknown" }
                if wpnData.name == weaponData.submunition_name then
                    local groundHeight = land.getHeight({x = wpnData.pos.x, y = wpnData.pos.z})
                    if wpnData.pos.y - groundHeight < 50 then --Impact threshold like old script
                        debugMsg("Submunition '" .. wpnData.name .. "' from '" .. (wpnData.parent or "unknown") .. "' impacted at X: " .. string.format("%.0f", wpnData.pos.x) .. ", Z: " .. string.format("%.0f", wpnData.pos.z))
                        local parentWeaponData = explTable[wpnData.parent] or { submunition_count = 30, submunition_explosive = 1 }
                        local submunitionCount = parentWeaponData.submunition_count or 30
                        local submunitionPower = (parentWeaponData.submunition_explosive or 1) * splash_damage_options.cluster_bomblet_damage_modifier * splash_damage_options.overall_scaling
						if splash_damage_options.cluster_bomblet_reductionmodifier then
							if submunitionCount > 35 then
                                local reductionFactor = (60 - 35) / (247 - 35)
								submunitionCount = 35 + math.floor((submunitionCount - 35) * reductionFactor)
                                if submunitionCount > 60 then submunitionCount = 60 end
							end
						end
        --Use parent velocity if available, else submunition speed
        local parentDir = wpnData.parentVelocity or wpnData.speed
        local dispersionLength, dispersionWidth = calculate_dispersion(parentDir, 2000) --Match original 2000m
        local dirMag = math.sqrt(parentDir.x^2 + parentDir.z^2)
        local dir = dirMag > 0 and {x = parentDir.x / dirMag, z = parentDir.z / dirMag} or {x = 1, z = 0}
                        debugMsg("Simulating " .. submunitionCount .. " bomblets for submunition '" .. wpnData.name .. "' from '" .. (wpnData.parent or "unknown") .. "' over " .. string.format("%.0f", dispersionLength) .. "m x " .. string.format("%.0f", dispersionWidth) .. "m")
                        for i = 1, submunitionCount do
                            local theta = math.random() * 2 * math.pi
                            local r = math.sqrt(math.random())
                            local xOffset = r * dispersionLength * 0.5 * math.cos(theta)
                            local zOffset = r * dispersionWidth * 0.5 * math.sin(theta)
                            local subPos = {
                                x = wpnData.pos.x + (xOffset * dir.x - zOffset * dir.z),
                                z = wpnData.pos.z + (xOffset * dir.z + zOffset * dir.x)
                            }
                            subPos.y = land.getHeight({x = subPos.x, y = subPos.z})
                            debugMsg("Triggering bomblet #" .. i .. " for submunition '" .. wpnData.name .. "' at X: " .. string.format("%.0f", subPos.x) .. ", Z: " .. string.format("%.0f", subPos.z) .. " with power " .. submunitionPower)
                            trigger.action.explosion(subPos, submunitionPower)
                        end
                        table.insert(weaponsToRemove, wpn_id_)
                    end
                end
            else
            --Weapon has impacted
            debugMsg("Weapon " .. wpnData.name .. " no longer exists at " .. timer.getTime() .. "s")
            local ip = land.getIP(wpnData.pos, wpnData.dir, lookahead(wpnData.speed))  --terrain intersection point with weapon's nose.  Only search out 20 meters though.
            local explosionPoint
            if not ip then --use last calculated IP
                explosionPoint = wpnData.pos
            else --use intersection point
                explosionPoint = ip
            end
			local chosenTargets = wpnData.tightTargets or {}
            local safeToBlast = true
			if splash_damage_options.ordnance_protection then
                local checkVol = { id = world.VolumeType.SPHERE, params = { point = explosionPoint, radius = splash_damage_options.ordnance_protection_radius } }
                    debugMsg("Checking ordnance protection for '" .. wpnData.name .. "' at X: " .. explosionPoint.x .. ", Y: " .. explosionPoint.y .. ", Z: " .. explosionPoint.z .. " with radius " .. splash_damage_options.ordnance_protection_radius .. "m")
				world.searchObjects(Object.Category.WEAPON, checkVol, function(obj)
					if obj:isExist() and tracked_weapons[obj.id_] then
						safeToBlast = false
                            debugMsg("Skipping explosion for '" .. wpnData.name .. "' - nearby bomb '" .. tracked_weapons[obj.id_].name .. "' within " .. splash_damage_options.ordnance_protection_radius .. "m")
						return false
					end
					return true
                end)
            end
            if safeToBlast then
                    debugMsg("FinalPos Check for '" .. wpnData.name .. "': X: " .. string.format("%.0f", explosionPoint.x) .. ", Y: " .. string.format("%.0f", explosionPoint.y) .. ", Z: " .. string.format("%.0f", explosionPoint.z) .. ")")
            local base_explosive, isShapedCharge = getWeaponExplosive(wpnData.name)
            base_explosive = base_explosive * splash_damage_options.overall_scaling
            if splash_damage_options.rocket_multiplier and wpnData.cat == Weapon.Category.ROCKET then
                base_explosive = base_explosive * splash_damage_options.rocket_multiplier
            end

            local explosionPower = base_explosive
            if splash_damage_options.apply_shaped_charge_effects and isShapedCharge then
                explosionPower = explosionPower * splash_damage_options.shaped_charge_multiplier
            end

            local blastRadius = splash_damage_options.blast_search_radius * 2 --Wider post-scan (180m default)
            if splash_damage_options.use_dynamic_blast_radius then
                    blastRadius = math.pow(explosionPower, 1/3) * 10 * splash_damage_options.dynamic_blast_radius_modifier
            end


                --Store pre-explosion state of all tracked weapons for detection
                local preExplosionWeapons = {}
                if splash_damage_options.detect_ordnance_destruction and splash_damage_options.larger_explosions then
                    for id, data in pairs(tracked_weapons) do
                        if data.wpn:isExist() then
                            preExplosionWeapons[id] = {
                                name = data.name,
                                pos = data.wpn:getPosition().p,
                                    distance = getDistance3D(explosionPoint, data.wpn:getPosition().p),
                                    explosive = getWeaponExplosive(data.name) --Store the explosive power
                            }
                        end
                    end
                end
			--Cluster Bomb Handling
			local weaponData = explTable[wpnData.name] or { explosive = 0, shaped_charge = false }
			local isCluster = weaponData.cluster or false
			if splash_damage_options.cluster_enabled and isCluster then
				local submunitionCount = weaponData.submunition_count or 30
				local submunitionPower = (weaponData.submunition_explosive or 1) * splash_damage_options.cluster_bomblet_damage_modifier * splash_damage_options.overall_scaling
                        local submunitionName = weaponData.submunition_name or "unknown"
				--Apply bomblet reduction logic if enabled
				if splash_damage_options.cluster_bomblet_reductionmodifier then
                            if submunitionCount > 35 then
                                local reductionFactor = (60 - 35) / (247 - 35)
							submunitionCount = 35 + math.floor((submunitionCount - 35) * reductionFactor)
							if submunitionCount > 60 then submunitionCount = 60 end --Cap at 60
						end
					end
                        -- Extended scan with general bomblet detection
                    timer.scheduleFunction(track_wpns_cluster_scan, {explosionPoint, wpnData.dir, wpnData.name, submunitionName, submunitionCount, submunitionPower, wpnData.speed}, timer.getTime() + 0.3)
			else
				--Standard explosion handling
				if splash_damage_options.larger_explosions then
                            debugMsg("Triggering initial explosion for '" .. wpnData.name .. "' at power " .. explosionPower)
					trigger.action.explosion(explosionPoint, explosionPower)
                            table.insert(recentExplosions, { pos = explosionPoint, time = timer.getTime(), radius = blastRadius })
                            debugMsg("Added to recentExplosions for '" .. wpnData.name .. "': X: " .. explosionPoint.x .. ", Y: " .. explosionPoint.y .. ", Z: " .. explosionPoint.z .. ", Time: " .. timer.getTime())
				end
				blastWave(explosionPoint, splash_damage_options.blast_search_radius, wpnData.ordnance, explosionPower, isShapedCharge)
			end
            --detect_ordnance_destruction comes before recent_large_explosion_snap in original
            if splash_damage_options.detect_ordnance_destruction and splash_damage_options.larger_explosions then
                timer.scheduleFunction(function(args)
                    local explosionPoint = args[1]
                    local blastRadius = args[2]
                    local triggeringWeapon = args[3]
                    local preExplosionWeapons = args[4]
                    for id, preData in pairs(preExplosionWeapons) do
                        if tracked_weapons[id] and not tracked_weapons[id].wpn:isExist() then
                            if preData.distance <= blastRadius then
                                local msg = "WARNING: " .. preData.name .. " destroyed by large explosion from " .. triggeringWeapon .. " at " .. string.format("X: %.0f, Y: %.0f, Z: %.0f", explosionPoint.x, explosionPoint.y, explosionPoint.z)
                                gameMsg(msg)
                                debugMsg(msg)
                                env.info(msg)
                                if splash_damage_options.snap_to_ground_if_destroyed_by_large_explosion then
                                    local groundPos = {
                                        x = preData.pos.x,
                                        y = land.getHeight({x = preData.pos.x, y = preData.pos.z}),
                                        z = preData.pos.z
                                    }
                                    local destroyedWeaponPower, isShapedCharge = preData.explosive
                                    destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.overall_scaling
                                    if splash_damage_options.rocket_multiplier and tracked_weapons[id].cat == Weapon.Category.ROCKET then
                                        destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.rocket_multiplier
                                    end
                                    if splash_damage_options.apply_shaped_charge_effects and isShapedCharge then
                                        destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.shaped_charge_multiplier
                                    end
                                    debugMsg("Triggering ground explosion for destroyed " .. preData.name .. " (detect_ordnance_destruction) at X: " .. string.format("%.0f", groundPos.x) .. ", Y: " .. string.format("%.0f", groundPos.y) .. ", Z: " .. string.format("%.0f", groundPos.z) .. " with power " .. destroyedWeaponPower)
                                    trigger.action.explosion(groundPos, destroyedWeaponPower)
                                end
                            end
                        end
                    end
                end, {explosionPoint, blastRadius, wpnData.name, preExplosionWeapons}, timer.getTime() + 0.2)
            end
            --recent_large_explosion_snap comes after main explosion and detect_ordnance_destruction
                    if splash_damage_options.larger_explosions and splash_damage_options.recent_large_explosion_snap then
                        local currentTime = timer.getTime()
                        for id, data in pairs(tracked_weapons) do
                            if id ~= wpn_id_ and not data.wpn:isExist() then
                                local terrainHeight = land.getHeight({x = data.pos.x, y = data.pos.z})
            			local isMidAir = data.pos.y > terrainHeight + 5
                                    local snapTriggered = false
                                    for _, explosion in ipairs(recentExplosions) do
                                        local timeDiff = currentTime - explosion.time
                                        local distance = getDistance3D(data.pos, explosion.pos)
                                        debugMsg("Checking " .. data.name .. " at X: " .. data.pos.x .. ", Y: " .. data.pos.y .. ", Z: " .. data.pos.z .. " against explosion at X: " .. explosion.pos.x .. ", Y: " .. explosion.pos.y .. ", Z: " .. explosion.pos.z .. " - Distance: " .. distance .. "m, TimeDiff: " .. timeDiff .. "s")
                                        if timeDiff <= splash_damage_options.recent_large_explosion_time and distance <= splash_damage_options.recent_large_explosion_range then
                                            if isMidAir then
                                                local groundPos = { x = data.pos.x, y = terrainHeight, z = data.pos.z }
                                                local destroyedWeaponPower, isShapedCharge = getWeaponExplosive(data.name)
                                                destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.overall_scaling
                                                if splash_damage_options.rocket_multiplier and data.cat == Weapon.Category.ROCKET then
                                                    destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.rocket_multiplier
                                                end
                                                if splash_damage_options.apply_shaped_charge_effects and isShapedCharge then
                                                    destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.shaped_charge_multiplier
                                                end
                                    debugMsg("Weapon " .. data.name .. " detected recent large explosion within " .. splash_damage_options.recent_large_explosion_range .. "m and " .. splash_damage_options.recent_large_explosion_time .. "s, snapping to ground at X: " .. string.format("%.0f", groundPos.x) .. ", Y: " .. string.format("%.0f", groundPos.y) .. ", Z: " .. string.format("%.0f", groundPos.z) .. " with power " .. destroyedWeaponPower)
                                                trigger.action.explosion(groundPos, destroyedWeaponPower)
                                                snapTriggered = true
                                                table.insert(weaponsToRemove, id)
                                                break
                                            else
                                                debugMsg("Weapon " .. data.name .. " impacted ground within recent_large_explosion_range (" .. splash_damage_options.recent_large_explosion_range .. "m) and time (" .. splash_damage_options.recent_large_explosion_time .. "s), no snap needed")
                                    snapTriggered = true
                                                break
                                            end
                                        end
                                    end
                                    if not snapTriggered then
                                        if isMidAir then
                                debugMsg("Weapon " .. data.name .. " destroyed in air, but no recent large explosion within " .. splash_damage_options.recent_large_explosion_range .. "m or " .. splash_damage_options.recent_large_explosion_time .. "s")
                                        else
                                            debugMsg("Weapon " .. data.name .. " impacted ground, not processed by recent large explosion settings")
                                        end
                                    end
                            end
                        end
                        local newExplosions = {}
                        for _, explosion in ipairs(recentExplosions) do
                            if currentTime - explosion.time <= splash_damage_options.recent_large_explosion_time then
                                table.insert(newExplosions, explosion)
                            end
                        end
                        recentExplosions = newExplosions
                    end
                --Mark units as destroyed to avoid MiST accessing them
                local destroyedUnits = {}
                for _, target in ipairs(chosenTargets) do
                    if target.unit:isExist() and target.health > 0 and target.unit:getLife() <= 0 then
                        destroyedUnits[target.name] = true
                        debugMsg("Marked " .. target.name .. " as destroyed pre-impact")
                    end
                end
                --Schedule explosion handling with original 0.1-second delay, enhanced error handling
            timer.scheduleFunction(function(args)
                local finalPos = args[1]
                local explosionPoint = args[2]
                local explosionPower = args[3]
                local isShapedCharge = args[4]
                local blastRadius = args[5]
                local chosenTargets = args[6]
                local weaponName = args[7]
                local wpnData = args[8]
                    debugMsg("Starting impact handling for " .. weaponName .. " at " .. timer.getTime() .. "s")
                    local status, err = pcall(function()
                --Log pre-explosion targets
                if splash_damage_options.track_pre_explosion then
                    if #chosenTargets > 0 then
                        local msg = "Targets in blast zone for " .. weaponName .. " BEFORE explosion (last frame, using finalPos):\n"
                        for i, target in ipairs(chosenTargets) do
                            msg = msg .. "- " .. target.name .. " (Dist: " .. string.format("%.1f", target.distance) .. "m, Health: " .. target.health .. ")\n"
                        end
                        debugMsg(msg)
                        env.info("SplashDamage Pre-Explosion (Last Frame): " .. msg)
                    else
                        debugMsg("No targets in blast zone for " .. weaponName .. " BEFORE explosion (last frame)")
                        env.info("SplashDamage Pre-Explosion (Last Frame): No targets in blast zone for " .. weaponName)
                    end
                end

                blastWave(explosionPoint, splash_damage_options.blast_search_radius, wpnData.ordnance, explosionPower, isShapedCharge)

                --Post-explosion analysis and queue cargo effects
                if splash_damage_options.track_pre_explosion then
                    timer.scheduleFunction(function(innerArgs)
                        local impactPoint = innerArgs[1]
                        local blastRadius = innerArgs[2]
                        local preExplosionTargets = innerArgs[3] or {}
                        local weaponName = innerArgs[4]
                        local weaponPower = innerArgs[5]
                                debugMsg("Starting post-explosion analysis for " .. weaponName .. " at " .. timer.getTime() .. "s")

                        --Scan all units in wider radius
                        local postExplosionTargets = {}
                        local volS = {
                            id = world.VolumeType.SPHERE,
                            params = {
                                point = impactPoint,
                                radius = blastRadius
                            }
                        }

                        local ifFound = function(foundObject)
                            if foundObject:isExist() then
                                local category = foundObject:getCategory()
                                if (category == Object.Category.UNIT and (foundObject:getDesc().category == Unit.Category.GROUND_UNIT or foundObject:getDesc().category == Unit.Category.AIRPLANE)) or
                                       category == Object.Category.STATIC then
                                        table.insert(postExplosionTargets, {
                                            name = foundObject:getTypeName(),
                                            health = foundObject:getLife() or 0,
                                            position = foundObject:getPoint(),
                                            maxHealth = (category == Object.Category.UNIT and foundObject:getDesc().life) or foundObject:getLife() or 0
                                        })
                                            end
                                end
                            return true
                        end

                        world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, volS, ifFound)

                        local msg = "Post-explosion analysis for " .. weaponName .. ":\n"

                        --Match pre-detected units
                        for _, preTarget in ipairs(preExplosionTargets) do
                            local found = false
                            local postHealth = 0
                            local postPosition = nil
                            for _, postTarget in ipairs(postExplosionTargets) do
                                if preTarget.name == postTarget.name and getDistance(preTarget.position, postTarget.position) < 1 then
                                    found = true
                                    postHealth = postTarget.health
                                    postPosition = postTarget.position
                                    break
                                end
                            end

                            local healthPercent = preTarget.maxHealth > 0 and (postHealth / preTarget.maxHealth * 100) or 0
                            local status = ""

                            if not found or postHealth <= 0 then
                                status = "WAS FULLY DESTROYED"
                            elseif healthPercent < splash_damage_options.cargo_damage_threshold then
                                status = "WAS DAMAGED BELOW THRESHOLD"
                            else
                                status = "SURVIVED (Health: " .. postHealth .. ")"
                            end

                            --Always include coords in status message
                            local coords = found and postPosition or preTarget.position
                            local statusMsg = status .. " AT " .. string.format("X: %.0f, Y: %.0f, Z: %.0f", coords.x, coords.y, coords.z) .. " (Pre: " .. preTarget.health .. ", Post: " .. postHealth .. ")"
                            --Check if target is in cargoUnits and within blast radius
                            local cargoData = cargoUnits[preTarget.name]
                            if cargoData and preTarget.distance <= blastRadius and 
                               (not found or postHealth <= 0 or healthPercent < splash_damage_options.cargo_damage_threshold) then

                                if splash_damage_options.enable_cargo_effects then
                                                local cargoPower = cargoData.cargoExplosionPower or weaponPower --Use fixed power or fallback
                                    table.insert(cargoEffectsQueue, {
                                        name = preTarget.name,
                                        distance = preTarget.distance,
                                        coords = coords,
                                        power = cargoPower,
                                        explosion = cargoData.cargoExplosion,
                                        cookOff = cargoData.cargoCookOff,
                                        cookOffCount = cargoData.cookOffCount,
                                        cookOffPower = cargoData.cookOffPower,
                                        cookOffDuration = cargoData.cookOffDuration,
                                        cookOffRandomTiming = cargoData.cookOffRandomTiming,
                                        cookOffPowerRandom = cargoData.cookOffPowerRandom,
                                        isTanker = cargoData.isTanker,
                                        flameSize = cargoData.flameSize,
                                        flameDuration = cargoData.flameDuration
                                    })
                                    statusMsg = statusMsg .. " WITH CARGO EXPLOSION (Power: " .. cargoPower .. ")"
                                    if cargoData.cargoCookOff and cargoData.cookOffCount > 0 then
                                        statusMsg = statusMsg .. " WITH COOK-OFF (" .. cargoData.cookOffCount .. " blasts over " .. cargoData.cookOffDuration .. "s)"
                                    end
                                end
                            end

                            msg = msg .. "- " .. preTarget.name .. " " .. statusMsg .. "\n"
                        end
                        --Check for additional units
                        for _, postTarget in ipairs(postExplosionTargets) do
                            local isPreDetected = false
                            for _, preTarget in ipairs(preExplosionTargets) do
                                if preTarget.name == postTarget.name and getDistance(preTarget.position, postTarget.position) < 1 then
                                    isPreDetected = true
                                    break
                                end
                            end
                            if not isPreDetected then
                                local coords = postTarget.position
                                local healthPercent = postTarget.maxHealth > 0 and (postTarget.health / postTarget.maxHealth * 100) or 0
                                local status = postTarget.health <= 0 and "WAS FULLY DESTROYED" or 
                                              (healthPercent < splash_damage_options.cargo_damage_threshold and "WAS DAMAGED BELOW THRESHOLD" or 
                                              "SURVIVED (Health: " .. postTarget.health .. ")")
                                local statusMsg = status .. " AT " .. string.format("X: %.0f, Y: %.0f, Z: %.0f", coords.x, coords.y, coords.z) .. " (Pre: Unknown, Post: " .. postTarget.health .. ")"
                                local cargoData = cargoUnits[postTarget.name]
                                if cargoData and (postTarget.health <= 0 or healthPercent < splash_damage_options.cargo_damage_threshold) then
                                    if splash_damage_options.enable_cargo_effects then
                                                    local cargoPower = cargoData.cargoExplosionPower or weaponPower --Use fixed power or fallback
                                    local distance = getDistance(impactPoint, coords)
                                    table.insert(cargoEffectsQueue, {
                                        name = postTarget.name,
                                        distance = distance,
                                        coords = coords,
                                        power = cargoPower,
                                        explosion = cargoData.cargoExplosion,
                                        cookOff = cargoData.cargoCookOff,
                                        cookOffCount = cargoData.cookOffCount,
                                        cookOffPower = cargoData.cookOffPower,
                                        cookOffDuration = cargoData.cookOffDuration,
                                        cookOffRandomTiming = cargoData.cookOffRandomTiming,
                                        cookOffPowerRandom = cargoData.cookOffPowerRandom,
                                        isTanker = cargoData.isTanker,
                                        flameSize = cargoData.flameSize,
                                        flameDuration = cargoData.flameDuration
                                    })
                                    statusMsg = statusMsg .. " WITH CARGO EXPLOSION (Power: " .. cargoPower .. ")"
                                                if cargoData.cargoCookOff and cargoData.cookOffCount > 0 then
                                        statusMsg = statusMsg .. " WITH COOK-OFF (" .. cargoData.cookOffCount .. " blasts over " .. cargoData.cookOffDuration .. "s)"
                                        end
                                    end
                                end
                                msg = msg .. "- " .. postTarget.name .. " " .. statusMsg .. "\n"
                            end
                        end

                        --Schedule all queued cargo effects
                        if #cargoEffectsQueue > 0 then
                            local effectIndex = 0
                                local processedCargoUnits = {} --Track processed units
    local flamePositions = {} --Track flame coords with 3m radius
                            for _, effect in ipairs(cargoEffectsQueue) do
                                    local unitKey = effect.name .. "_" .. effect.coords.x .. "_" .. effect.coords.z
                                    if not processedUnitsGlobal[unitKey] and not processedCargoUnits[unitKey] then
                                if effect.explosion then
                                    debugMsg("Triggering cargo explosion for " .. effect.name .. " at " .. string.format("%.1f", effect.distance) .. "m with power " .. effect.power .. " scheduled at " .. effectIndex .. "s")
                                    timer.scheduleFunction(function(params)
                    debugMsg("Executing cargo explosion at X: " .. string.format("%.0f", params[1].x) .. ", Y: " .. string.format("%.0f", params[1].y) .. ", Z: " .. string.format("%.0f", params[1].z) .. " with power " .. params[2])
                                        trigger.action.explosion(params[1], params[2])
                end, {effect.coords, effect.power}, timer.getTime() + effectIndex + 0.1) --Slight delay for visibility
                                    if effect.isTanker then
                                        local flameSize = effect.flameSize or 3
                    			local flameDuration = effect.flameDuration --Use cargoUnits value directly, no default
                                        local flameDensity = 1.0 --Max density for visibility
                                        local effectId = effectSmokeId
                                        effectSmokeId = effectSmokeId + 1
                    --Check for nearby flames within 3m
                    local isDuplicate = false
                    for _, pos in pairs(flamePositions) do
                        if getDistance3D(effect.coords, pos) < 3 then
                            isDuplicate = true
                            debugMsg("Skipping duplicate flame for " .. effect.name .. " near X: " .. string.format("%.0f", pos.x) .. ", Y: " .. string.format("%.0f", pos.y) .. ", Z: " .. string.format("%.0f", pos.z))
                            break
                        end
                    end
                    if not isDuplicate then
                                        debugMsg("Adding flame effect for tanker " .. effect.name .. " at " .. string.format("%.1f", effect.distance) .. "m (Size: " .. flameSize .. ", Duration: " .. flameDuration .. "s, ID: " .. effectId .. ") scheduled at " .. effectIndex .. "s")
                                        timer.scheduleFunction(function(params)
                                                    --Adjust Y-coordinate to terrain height + offset
                                                    local terrainHeight = land.getHeight({x = params[1].x, y = params[1].z})
                                                    local adjustedCoords = {x = params[1].x, y = terrainHeight + 2, z = params[1].z}
                                                            debugMsg("Spawning flame effect at X: " .. string.format("%.0f", adjustedCoords.x) .. ", Y: " .. string.format("%.0f", adjustedCoords.y) .. ", Z: " .. string.format("%.0f", adjustedCoords.z))
            trigger.action.explosion(adjustedCoords, 10) --Small explosion to force visibility
                                                    trigger.action.effectSmokeBig(adjustedCoords, params[2], params[3], params[4])
                        end, {effect.coords, flameSize, flameDensity, effectId}, timer.getTime() + effectIndex + 0.2) --Slight delay
                                        timer.scheduleFunction(function(id)
                                            debugMsg("Stopping flame effect for " .. effect.name .. " (ID: " .. id .. ")")
                                            trigger.action.effectSmokeStop(id)
                        end, effectId, timer.getTime() + effectIndex + flameDuration + 0.2)
                        table.insert(flamePositions, effect.coords)
                                                end
                                    end
                                end
                                            debugMsg("Checking cook-off for " .. effect.name .. ": cookOff=" .. tostring(effect.cookOff) .. ", count=" .. tostring(effect.cookOffCount))
					if effect.cookOff and effect.cookOffCount > 0 then
						debugMsg("Scheduling " .. effect.cookOffCount .. " cook-off explosions for " .. effect.name .. " at " .. string.format("%.1f", effect.distance) .. "m over " .. effect.cookOffDuration .. "s starting at " .. effectIndex .. "s")
						for i = 1, effect.cookOffCount do
							local delay = effect.cookOffRandomTiming and math.random() * effect.cookOffDuration or (i - 1) * (effect.cookOffDuration / effect.cookOffCount)
							local basePower = effect.cookOffPower
							local powerVariation = effect.cookOffPowerRandom / 100
							local cookOffPower = effect.cookOffPowerRandom == 0 and basePower or basePower * (1 + powerVariation * (math.random() * 2 - 1))
							debugMsg("Cook-off #" .. i .. " for " .. effect.name .. " at " .. string.format("%.1f", effect.distance) .. "m scheduled at " .. string.format("%.3f", delay) .. "s with power " .. string.format("%.2f", cookOffPower))
							timer.scheduleFunction(function(params)
								local pos = params[1]
								local power = params[2]
								debugMsg("Executing cook-off at " .. string.format("X: %.0f, Y: %.0f, Z: %.0f", pos.x, pos.y, pos.z) .. " with power " .. power)
								trigger.action.explosion(pos, power)
							end, {effect.coords, cookOffPower}, timer.getTime() + effectIndex + delay)
						end
						--Debris burst only if cook-off is true and enabled
						if splash_damage_options.debris_effects then
							local debrisCount = math.random(splash_damage_options.debris_count_min, splash_damage_options.debris_count_max)
							for j = 1, debrisCount do
								--Random spherical offset
								local theta = math.random() * 2 * math.pi --Horizontal angle
								local phi = math.acos(math.random() * 2 - 1) --Vertical angle for sphere
								local minDist = splash_damage_options.debris_max_distance * 0.1 --10% of max
								local maxDist = splash_damage_options.debris_max_distance
								local r = math.random() * (maxDist - minDist) + minDist --10% to full max distance
								local debrisX = effect.coords.x + r * math.sin(phi) * math.cos(theta)
								local debrisZ = effect.coords.z + r * math.sin(phi) * math.sin(theta)
								local terrainY = land.getHeight({x = debrisX, y = debrisZ})
								local debrisY = terrainY + math.random() * maxDist --0 to max_distance above ground
								local debrisPos = {x = debrisX, y = debrisY, z = debrisZ}
								local debrisPower = splash_damage_options.debris_power
								local debrisDelay = (j - 1) * (effect.cookOffDuration / debrisCount) --Spread over cook-off duration
								timer.scheduleFunction(function(debrisArgs)
									local dPos = debrisArgs[1]
									local dPower = debrisArgs[2]
									debugMsg("Debris explosion at X: " .. string.format("%.0f", dPos.x) .. ", Y: " .. string.format("%.0f", dPos.y) .. ", Z: " .. string.format("%.0f", dPos.z) .. " with power " .. dPower)
									trigger.action.explosion(dPos, dPower)
								end, {debrisPos, debrisPower}, timer.getTime() + effectIndex + debrisDelay)
							end
						end
					end


                                        processedCargoUnits[unitKey] = true
                                        processedUnitsGlobal[unitKey] = true
							effectIndex = effectIndex + 3  --3 secs spacing if not random
                            end
                                end
                            --Clear the queue after scheduling
                            cargoEffectsQueue = {}
                        end

                        debugMsg(msg)
                        env.info("SplashDamage Post-Explosion: " .. msg)
                    end, {finalPos, blastRadius, chosenTargets, weaponName, explosionPower}, timer.getTime() + 1)
                        end
                    end)
                    if not status then
                            debugMsg("Impact handling error for '" .. weaponName .. "': " .. err)
                        end
                    end, {explosionPoint, explosionPoint, explosionPower, isShapedCharge, blastRadius, chosenTargets, wpnData.name, wpnData}, timer.getTime() + 0.1)
                else
                    debugMsg("Explosion skipped due to ordnance protection for '" .. wpnData.name .. "'")
                    if splash_damage_options.larger_explosions then
                        table.insert(recentExplosions, { pos = explosionPoint, time = timer.getTime(), radius = blastRadius })
                        debugMsg("Skipped explosion logged for snap check for '" .. wpnData.name .. "': X: " .. explosionPoint.x .. ", Y: " .. explosionPoint.y .. ", Z: " .. explosionPoint.z .. ", Time: " .. timer.getTime())
                    end
                end
                table.insert(weaponsToRemove, wpn_id_)
            end
        end)
        if not status then
            debugMsg("Error in track_wpns for '" .. (wpnData.name or "unknown weapon") .. "': " .. err)
        end
    end
    --Perform all removals after iteration
    for _, id in ipairs(weaponsToRemove) do
        tracked_weapons[id] = nil
end
    return timer.getTime() + refreshRate
end
function onWpnEvent(event)
        if event.id == world.event.S_EVENT_SHOT then
        if event.weapon then
                local ordnance = event.weapon
                local typeName = trim(ordnance:getTypeName())
                env.info("Weapon fired: [" .. typeName .. "]")
                debugMsg("Weapon fired: [" .. typeName .. "]")
  
                if string.find(typeName, "weapons.shells") then 
                    debugMsg("Event shot, but not tracking: " .. typeName)
                env.info("SplashDamage: event shot, but not tracking: " .. typeName .. " (" .. event.initiator:getTypeName() .. ")")
                    return
                end
  
                if not explTable[typeName] then
                env.info("SplashDamage: " .. typeName .. " missing from script (" .. event.initiator:getTypeName() .. ")")
                    if splash_damage_options.weapon_missing_message == true then
                        trigger.action.outText("SplashDamage: " .. typeName .. " missing from script (" .. (event.initiator and event.initiator:isExist() and event.initiator:getTypeName() or "no initiator") .. ")", 3)
  --                  if mist and mist.utils and mist.utils.tableShow then --Only if MiST is present
 --                       local success, desc = pcall(mist.utils.tableShow, ordnance:getDesc())
 --                       if success then
--                            debugMsg("desc for [" .. typeName .. "]: " .. desc)
 --                       else
 --                           debugMsg("Could not retrieve description for [" .. typeName .. "]. Object may no longer exist.")
 --                       end
--                    end
                        env.info("Current keys in explTable:")
                        for k, v in pairs(explTable) do
                            env.info("Key: [" .. k .. "]")
                        end
                    end
                end
  
            if (ordnance:getDesc().category ~= 0) and event.initiator then
                    if ordnance:getDesc().category == 1 then
                        if (ordnance:getDesc().MissileCategory ~= 1 and ordnance:getDesc().MissileCategory ~= 2) then
                        tracked_weapons[event.weapon.id_] = { wpn = ordnance, init = event.initiator:getName(), pos = ordnance:getPoint(), dir = ordnance:getPosition().x, name = typeName, speed = ordnance:getVelocity(), cat = ordnance:getCategory() }
                    end
                else
                    tracked_weapons[event.weapon.id_] = { wpn = ordnance, init = event.initiator:getName(), pos = ordnance:getPoint(), dir = ordnance:getPosition().x, name = typeName, speed = ordnance:getVelocity(), cat = ordnance:getCategory() }
                end
            end
        end
    end
end
  
local function protectedCall(...)
    local status, retval = pcall(...)
    if not status then
        env.warning("Splash damage script error... gracefully caught! " .. retval, true)
    end
end
  
function WpnHandler:onEvent(event)
    protectedCall(onWpnEvent, event)
end
  
function explodeObject(args)
    local point = args[1]
    local distance = args[2]
    local power = args[3]
    trigger.action.explosion(point, power)
end
  
function blastWave(_point, _radius, weapon, power, isShapedCharge)
    if isShapedCharge then
        _radius = _radius * splash_damage_options.shaped_charge_multiplier
    end
    if splash_damage_options.use_dynamic_blast_radius then
        local dynamicRadius = math.pow(power, 1/3) * 5 * splash_damage_options.dynamic_blast_radius_modifier
        if isShapedCharge then
            _radius = dynamicRadius * splash_damage_options.shaped_charge_multiplier
        else
            _radius = dynamicRadius
        end
    end
    
    local foundUnits = {}
    local volS = {
        id = world.VolumeType.SPHERE,
        params = {
            point = _point,
            radius = _radius
        }
    }
  
    local ifFound = function(foundObject, val)
        if foundObject:getDesc().category == Unit.Category.GROUND_UNIT and foundObject:getCategory() == Object.Category.UNIT then
            foundUnits[#foundUnits + 1] = foundObject
        end
        if foundObject:getDesc().category == Unit.Category.GROUND_UNIT then
            if splash_damage_options.blast_stun == true then
                --suppressUnit(foundObject, 2, weapon)
            end
        end
        if splash_damage_options.wave_explosions then
            local obj = foundObject
            local obj_location = obj:getPoint()
            local dist = getDistance(_point, obj_location)
            local timing = dist / 500
            if obj:isExist() and tableHasKey(obj:getDesc(), "box") then
                local length = (obj:getDesc().box.max.x + math.abs(obj:getDesc().box.min.x))
                local height = (obj:getDesc().box.max.y + math.abs(obj:getDesc().box.min.y))
                local depth = (obj:getDesc().box.max.z + math.abs(obj:getDesc().box.min.z))
                local _length = length
                local _depth = depth
                if depth > length then 
                    _length = depth 
                    _depth = length
                end
                local surface_distance = dist - _depth / 2
                local scaled_power_factor = 0.006 * power + 1
                local intensity = (power * scaled_power_factor) / (4 * math.pi * surface_distance^2)
                local surface_area = _length * height
                local damage_for_surface = intensity * surface_area
                if damage_for_surface > splash_damage_options.cascade_damage_threshold then
                    local explosion_size = damage_for_surface
                    if obj:getDesc().category == Unit.Category.STRUCTURE then
                        explosion_size = intensity * splash_damage_options.static_damage_boost
                    end
                    if explosion_size > power then explosion_size = power end
                    local triggerExplosion = false
                    if splash_damage_options.always_cascade_explode then
                        triggerExplosion = true
                    else
                        if obj:getDesc().life then
                            local healthPercent = (obj:getLife() / obj:getDesc().life) * 100
                            if healthPercent <= splash_damage_options.cascade_explode_threshold then
                                triggerExplosion = true
                            end
                            --Queue cargo effects for units below
                            local cargoData = cargoUnits[obj:getTypeName()]
                            if cargoData and healthPercent <= splash_damage_options.cargo_damage_threshold and splash_damage_options.enable_cargo_effects then
                                local cargoPower = power * cargoData.cargoExplosionMult
                                table.insert(cargoEffectsQueue, {
                                    name = obj:getTypeName(),
                                    distance = dist,
                                    coords = obj_location,
                                    power = cargoPower,
                                    explosion = cargoData.cargoExplosion,
                                    cookOff = cargoData.cargoCookOff,
                                    cookOffCount = cargoData.cookOffCount,
                                    cookOffPower = cargoData.cookOffPower,
                                    cookOffDuration = cargoData.cookOffDuration,
                                    cookOffRandomTiming = cargoData.cookOffRandomTiming,
                                    cookOffPowerRandom = cargoData.cookOffPowerRandom,
                                    isTanker = cargoData.isTanker,
                                    flameSize = cargoData.flameSize,
                                    flameDuration = cargoData.flameDuration
                                })
                            end
                        else
                            triggerExplosion = true
                        end
                        if not triggerExplosion and obj:getDesc().category == Unit.Category.GROUND_UNIT then
                            local health = obj:getLife() or 0
                            if health <= 0 then
                                triggerExplosion = true
                            end
                        end
                    end
                    if triggerExplosion then
                        timer.scheduleFunction(explodeObject, {obj_location, dist, explosion_size * splash_damage_options.cascade_scaling}, timer.getTime() + timing)
                    end
                end
            end
        end
        return true
    end
  
    world.searchObjects(Object.Category.UNIT, volS, ifFound)
    world.searchObjects(Object.Category.STATIC, volS, ifFound)
    world.searchObjects(Object.Category.SCENERY, volS, ifFound)
    world.searchObjects(Object.Category.CARGO, volS, ifFound)
    if splash_damage_options.damage_model then
        timer.scheduleFunction(modelUnitDamage, foundUnits, timer.getTime() + 1.5)
    end
end
  
function modelUnitDamage(units)
    for i, unit in ipairs(units) do
        if unit:isExist() then
            local health = (unit:getLife() / unit:getDesc().life) * 100
            if unit:hasAttribute("Infantry") and health > 0 then
                if health <= splash_damage_options.infantry_cant_fire_health then
                    unit:getController():setOption(AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD)
                end
            end
            if unit:getDesc().category == Unit.Category.GROUND_UNIT and (not unit:hasAttribute("Infantry")) and health > 0 then
                if health <= splash_damage_options.unit_cant_fire_health then
                    unit:getController():setOption(AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD)
                    gameMsg(unit:getTypeName() .. " weapons disabled")
                end
                if health <= splash_damage_options.unit_disabled_health and health > 0 then
                    unit:getController():setTask({id = 'Hold', params = {}})
                    unit:getController():setOnOff(false)
                    gameMsg(unit:getTypeName() .. " disabled")
                end
            end
        end
    end
end

function updateSplashDamageSetting(setting, increment)
    if not splash_damage_options[setting] then
        env.info("Error: Setting " .. setting .. " does not exist.")
        return
    end

    local newValue = math.max(0, splash_damage_options[setting] + increment)
    env.info("Updating " .. setting .. " from " .. tostring(splash_damage_options[setting]) .. " to " .. tostring(newValue))
    splash_damage_options[setting] = newValue
    trigger.action.outText("Updated " .. setting .. " to: " .. tostring(splash_damage_options[setting]), 5)
end

function toggleSplashDamageSetting(setting)
    splash_damage_options[setting] = not splash_damage_options[setting]
    trigger.action.outText("Toggled " .. setting .. " to: " .. tostring(splash_damage_options[setting]), 5)

    if setting == "enable_radio_menu" then
        if splash_damage_options.enable_radio_menu then
            addSplashDamageMenu()
        else
            missionCommands.removeItem(splash_damage_menu)
            splash_damage_menu = nil
        end
    end
end

function addValueAdjustmentCommands(menu, setting)
    missionCommands.addCommand("+0.1", menu, updateSplashDamageSetting, setting, 0.1)
    missionCommands.addCommand("+1", menu, updateSplashDamageSetting, setting, 1)
    missionCommands.addCommand("+10", menu, updateSplashDamageSetting, setting, 10)
    missionCommands.addCommand("+100", menu, updateSplashDamageSetting, setting, 100)

    missionCommands.addCommand("-0.1", menu, updateSplashDamageSetting, setting, -0.1)
    missionCommands.addCommand("-1", menu, updateSplashDamageSetting, setting, -1)
    missionCommands.addCommand("-10", menu, updateSplashDamageSetting, setting, -10)
    missionCommands.addCommand("-100", menu, updateSplashDamageSetting, setting, -100)
end

function exitSplashDamageMenu()
    if splash_damage_menu then
        missionCommands.removeItem(splash_damage_menu)
        splash_damage_menu = nil
    end
end

function addSplashDamageMenu()
    if not splash_damage_options.enable_radio_menu then return end

    if splash_damage_menu then
        missionCommands.removeItem(splash_damage_menu)
    end

    splash_damage_menu = missionCommands.addSubMenu("Splash Damage Settings")

    --Page 1: Debug & General Settings
    local debugGeneralMenu = missionCommands.addSubMenu("Debug & General Settings", splash_damage_menu)
    missionCommands.addCommand("Toggle Game Messages", debugGeneralMenu, toggleSplashDamageSetting, "game_messages")
    missionCommands.addCommand("Toggle Debug Messages", debugGeneralMenu, toggleSplashDamageSetting, "debug")
    missionCommands.addCommand("Toggle Weapon Missing Messages", debugGeneralMenu, toggleSplashDamageSetting, "weapon_missing_message")
    missionCommands.addCommand("Toggle Pre-Explosion Debug", debugGeneralMenu, toggleSplashDamageSetting, "track_pre_explosion_debug")
    missionCommands.addCommand("Toggle Damage Model", debugGeneralMenu, toggleSplashDamageSetting, "damage_model")
    missionCommands.addCommand("Toggle Blast Stun", debugGeneralMenu, toggleSplashDamageSetting, "blast_stun")
    local unitDisabledMenu = missionCommands.addSubMenu("Unit Disabled Health", debugGeneralMenu)
    addValueAdjustmentCommands(unitDisabledMenu, "unit_disabled_health")
    local unitCantFireMenu = missionCommands.addSubMenu("Unit Cant Fire Health", debugGeneralMenu)
    addValueAdjustmentCommands(unitCantFireMenu, "unit_cant_fire_health")
    local infantryCantFireMenu = missionCommands.addSubMenu("Infantry Cant Fire Health", debugGeneralMenu)
    addValueAdjustmentCommands(infantryCantFireMenu, "infantry_cant_fire_health")
    local rocketMultiplierMenu = missionCommands.addSubMenu("Rocket Multiplier", debugGeneralMenu)
    addValueAdjustmentCommands(rocketMultiplierMenu, "rocket_multiplier")
    --Page 2: Explosion
    local explosionCargoMenu = missionCommands.addSubMenu("Explosion Settings", splash_damage_menu)
    local staticDamageMenu = missionCommands.addSubMenu("Static Damage Boost", explosionCargoMenu)
    addValueAdjustmentCommands(staticDamageMenu, "static_damage_boost")
    missionCommands.addCommand("Toggle Wave Explosions", explosionCargoMenu, toggleSplashDamageSetting, "wave_explosions")
    missionCommands.addCommand("Toggle Larger Explosions", explosionCargoMenu, toggleSplashDamageSetting, "larger_explosions")
    local blastRadiusMenu = missionCommands.addSubMenu("Blast Search Radius", explosionCargoMenu)
    addValueAdjustmentCommands(blastRadiusMenu, "blast_search_radius")
    local cascadeThresholdMenu = missionCommands.addSubMenu("Cascade Damage Threshold", explosionCargoMenu)
    addValueAdjustmentCommands(cascadeThresholdMenu, "cascade_damage_threshold")
    local overallScalingMenu = missionCommands.addSubMenu("Overall Scaling", explosionCargoMenu)
    addValueAdjustmentCommands(overallScalingMenu, "overall_scaling")
    missionCommands.addCommand("Toggle Shaped Charge Effects", explosionCargoMenu, toggleSplashDamageSetting, "apply_shaped_charge_effects")
    local shapedChargeMenu = missionCommands.addSubMenu("Shaped Charge Multiplier", explosionCargoMenu)
    addValueAdjustmentCommands(shapedChargeMenu, "shaped_charge_multiplier")
    missionCommands.addCommand("Toggle Dynamic Blast Radius", explosionCargoMenu, toggleSplashDamageSetting, "use_dynamic_blast_radius")
    local dynamicBlastMenu = missionCommands.addSubMenu("Dynamic Blast Radius Modifier", explosionCargoMenu)
    addValueAdjustmentCommands(dynamicBlastMenu, "dynamic_blast_radius_modifier")
    local cascadeScalingMenu = missionCommands.addSubMenu("Cascade Scaling", explosionCargoMenu)
    addValueAdjustmentCommands(cascadeScalingMenu, "cascade_scaling")
    local cascadeExplodeThresholdMenu = missionCommands.addSubMenu("Cascade Explode Threshold", explosionCargoMenu)
    addValueAdjustmentCommands(cascadeExplodeThresholdMenu, "cascade_explode_threshold")


    --Page 3: Cargo and Ordnance Protection 
    local explosionCargoMenu = missionCommands.addSubMenu("Cargo and Ordnance", splash_damage_menu)
    missionCommands.addCommand("Toggle Always Cascade Explode", explosionCargoMenu, toggleSplashDamageSetting, "always_cascade_explode")
    missionCommands.addCommand("Toggle Tracking & Cargo Effects", explosionCargoMenu, toggleSplashDamageSetting, "track_pre_explosion")
    local cargoThresholdMenu = missionCommands.addSubMenu("Cargo Damage Threshold", explosionCargoMenu)
    addValueAdjustmentCommands(cargoThresholdMenu, "cargo_damage_threshold")
    missionCommands.addCommand("Toggle Ordnance Protection", explosionCargoMenu, toggleSplashDamageSetting, "ordnance_protection")
    local ordnanceRadiusMenu = missionCommands.addSubMenu("Ordnance Protection Radius", explosionCargoMenu)
    addValueAdjustmentCommands(ordnanceRadiusMenu, "ordnance_protection_radius")
    missionCommands.addCommand("Toggle Snap To Ground If Destroyed By LE", explosionCargoMenu, toggleSplashDamageSetting, "snap_to_ground_if_destroyed_by_large_explosion")
	
	--Page 4: Debris Settings 
    local debrisMenu = missionCommands.addSubMenu("Debris Settings", splash_damage_menu)
    missionCommands.addCommand("Toggle Debris Effects", debrisMenu, toggleSplashDamageSetting, "debris_effects")
    local debrisCountMinMenu = missionCommands.addSubMenu("Min Debris Count", debrisMenu)
    addValueAdjustmentCommands(debrisCountMinMenu, "debris_count_min")
    local debrisCountMaxMenu = missionCommands.addSubMenu("Max Debris Count", debrisMenu)
    addValueAdjustmentCommands(debrisCountMaxMenu, "debris_count_max")
    local debrisDistanceMenu = missionCommands.addSubMenu("Max Debris Distance", debrisMenu)
    addValueAdjustmentCommands(debrisDistanceMenu, "debris_max_distance")
    local debrisPowerMenu = missionCommands.addSubMenu("Debris Power", debrisMenu)
    addValueAdjustmentCommands(debrisPowerMenu, "debris_power")

    --Page 5: Cluster Settings
    local clusterMenu = missionCommands.addSubMenu("Cluster Settings", splash_damage_menu)
    missionCommands.addCommand("Toggle Cluster Enabled", clusterMenu, toggleSplashDamageSetting, "cluster_enabled")
    local clusterBaseLengthMenu = missionCommands.addSubMenu("Cluster Base Length", clusterMenu)
    addValueAdjustmentCommands(clusterBaseLengthMenu, "cluster_base_length")
    local clusterBaseWidthMenu = missionCommands.addSubMenu("Cluster Base Width", clusterMenu)
    addValueAdjustmentCommands(clusterBaseWidthMenu, "cluster_base_width")
    local clusterMaxLengthMenu = missionCommands.addSubMenu("Cluster Max Length", clusterMenu)
    addValueAdjustmentCommands(clusterMaxLengthMenu, "cluster_max_length")
    local clusterMaxWidthMenu = missionCommands.addSubMenu("Cluster Max Width", clusterMenu)
    addValueAdjustmentCommands(clusterMaxWidthMenu, "cluster_max_width")
    local clusterMinLengthMenu = missionCommands.addSubMenu("Cluster Min Length", clusterMenu)
    addValueAdjustmentCommands(clusterMinLengthMenu, "cluster_min_length")
    local clusterMinWidthMenu = missionCommands.addSubMenu("Cluster Min Width", clusterMenu)
    addValueAdjustmentCommands(clusterMinWidthMenu, "cluster_min_width")
    missionCommands.addCommand("Toggle Bomblet Reduction Modifier", clusterMenu, toggleSplashDamageSetting, "cluster_bomblet_reductionmodifier")
    local clusterBombletDamageMenu = missionCommands.addSubMenu("Bomblet Damage Modifier", clusterMenu)
    addValueAdjustmentCommands(clusterBombletDamageMenu, "cluster_bomblet_damage_modifier")
    
end

if (script_enable == 1) then
    gameMsg("SPLASH DAMAGE 3.0 SCRIPT RUNNING")
    env.info("SPLASH DAMAGE 2. SCRIPT RUNNING")

    timer.scheduleFunction(function()
        protectedCall(track_wpns)
        return timer.getTime() + refreshRate
    end, {}, timer.getTime() + refreshRate)

    world.addEventHandler(WpnHandler)
    addSplashDamageMenu()
end
