@jtff/miztemplate-lib 3.7.8 → 3.7.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (461) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +0 -0
  3. package/ci/build.js +0 -0
  4. package/ci/clean.js +0 -0
  5. package/ci/ftpupload.js +0 -0
  6. package/ci/gdrive-upload.js +0 -0
  7. package/ci/get-mizfiles.js +0 -0
  8. package/ci/getset-version.js +0 -0
  9. package/ci/inject-scripts.js +0 -0
  10. package/ci/prepare-nextversion.js +0 -0
  11. package/ci/release.js +0 -0
  12. package/ci/template-update.js +0 -0
  13. package/index.js +0 -0
  14. package/lib/jtff-lib-ci.js +0 -0
  15. package/lib/mizlib.js +0 -0
  16. package/lua/lib/Hercules_Cargo.lua +0 -0
  17. package/lua/lib/HoundElint.lua +10861 -10861
  18. package/lua/lib/Moose_.lua +0 -0
  19. package/lua/lib/Splash_Damage_main.lua +2151 -2151
  20. package/lua/lib/funkman.lua +0 -0
  21. package/lua/lib/mist.lua +0 -0
  22. package/lua/lib/skynet-iads-compiled.lua +0 -0
  23. package/lua/settings/settings-RAT.lua +0 -0
  24. package/lua/settings/settings-airboss.lua +0 -0
  25. package/lua/settings/settings-atis.lua +0 -0
  26. package/lua/settings/settings-awacs.lua +0 -0
  27. package/lua/settings/settings-awacsondemand.lua +0 -0
  28. package/lua/settings/settings-beacons.lua +0 -0
  29. package/lua/settings/settings-capwarzone.lua +0 -0
  30. package/lua/settings/settings-capzone.lua +0 -0
  31. package/lua/settings/settings-elint.lua +0 -0
  32. package/lua/settings/settings-fac_ranges.lua +0 -0
  33. package/lua/settings/settings-foxzone.lua +0 -0
  34. package/lua/settings/settings-funkman.lua +0 -0
  35. package/lua/settings/settings-global.lua +0 -0
  36. package/lua/settings/settings-intercept.lua +0 -0
  37. package/lua/settings/settings-logistics.lua +0 -0
  38. package/lua/settings/settings-ondemandawacs.lua +0 -0
  39. package/lua/settings/settings-ondemandtankers.lua +0 -0
  40. package/lua/settings/settings-pedros.lua +0 -0
  41. package/lua/settings/settings-qra.lua +24 -24
  42. package/lua/settings/settings-ranges.lua +0 -0
  43. package/lua/settings/settings-reapers.lua +0 -0
  44. package/lua/settings/settings-sams.lua +0 -0
  45. package/lua/settings/settings-skynet.lua +0 -0
  46. package/lua/settings/settings-tankers.lua +0 -0
  47. package/lua/settings/settings-training_ranges.lua +0 -0
  48. package/lua/src/010-root_menus.lua +0 -0
  49. package/lua/src/020-mission_functions.lua +0 -0
  50. package/lua/src/110-set_clients.lua +17 -14
  51. package/lua/src/120-tankers.lua +0 -0
  52. package/lua/src/130-airboss.lua +0 -0
  53. package/lua/src/135-pedro.lua +0 -0
  54. package/lua/src/140-beacons.lua +0 -0
  55. package/lua/src/150-awacs.lua +0 -0
  56. package/lua/src/160-atis.lua +0 -0
  57. package/lua/src/170-cap_zone_training.lua +0 -0
  58. package/lua/src/172-cap_zone_war.lua +0 -0
  59. package/lua/src/173-fox_zone_training.lua +0 -0
  60. package/lua/src/174-qra-scenario.lua +362 -362
  61. package/lua/src/176-random_air_traffic.lua +0 -0
  62. package/lua/src/178-training-intercept.lua +0 -0
  63. package/lua/src/180-logistics.lua +0 -0
  64. package/lua/src/190-ranges.lua +0 -0
  65. package/lua/src/191-sams.lua +0 -0
  66. package/lua/src/193-training_ranges.lua +0 -0
  67. package/lua/src/195-reaper-ondemand.lua +0 -0
  68. package/lua/src/196-fac_ranges.lua +0 -0
  69. package/lua/src/197-elint-ondemand.lua +0 -0
  70. package/lua/src/199-skynet.lua +0 -0
  71. package/lua/src/200-mission.lua +0 -0
  72. package/package.json +1 -1
  73. package/resources/radios/Caucasus/354th-caucasus.lua +0 -0
  74. package/resources/radios/Caucasus/494th-caucasus.lua +0 -0
  75. package/resources/radios/Caucasus/79th-SUFA-caucasus.lua +0 -0
  76. package/resources/radios/Caucasus/79th-caucasus.lua +0 -0
  77. package/resources/radios/Caucasus/ec25-D-caucasus.lua +0 -0
  78. package/resources/radios/Caucasus/ec25-caucasus.lua +0 -0
  79. package/resources/radios/Caucasus/vf84-a-caucasus.lua +0 -0
  80. package/resources/radios/Caucasus/vf84-b-caucasus.lua +0 -0
  81. package/resources/radios/Caucasus/vfa-131-caucasus.lua +0 -0
  82. package/resources/radios/Caucasus/vmfa-314-caucasus.lua +0 -0
  83. package/resources/radios/MarianaIslands/354th-marianas.lua +0 -0
  84. package/resources/radios/MarianaIslands/494th-marianas.lua +0 -0
  85. package/resources/radios/MarianaIslands/79th-marianas.lua +0 -0
  86. package/resources/radios/MarianaIslands/ec25-marianas.lua +0 -0
  87. package/resources/radios/MarianaIslands/vf84-a-marianas.lua +0 -0
  88. package/resources/radios/MarianaIslands/vf84-b-marianas.lua +0 -0
  89. package/resources/radios/MarianaIslands/vfa-131-marianas.lua +0 -0
  90. package/resources/radios/MarianaIslands/vmfa-314-marianas.lua +0 -0
  91. package/resources/radios/Nevada/354th-nttr.lua +0 -0
  92. package/resources/radios/Nevada/494th-nttr.lua +0 -0
  93. package/resources/radios/Nevada/79th-nttr.lua +0 -0
  94. package/resources/radios/Nevada/ec25-nttr.lua +0 -0
  95. package/resources/radios/Nevada/vf84-a-nttr.lua +0 -0
  96. package/resources/radios/Nevada/vf84-b-nttr.lua +0 -0
  97. package/resources/radios/Nevada/vfa-131-nttr.lua +0 -0
  98. package/resources/radios/Nevada/vmfa-314-nttr.lua +0 -0
  99. package/resources/radios/PersianGulf/354th-persian.lua +0 -0
  100. package/resources/radios/PersianGulf/3_30-persian.lua +0 -0
  101. package/resources/radios/PersianGulf/494th-persian.lua +0 -0
  102. package/resources/radios/PersianGulf/79th-persian.lua +0 -0
  103. package/resources/radios/PersianGulf/ec25-persian.lua +0 -0
  104. package/resources/radios/PersianGulf/vf84-a-persian.lua +0 -0
  105. package/resources/radios/PersianGulf/vf84-b-persian.lua +0 -0
  106. package/resources/radios/PersianGulf/vfa-131-persian.lua +0 -0
  107. package/resources/radios/PersianGulf/vmfa-314-persian.lua +0 -0
  108. package/resources/radios/SinaiMap/354th-sinai.lua +0 -0
  109. package/resources/radios/SinaiMap/494th-sinai.lua +0 -0
  110. package/resources/radios/SinaiMap/79th-SUFA-sinai.lua +0 -0
  111. package/resources/radios/SinaiMap/79th-sinai.lua +0 -0
  112. package/resources/radios/SinaiMap/ec25-D-sinai.lua +0 -0
  113. package/resources/radios/SinaiMap/ec25-sinai.lua +0 -0
  114. package/resources/radios/SinaiMap/vf84-a-sinai.lua +0 -0
  115. package/resources/radios/SinaiMap/vf84-b-sinai.lua +0 -0
  116. package/resources/radios/SinaiMap/vfa-131-sinai.lua +0 -0
  117. package/resources/radios/SinaiMap/vmfa-314-sinai.lua +0 -0
  118. package/resources/radios/Syria/354th-syria.lua +0 -0
  119. package/resources/radios/Syria/494th-syria.lua +0 -0
  120. package/resources/radios/Syria/79th-SUFA-syria.lua +0 -0
  121. package/resources/radios/Syria/79th-syria.lua +0 -0
  122. package/resources/radios/Syria/ec25-D-syria.lua +0 -0
  123. package/resources/radios/Syria/ec25-syria.lua +0 -0
  124. package/resources/radios/Syria/vf84-a-syria.lua +0 -0
  125. package/resources/radios/Syria/vf84-b-syria.lua +0 -0
  126. package/resources/radios/Syria/vfa-131-syria.lua +0 -0
  127. package/resources/radios/Syria/vmfa-314-syria.lua +0 -0
  128. package/resources/sounds/AICSAR/helodown.ogg +0 -0
  129. package/resources/sounds/AICSAR/initialnotok.ogg +0 -0
  130. package/resources/sounds/AICSAR/initialok.ogg +0 -0
  131. package/resources/sounds/AICSAR/pilotdown.ogg +0 -0
  132. package/resources/sounds/AICSAR/pilotinhelo.ogg +0 -0
  133. package/resources/sounds/AICSAR/pilotkia.ogg +0 -0
  134. package/resources/sounds/AICSAR/pilotrescued.ogg +0 -0
  135. package/resources/sounds/AIRBOSS/Airboss Soundfiles/AIRBOSS-Noise.ogg +0 -0
  136. package/resources/sounds/AIRBOSS/Airboss Soundfiles/AIRBOSS-RadioClick.ogg +0 -0
  137. package/resources/sounds/AIRBOSS/Airboss Soundfiles/AIRBOSS-SpinIt.ogg +0 -0
  138. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-BolterBolter.ogg +0 -0
  139. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-CallTheBall.ogg +0 -0
  140. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-Check.ogg +0 -0
  141. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-ClearedToLand.ogg +0 -0
  142. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-ComeLeft.ogg +0 -0
  143. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-ComeLeft_Loud.ogg +0 -0
  144. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-DepartAndReenter.ogg +0 -0
  145. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-ExpectHeavyWaveoff.ogg +0 -0
  146. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-ExpectSpot5.ogg +0 -0
  147. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-ExpectSpot75.ogg +0 -0
  148. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-Fast.ogg +0 -0
  149. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-Fast_Loud.ogg +0 -0
  150. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-FoulDeck.ogg +0 -0
  151. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-High.ogg +0 -0
  152. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-High_Loud.ogg +0 -0
  153. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-Idle.ogg +0 -0
  154. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-LongInTheGroove.ogg +0 -0
  155. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-Low.ogg +0 -0
  156. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-Low_Loud.ogg +0 -0
  157. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-N0.ogg +0 -0
  158. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-N1.ogg +0 -0
  159. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-N2.ogg +0 -0
  160. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-N3.ogg +0 -0
  161. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-N4.ogg +0 -0
  162. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-N5.ogg +0 -0
  163. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-N6.ogg +0 -0
  164. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-N7.ogg +0 -0
  165. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-N8.ogg +0 -0
  166. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-N9.ogg +0 -0
  167. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-PaddlesContact.ogg +0 -0
  168. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-Power.ogg +0 -0
  169. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-Power_Loud.ogg +0 -0
  170. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-RadioCheck.ogg +0 -0
  171. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-RightForLineup.ogg +0 -0
  172. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-RightForLineup_Loud.ogg +0 -0
  173. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-RogerBall.ogg +0 -0
  174. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-Slow.ogg +0 -0
  175. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-Slow_Loud.ogg +0 -0
  176. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-Stabilized.ogg +0 -0
  177. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-WaveOff.ogg +0 -0
  178. package/resources/sounds/AIRBOSS/Airboss Soundfiles/LSO-WelcomeAboard.ogg +0 -0
  179. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-Affirmative.ogg +0 -0
  180. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-Altimeter.ogg +0 -0
  181. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-BRC.ogg +0 -0
  182. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-CarrierTurnToHeading.ogg +0 -0
  183. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-Case.ogg +0 -0
  184. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-CharlieTime.ogg +0 -0
  185. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-ClearedForRecovery.ogg +0 -0
  186. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-DeckClosed.ogg +0 -0
  187. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-Degrees.ogg +0 -0
  188. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-Expected.ogg +0 -0
  189. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-FlyYourNeedles.ogg +0 -0
  190. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-HoldAtAngels.ogg +0 -0
  191. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-Hours.ogg +0 -0
  192. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-MarshalRadial.ogg +0 -0
  193. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-N0.ogg +0 -0
  194. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-N1.ogg +0 -0
  195. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-N2.ogg +0 -0
  196. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-N3.ogg +0 -0
  197. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-N4.ogg +0 -0
  198. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-N5.ogg +0 -0
  199. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-N6.ogg +0 -0
  200. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-N7.ogg +0 -0
  201. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-N8.ogg +0 -0
  202. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-N9.ogg +0 -0
  203. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-Negative.ogg +0 -0
  204. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-NewFB.ogg +0 -0
  205. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-Ops.ogg +0 -0
  206. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-Point.ogg +0 -0
  207. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-RadioCheck.ogg +0 -0
  208. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-Recovery.ogg +0 -0
  209. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-RecoveryOpsStopped.ogg +0 -0
  210. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-RecoveryPausedNotice.ogg +0 -0
  211. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-RecoveryPausedResumed.ogg +0 -0
  212. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-ReportSeeMe.ogg +0 -0
  213. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-ResumeRecovery.ogg +0 -0
  214. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-Roger.ogg +0 -0
  215. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-SayNeedles.ogg +0 -0
  216. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-StackFull.ogg +0 -0
  217. package/resources/sounds/AIRBOSS/Airboss Soundfiles/MARSHAL-StartingRecovery.ogg +0 -0
  218. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-Angels.ogg +0 -0
  219. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-Ball.ogg +0 -0
  220. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-BingoFuel.ogg +0 -0
  221. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-Commencing.ogg +0 -0
  222. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-For.ogg +0 -0
  223. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-GasAtDivert.ogg +0 -0
  224. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-GasAtTanker.ogg +0 -0
  225. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-Harrier.ogg +0 -0
  226. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-Hawkeye.ogg +0 -0
  227. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-Hornet.ogg +0 -0
  228. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-MarkingMoms.ogg +0 -0
  229. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-Marshal.ogg +0 -0
  230. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-N0.ogg +0 -0
  231. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-N1.ogg +0 -0
  232. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-N2.ogg +0 -0
  233. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-N3.ogg +0 -0
  234. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-N4.ogg +0 -0
  235. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-N5.ogg +0 -0
  236. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-N6.ogg +0 -0
  237. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-N7.ogg +0 -0
  238. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-N8.ogg +0 -0
  239. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-N9.ogg +0 -0
  240. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-Point.ogg +0 -0
  241. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-Skyhawk.ogg +0 -0
  242. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-State.ogg +0 -0
  243. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-Tomcat.ogg +0 -0
  244. package/resources/sounds/AIRBOSS/Airboss Soundfiles/PILOT-Viking.ogg +0 -0
  245. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-BolterBolter.ogg +0 -0
  246. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-CallTheBall.ogg +0 -0
  247. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-Check.ogg +0 -0
  248. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-ClearedToLand.ogg +0 -0
  249. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-ComeLeft.ogg +0 -0
  250. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-ComeLeft_Loud.ogg +0 -0
  251. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-DepartAndReenter.ogg +0 -0
  252. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-ExpectHeavyWaveoff.ogg +0 -0
  253. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-ExpectSpot5.ogg +0 -0
  254. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-ExpectSpot75.ogg +0 -0
  255. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-Fast.ogg +0 -0
  256. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-Fast_Loud.ogg +0 -0
  257. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-FoulDeck.ogg +0 -0
  258. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-High.ogg +0 -0
  259. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-High_Loud.ogg +0 -0
  260. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-Idle.ogg +0 -0
  261. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-LongInTheGroove.ogg +0 -0
  262. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-Low.ogg +0 -0
  263. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-Low_Loud.ogg +0 -0
  264. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-N0.ogg +0 -0
  265. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-N1.ogg +0 -0
  266. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-N2.ogg +0 -0
  267. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-N3.ogg +0 -0
  268. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-N4.ogg +0 -0
  269. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-N5.ogg +0 -0
  270. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-N6.ogg +0 -0
  271. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-N7.ogg +0 -0
  272. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-N8.ogg +0 -0
  273. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-N9.ogg +0 -0
  274. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-PaddlesContact.ogg +0 -0
  275. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-Power.ogg +0 -0
  276. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-Power_Loud.ogg +0 -0
  277. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-RadioCheck.ogg +0 -0
  278. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-RightForLineup.ogg +0 -0
  279. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-RightForLineup_Loud.ogg +0 -0
  280. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-RogerBall.ogg +0 -0
  281. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-Slow.ogg +0 -0
  282. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-Slow_Loud.ogg +0 -0
  283. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-Stabilized.ogg +0 -0
  284. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-WaveOff.ogg +0 -0
  285. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO FF/LSO-WelcomeAboard.ogg +0 -0
  286. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-BolterBolter.ogg +0 -0
  287. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-CallTheBall.ogg +0 -0
  288. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-Check.ogg +0 -0
  289. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-ClearedToLand.ogg +0 -0
  290. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-ComeLeft.ogg +0 -0
  291. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-ComeLeft_Loud.ogg +0 -0
  292. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-DepartAndReenter.ogg +0 -0
  293. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-ExpectHeavyWaveoff.ogg +0 -0
  294. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-ExpectSpot5.ogg +0 -0
  295. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-ExpectSpot75.ogg +0 -0
  296. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-Fast.ogg +0 -0
  297. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-Fast_Loud.ogg +0 -0
  298. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-FoulDeck.ogg +0 -0
  299. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-High.ogg +0 -0
  300. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-High_Loud.ogg +0 -0
  301. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-Idle.ogg +0 -0
  302. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-LongInTheGroove.ogg +0 -0
  303. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-Low.ogg +0 -0
  304. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-Low_Loud.ogg +0 -0
  305. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-N0.ogg +0 -0
  306. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-N1.ogg +0 -0
  307. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-N2.ogg +0 -0
  308. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-N3.ogg +0 -0
  309. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-N4.ogg +0 -0
  310. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-N5.ogg +0 -0
  311. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-N6.ogg +0 -0
  312. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-N7.ogg +0 -0
  313. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-N8.ogg +0 -0
  314. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-N9.ogg +0 -0
  315. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-PaddlesContact.ogg +0 -0
  316. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-Power.ogg +0 -0
  317. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-Power_Loud.ogg +0 -0
  318. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-RadioCheck.ogg +0 -0
  319. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-RightForLineup.ogg +0 -0
  320. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-RightForLineup_Loud.ogg +0 -0
  321. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-RogerBall.ogg +0 -0
  322. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-Slow.ogg +0 -0
  323. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-Slow_Loud.ogg +0 -0
  324. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-Stabilized.ogg +0 -0
  325. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-WaveOff.ogg +0 -0
  326. package/resources/sounds/AIRBOSS/Airboss Soundpack LSO Raynor/LSO-WelcomeAboard.ogg +0 -0
  327. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-Affirmative.ogg +0 -0
  328. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-Altimeter.ogg +0 -0
  329. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-BRC.ogg +0 -0
  330. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-CarrierTurnToHeading.ogg +0 -0
  331. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-Case.ogg +0 -0
  332. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-CharlieTime.ogg +0 -0
  333. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-ClearedForRecovery.ogg +0 -0
  334. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-DeckClosed.ogg +0 -0
  335. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-Degrees.ogg +0 -0
  336. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-Expected.ogg +0 -0
  337. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-FlyYourNeedles.ogg +0 -0
  338. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-HoldAtAngels.ogg +0 -0
  339. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-Hours.ogg +0 -0
  340. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-MarshalRadial.ogg +0 -0
  341. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-N0.ogg +0 -0
  342. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-N1.ogg +0 -0
  343. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-N2.ogg +0 -0
  344. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-N3.ogg +0 -0
  345. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-N4.ogg +0 -0
  346. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-N5.ogg +0 -0
  347. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-N6.ogg +0 -0
  348. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-N7.ogg +0 -0
  349. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-N8.ogg +0 -0
  350. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-N9.ogg +0 -0
  351. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-Negative.ogg +0 -0
  352. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-NewFB.ogg +0 -0
  353. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-Ops.ogg +0 -0
  354. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-Point.ogg +0 -0
  355. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-RadioCheck.ogg +0 -0
  356. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-Recovery.ogg +0 -0
  357. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-RecoveryOpsStopped.ogg +0 -0
  358. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-RecoveryPausedNotice.ogg +0 -0
  359. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-RecoveryPausedResumed.ogg +0 -0
  360. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-ReportSeeMe.ogg +0 -0
  361. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-ResumeRecovery.ogg +0 -0
  362. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-Roger.ogg +0 -0
  363. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-SayNeedles.ogg +0 -0
  364. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-StackFull.ogg +0 -0
  365. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal FF/MARSHAL-StartingRecovery.ogg +0 -0
  366. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-Affirmative.ogg +0 -0
  367. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-Altimeter.ogg +0 -0
  368. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-BRC.ogg +0 -0
  369. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-CarrierTurnToHeading.ogg +0 -0
  370. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-Case.ogg +0 -0
  371. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-CharlieTime.ogg +0 -0
  372. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-ClearedForRecovery.ogg +0 -0
  373. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-DeckClosed.ogg +0 -0
  374. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-Degrees.ogg +0 -0
  375. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-Expected.ogg +0 -0
  376. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-FlyYourNeedles.ogg +0 -0
  377. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-HoldAtAngels.ogg +0 -0
  378. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-Hours.ogg +0 -0
  379. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-MarshalRadial.ogg +0 -0
  380. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-N0.ogg +0 -0
  381. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-N1.ogg +0 -0
  382. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-N2.ogg +0 -0
  383. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-N3.ogg +0 -0
  384. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-N4.ogg +0 -0
  385. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-N5.ogg +0 -0
  386. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-N6.ogg +0 -0
  387. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-N7.ogg +0 -0
  388. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-N8.ogg +0 -0
  389. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-N9.ogg +0 -0
  390. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-Negative.ogg +0 -0
  391. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-NewFB.ogg +0 -0
  392. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-Ops.ogg +0 -0
  393. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-Point.ogg +0 -0
  394. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-RadioCheck.ogg +0 -0
  395. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-Recovery-2.ogg +0 -0
  396. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-Recovery.ogg +0 -0
  397. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-RecoveryOpsStopped.ogg +0 -0
  398. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-RecoveryPausedNotice.ogg +0 -0
  399. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-RecoveryPausedResumed.ogg +0 -0
  400. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-ReportSeeMe.ogg +0 -0
  401. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-ResumeRecovery.ogg +0 -0
  402. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-Roger.ogg +0 -0
  403. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-SayNeedles.ogg +0 -0
  404. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-StackFull.ogg +0 -0
  405. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Gabriella/MARSHAL-StartingRecovery.ogg +0 -0
  406. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-Affirmative.ogg +0 -0
  407. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-Altimeter.ogg +0 -0
  408. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-BRC.ogg +0 -0
  409. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-CarrierTurnToHeading.ogg +0 -0
  410. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-Case.ogg +0 -0
  411. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-CharlieTime.ogg +0 -0
  412. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-ClearedForRecovery.ogg +0 -0
  413. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-DeckClosed.ogg +0 -0
  414. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-Degrees.ogg +0 -0
  415. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-Expected.ogg +0 -0
  416. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-FlyYourNeedles.ogg +0 -0
  417. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-HoldAtAngels.ogg +0 -0
  418. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-Hours.ogg +0 -0
  419. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-MarshalRadial.ogg +0 -0
  420. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-N0.ogg +0 -0
  421. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-N1.ogg +0 -0
  422. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-N2.ogg +0 -0
  423. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-N3.ogg +0 -0
  424. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-N4.ogg +0 -0
  425. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-N5.ogg +0 -0
  426. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-N6.ogg +0 -0
  427. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-N7.ogg +0 -0
  428. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-N8.ogg +0 -0
  429. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-N9.ogg +0 -0
  430. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-Negative.ogg +0 -0
  431. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-NewFB.ogg +0 -0
  432. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-Ops.ogg +0 -0
  433. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-Point.ogg +0 -0
  434. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-RadioCheck.ogg +0 -0
  435. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-Recovery.ogg +0 -0
  436. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-RecoveryOpsStopped.ogg +0 -0
  437. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-RecoveryPausedNotice.ogg +0 -0
  438. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-RecoveryPausedResumed.ogg +0 -0
  439. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-ReportSeeMe.ogg +0 -0
  440. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-ResumeRecovery.ogg +0 -0
  441. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-Roger.ogg +0 -0
  442. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-SayNeedles.ogg +0 -0
  443. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-StackFull.ogg +0 -0
  444. package/resources/sounds/AIRBOSS/Airboss Soundpack Marshal Raynor/MARSHAL-StartingRecovery.ogg +0 -0
  445. package/resources/sounds/CTLD CSAR/beacon.ogg +0 -0
  446. package/resources/sounds/CTLD CSAR/beaconsilent.ogg +0 -0
  447. package/resources/sounds/Misc/.gitkeep +0 -0
  448. package/resources/sounds/Misc/2_Bips.ogg +0 -0
  449. package/resources/sounds/Misc/Bip.ogg +0 -0
  450. package/resources/sounds/Misc/SCRAMBLE QRA.ogg +0 -0
  451. package/resources/sounds/Misc/crash_wood.ogg +0 -0
  452. package/scripts/build.js +0 -0
  453. package/scripts/clean.js +0 -0
  454. package/scripts/ftpupload.js +0 -0
  455. package/scripts/gdrive-upload.js +0 -0
  456. package/scripts/get-mizfiles.js +0 -0
  457. package/scripts/getset-version.js +0 -0
  458. package/scripts/inject-scripts.js +0 -0
  459. package/scripts/prepare-nextversion.js +0 -0
  460. package/scripts/release.js +0 -0
  461. package/scripts/template-update.js +0 -0
@@ -1,2151 +1,2151 @@
1
- --[[
2
- 04 April 2025 (Stevey666) - 3.1
3
- - Set default cluster munitions option to false, set this to true in the options if you want it
4
- - Added missing radio commands for Cascade Scaling
5
- - Adjust default cascading to 2 (from 1)
6
- - Adjusted Ural-4320 to be a tanker and ammo carrier for cargo cookoff
7
- - Prevent weapons not in the list from being tracked
8
- - Moved some logging behind the debug mode flag
9
- - Ordnance Protection, added a max height ordnance protection will snap explosion to ground
10
- - Ordnance Protection, fixed enable/disable option
11
- - Added Giant Explosion feature
12
- - Adjusted some hydra70 values on recom. from ETBSmorgan
13
-
14
-
15
- 09 March 2025 (Stevey666) - 3.0
16
- - 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
17
- - 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)
18
- - Added vehicle scanning around a weapon to allow for..
19
- - Cook offs - you can set vehicles that will cook off i.e ammo trucks, number of explosions, debris explosions, power adjustable
20
- - Fuel/Tanker explosion and flames - when a fuel tanker blows it will through up a big flame - adjustable in the scripts
21
- - Added section for vehicles for the above
22
- - Added radio commands for everything
23
- - Added in cluster munitions changes (note: barely tested, its not particularly accurate or that useful at this point so leaving disabled)
24
- - Potential bug - testing, stacking too many units together may cause a MIST error if you're using mist
25
-
26
- - Setting this as 3.0 as I'd like to be responsive to requests, updates etc - creating a new fork to track this
27
-
28
-
29
- 10 Feb 2025 (Stevey666) - 2.0.7
30
- - Fixed AGM 154/Adjusted weapons
31
- - Added overall damage scaling
32
- - Added modifier for shaped charges (i.e. Mavericks), adjusted weapon list accordingly
33
- - Adjusted blast radius and damage calculations, created option for dynamic blast radius
34
- - Adjusted cascading explosions, added additional "cascade_scaling" modifier and cascade explode threshold modifier. Units wont explode on initial impact unless health drops under threshold
35
- - Added always_cascade_explode option so you can set it to the old ways of making everything in the blast wave go kaboom
36
- - Added in game radio commands to change the new options ingame without having to reload everything in mission editor to test it out
37
-
38
- 12 November 2024 (by JGi | Quéton 1-1)
39
- - Tweak down radius 100>90 (Thanks Arhibeau)
40
- - Tweak down some values
41
-
42
- 20 January 2024 (by JGi | Quéton 1-1)
43
- - Added missing weapons to explTable
44
- - Sort weapons in explTable by type
45
- - Added aircraft type in log when missing
46
-
47
- 03 May 2023 (KERV)
48
- Correction AGM 154 (https://forum.dcs.world/topic/289290-splash-damage-20-script-make-explosions-better/page/5/#comment-5207760)
49
-
50
- 06 March 2023 (Kerv)
51
- - Add some data for new ammunition
52
-
53
- 16 April 2022
54
- spencershepard (GRIMM):
55
- - Added new/missing weapons to explTable
56
- - Added new option rocket_multiplier
57
-
58
- 31 December 2021
59
- spencershepard (GRIMM):
60
- - Added many new weapons
61
- - Added filter for weapons.shells events
62
- - Fixed mission weapon message option
63
- - Changed default for damage_model option
64
-
65
- 21 December 2021
66
- spencershepard (GRIMM):
67
- SPLASH DAMAGE 2.0:
68
- - Added blast wave effect to add timed and scaled secondary explosions on top of game objects
69
- - Object geometry within blast wave changes damage intensity
70
- - Damage boost for structures since they are hard to kill, even if very close to large explosions
71
- - Increased some rocket values in explTable
72
- - Missing weapons from explTable will display message to user and log to DCS.log so that we can add what's missing
73
- - Damage model for ground units that will disable their weapons and ability to move with partial damage before they are killed
74
- - Added options table to allow easy adjustments before release
75
- - General refactoring and restructure
76
-
77
- 28 October 2020
78
- FrozenDroid:
79
- - Uncommented error logging, actually made it an error log which shows a message box on error.
80
- - Fixed the too restrictive weapon filter (took out the HE warhead requirement)
81
-
82
- 2 October 2020
83
- FrozenDroid:
84
- - Added error handling to all event handler and scheduled functions. Lua script errors can no longer bring the server down.
85
- - 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...?)
86
- --]]
87
-
88
- ----[[ ##### SCRIPT CONFIGURATION ##### ]]----
89
- splash_damage_options = {
90
- --debug options
91
- ["game_messages"] = false, --enable some messages on screen
92
- ["debug"] = false, --enable debugging messages
93
- ["weapon_missing_message"] = false, --false disables messages alerting you to weapons missing from the explTable
94
- ["track_pre_explosion_debug"] = false, --Toggle to enable/disable pre-explosion tracking debugging
95
-
96
- ["enable_radio_menu"] = true, --enables the in-game radio menu for modifying settings
97
-
98
- ["static_damage_boost"] = 2000, --apply extra damage to Unit.Category.STRUCTUREs with wave explosions
99
- ["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
100
- ["larger_explosions"] = true, --secondary explosions on top of weapon impact points, dictated by the values in the explTable
101
- ["damage_model"] = true, --allow blast wave to affect ground unit movement and weapons
102
- ["blast_search_radius"] = 90, --this is the max size of any blast wave radius, since we will only find objects within this zone
103
- ["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.
104
- ["blast_stun"] = false, --not implemented
105
- ["unit_disabled_health"] = 30, --if health is below this value after our explosions, disable its movement
106
- ["unit_cant_fire_health"] = 40, --if health is below this value after our explosions, set ROE to HOLD to simulate damage weapon systems
107
- ["infantry_cant_fire_health"] = 60, --if health is below this value after our explosions, set ROE to HOLD to simulate severe injury
108
-
109
- ["rocket_multiplier"] = 1.3, --multiplied by the explTable value for rockets
110
- ["overall_scaling"] = 1, --overall scaling for explosive power
111
-
112
- ["apply_shaped_charge_effects"] = true, --apply reduction in blastwave etc for shaped charge munitions
113
- ["shaped_charge_multiplier"] = 0.2, --multiplier that reduces blast radius and explosion power for shaped charge munitions.
114
-
115
- ["use_dynamic_blast_radius"] = true, --if true, blast radius is calculated from explosion power; if false, blast_search_radius (90) is used
116
- ["dynamic_blast_radius_modifier"] = 2, --multiplier for the blast radius
117
-
118
- ["cascade_scaling"] = 2, --multiplier for secondary (cascade) blast damage, 1 damage fades out too soon, 2 or 3 damage seems a good balance
119
- ["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
120
- ["always_cascade_explode"] = false, --switch if you want everything to explode like with the original script
121
-
122
-
123
- --track_pre_explosion/enable_cargo_effects should both be the same value
124
- ["track_pre_explosion"] = true, --Toggle to enable/disable pre-explosion tracking
125
- ["enable_cargo_effects"] = true, --Toggle for enabling/disabling cargo explosions and cook-offs
126
- ["cargo_damage_threshold"] = 60, --Health % below which cargo explodes (0 = destroyed only)
127
- ["debris_effects"] = true, --Enable debris from cargo cook-offs
128
- ["debris_power"] = 1, --Power of each debris explosion
129
- ["debris_count_min"] = 6, --Minimum debris pieces per cook-off
130
- ["debris_count_max"] = 12, --Maximum debris pieces per cook-off
131
- ["debris_max_distance"] = 10, --Max distance debris can travel (meters), the min distance from the vehicle will be 10% of this
132
-
133
- ["ordnance_protection"] = true, --Toggle ordinance protection features
134
- ["ordnance_protection_radius"] = 10, --Distance in meters to protect nearby bombs
135
- ["detect_ordnance_destruction"] = true, --Toggle detection of ordnance destroyed by large explosions
136
- ["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
137
- ["max_snapped_height"] = 80, --max height it will snap to ground from
138
- ["recent_large_explosion_snap"] = true, --enable looking for a recent large_explosion generated by the script
139
- ["recent_large_explosion_range"] = 100, --range its looking for in meters for a recent large_explosion generated by the script
140
- ["recent_large_explosion_time"] = 4, --in seconds how long ago there was a recent large_explosion generated by the script
141
-
142
- --Cluster bomb settings
143
- ["cluster_enabled"] = false,
144
- ["cluster_base_length"] = 150, --Base forward spread (meters)
145
- ["cluster_base_width"] = 200, --Base lateral spread (meters)
146
- ["cluster_max_length"] = 300, --Max forward spread (meters)
147
- ["cluster_max_width"] = 400, --Max lateral spread (meters)
148
- ["cluster_min_length"] = 100, --Min forward spread
149
- ["cluster_min_width"] = 150, --Min lateral spread
150
- ["cluster_bomblet_reductionmodifier"] = true, --Use equation to reduce number of bomblets (to make it look better)
151
- ["cluster_bomblet_damage_modifier"] = 1, --Adjustable global modifier for bomblet explosive power
152
-
153
- --Giant Explosion Options - Remember, any target you want to blow up needs to be named "GiantExplosionTarget(X)" (X) being any value/name etc
154
- ["giant_explosion_enabled"] = true, --Toggle to enable/disable Giant Explosion
155
- ["giant_explosion_power"] = 6000, --Power in kg of TNT (default 8 tons)
156
- ["giant_explosion_scale"] = 1, --Size scale factor (default 1)
157
- ["giant_explosion_duration"] = 3.0, --Total duration in seconds (default 3s)
158
- ["giant_explosion_count"] = 250, --Number of explosions (default 300)
159
- ["giant_explosion_target_static"] = true, --Toggle to true for static targets (store position once), false for dynamic (update every second)
160
- ["giant_explosion_poll_rate"] = 1, --Polling rate in seconds for flag checks (default 1s)
161
- }
162
-
163
- local script_enable = 1
164
- refreshRate = 0.1
165
- ----[[ ##### End of SCRIPT CONFIGURATION ##### ]]----
166
-
167
- --Helper function: Trim whitespace.
168
- local function trim(s)
169
- return s:match("^%s*(.-)%s*$")
170
- end
171
-
172
- cargoUnits = {
173
-
174
- --[[
175
- flamesize:
176
-
177
- 1 = small smoke and fire
178
- 2 = medium smoke and fire
179
- 3 = large smoke and fire
180
- 4 = huge smoke and fire
181
- 5 = small smoke
182
- 6 = medium smoke
183
- 7 = large smoke
184
- 8 = huge smoke
185
- ]]--
186
-
187
- --1) M92 R11 Volvo driveable (Fuel Truck Tanker)
188
- ["r11_volvo_drivable"] = {
189
- cargoExplosion = true,
190
- cargoExplosionMult = 2.0,
191
- cargoExplosionPower = 200,
192
- cargoCookOff = false,
193
- cookOffCount = 0,
194
- cookOffPower = 0,
195
- cookOffDuration = 0,
196
- cookOffRandomTiming = false,
197
- cookOffPowerRandom = 50,
198
- isTanker = true,
199
- flameSize = 3,
200
- flameDuration = 5,
201
- },
202
-
203
- --2) Refueler ATMZ-5
204
- ["ATMZ-5"] = {
205
- cargoExplosion = true,
206
- cargoExplosionMult = 2.0,
207
- cargoExplosionPower = 200,
208
- cargoCookOff = false,
209
- cookOffCount = 0,
210
- cookOffPower = 0,
211
- cookOffDuration = 0,
212
- cookOffRandomTiming = false,
213
- cookOffPowerRandom = 50,
214
- isTanker = true,
215
- flameSize = 3,
216
- flameDuration = 5,
217
- },
218
-
219
- --3) Refueler ATZ-10
220
- ["ATZ-10"] = {
221
- cargoExplosion = true,
222
- cargoExplosionMult = 2,
223
- cargoExplosionPower = 200,
224
- cargoCookOff = false,
225
- cookOffCount = 0,
226
- cookOffPower = 0,
227
- cookOffDuration = 0,
228
- cookOffRandomTiming = false,
229
- cookOffPowerRandom = 50,
230
- isTanker = true,
231
- flameSize = 3,
232
- flameDuration = 5,
233
- },
234
-
235
- --4) Refueler ATZ-5
236
- ["ATZ-5"] = {
237
- cargoExplosion = true,
238
- cargoExplosionMult = 1.8,
239
- cargoExplosionPower = 200,
240
- cargoCookOff = false,
241
- cookOffCount = 0,
242
- cookOffPower = 0,
243
- cookOffDuration = 0,
244
- cookOffRandomTiming = false,
245
- cookOffPowerRandom = 50,
246
- isTanker = true,
247
- flameSize = 3,
248
- flameDuration = 5,
249
- },
250
-
251
- --5) Refueler M978 HEMTT (Fuel truck tanker)
252
- ["M978 HEMTT Tanker"] = {
253
- cargoExplosion = true,
254
- cargoExplosionMult = 2.0,
255
- cargoExplosionPower = 200,
256
- cargoCookOff = false,
257
- cookOffCount = 0,
258
- cookOffPower = 0,
259
- cookOffDuration = 0,
260
- cookOffRandomTiming = false,
261
- cookOffPowerRandom = 50,
262
- isTanker = true,
263
- flameSize = 3,
264
- flameDuration = 5,
265
- },
266
-
267
- --##### AMMO CARRIERS #####
268
- ["GAZ-66"] = {
269
- cargoExplosion = true,
270
- cargoExplosionMult = 1,
271
- cargoExplosionPower = 200,
272
- cargoCookOff = true,
273
- cookOffCount = 4,
274
- cookOffPower = 1,
275
- cookOffDuration = 20,
276
- cookOffRandomTiming = true,
277
- cookOffPowerRandom = 50,
278
- isTanker = false,
279
- flameSize = 1,
280
- flameDuration = 30,
281
- },
282
- --#Technically this is both ammo and fuel looking at the model
283
- ["Ural-4320"] = {
284
- cargoExplosion = true,
285
- cargoExplosionMult = 1,
286
- cargoExplosionPower = 200,
287
- cargoCookOff = true,
288
- cookOffCount = 4,
289
- cookOffPower = 1,
290
- cookOffDuration = 20,
291
- cookOffRandomTiming = true,
292
- cookOffPowerRandom = 50,
293
- isTanker = true,
294
- flameSize = 1,
295
- flameDuration = 30,
296
- },
297
-
298
- ["ZIL-135"] = {
299
- cargoExplosion = true,
300
- cargoExplosionMult = 1,
301
- cargoExplosionPower = 200,
302
- cargoCookOff = true,
303
- cookOffCount = 5,
304
- cookOffPower = 1,
305
- cookOffDuration = 20,
306
- cookOffRandomTiming = true,
307
- cookOffPowerRandom = 50,
308
- isTanker = false,
309
- flameSize = 1,
310
- flameDuration = 30,
311
- },
312
- }
313
-
314
- --Weapon Explosive Table
315
- explTable = {
316
- --*** WWII BOMBS ***
317
- ["British_GP_250LB_Bomb_Mk1"] = { explosive = 100, shaped_charge = false },
318
- ["British_GP_250LB_Bomb_Mk4"] = { explosive = 100, shaped_charge = false },
319
- ["British_GP_250LB_Bomb_Mk5"] = { explosive = 100, shaped_charge = false },
320
- ["British_GP_500LB_Bomb_Mk1"] = { explosive = 213, shaped_charge = false },
321
- ["British_GP_500LB_Bomb_Mk4"] = { explosive = 213, shaped_charge = false },
322
- ["British_GP_500LB_Bomb_Mk4_Short"] = { explosive = 213, shaped_charge = false },
323
- ["British_GP_500LB_Bomb_Mk5"] = { explosive = 213, shaped_charge = false },
324
- ["British_MC_250LB_Bomb_Mk1"] = { explosive = 100, shaped_charge = false },
325
- ["British_MC_250LB_Bomb_Mk2"] = { explosive = 100, shaped_charge = false },
326
- ["British_MC_500LB_Bomb_Mk1_Short"] = { explosive = 213, shaped_charge = false },
327
- ["British_MC_500LB_Bomb_Mk2"] = { explosive = 213, shaped_charge = false },
328
- ["British_SAP_250LB_Bomb_Mk5"] = { explosive = 100, shaped_charge = false },
329
- ["British_SAP_500LB_Bomb_Mk5"] = { explosive = 213, shaped_charge = false },
330
- ["British_AP_25LBNo1_3INCHNo1"] = { explosive = 4, shaped_charge = false },
331
- ["British_HE_60LBSAPNo2_3INCHNo1"] = { explosive = 4, shaped_charge = false },
332
- ["British_HE_60LBFNo1_3INCHNo1"] = { explosive = 4, shaped_charge = false },
333
-
334
- ["SC_50"] = { explosive = 20, shaped_charge = false },
335
- ["ER_4_SC50"] = { explosive = 20, shaped_charge = false },
336
- ["SC_250_T1_L2"] = { explosive = 100, shaped_charge = false },
337
- ["SC_501_SC250"] = { explosive = 100, shaped_charge = false },
338
- ["Schloss500XIIC1_SC_250_T3_J"] = { explosive = 100, shaped_charge = false },
339
- ["SC_501_SC500"] = { explosive = 213, shaped_charge = false },
340
- ["SC_500_L2"] = { explosive = 213, shaped_charge = false },
341
- ["SD_250_Stg"] = { explosive = 100, shaped_charge = false },
342
- ["SD_500_A"] = { explosive = 213, shaped_charge = false },
343
-
344
- --*** WWII CBU ***
345
- ["AB_250_2_SD_2"] = { explosive = 100, shaped_charge = false },
346
- ["AB_250_2_SD_10A"] = { explosive = 100, shaped_charge = false },
347
- ["AB_500_1_SD_10A"] = { explosive = 213, shaped_charge = false },
348
-
349
- --*** WWII ROCKETS ***
350
- ["3xM8_ROCKETS_IN_TUBES"] = { explosive = 4, shaped_charge = false },
351
- ["WGr21"] = { explosive = 4, shaped_charge = false },
352
-
353
- --*** UNGUIDED BOMBS (UGB) ***
354
- ["M_117"] = { explosive = 201, shaped_charge = false },
355
- ["AN_M30A1"] = { explosive = 45, shaped_charge = false },
356
- ["AN_M57"] = { explosive = 100, shaped_charge = false },
357
- ["AN_M64"] = { explosive = 121, shaped_charge = false },
358
- ["AN_M65"] = { explosive = 400, shaped_charge = false },
359
- ["AN_M66"] = { explosive = 800, shaped_charge = false },
360
- ["AN-M66A2"] = { explosive = 536, shaped_charge = false },
361
- ["AN-M81"] = { explosive = 100, shaped_charge = false },
362
- ["AN-M88"] = { explosive = 100, shaped_charge = false },
363
-
364
- ["Mk_81"] = { explosive = 60, shaped_charge = false },
365
- ["MK-81SE"] = { explosive = 60, shaped_charge = false },
366
- ["Mk_82"] = { explosive = 100, shaped_charge = false },
367
- ["MK_82AIR"] = { explosive = 100, shaped_charge = false },
368
- ["MK_82SNAKEYE"] = { explosive = 100, shaped_charge = false },
369
- ["Mk_83"] = { explosive = 274, shaped_charge = false },
370
- ["Mk_84"] = { explosive = 582, shaped_charge = false },
371
-
372
- ["HEBOMB"] = { explosive = 40, shaped_charge = false },
373
- ["HEBOMBD"] = { explosive = 40, shaped_charge = false },
374
-
375
- ["SAMP125LD"] = { explosive = 60, shaped_charge = false },
376
- ["SAMP250LD"] = { explosive = 118, shaped_charge = false },
377
- ["SAMP250HD"] = { explosive = 118, shaped_charge = false },
378
- ["SAMP400LD"] = { explosive = 274, shaped_charge = false },
379
- ["SAMP400HD"] = { explosive = 274, shaped_charge = false },
380
-
381
- ["BR_250"] = { explosive = 100, shaped_charge = false },
382
- ["BR_500"] = { explosive = 100, shaped_charge = false },
383
-
384
- ["FAB_100"] = { explosive = 45, shaped_charge = false },
385
- ["FAB_250"] = { explosive = 118, shaped_charge = false },
386
- ["FAB_250M54TU"] = { explosive = 118, shaped_charge = false },
387
- ["FAB-250-M62"] = { explosive = 118, shaped_charge = false },
388
- ["FAB_500"] = { explosive = 213, shaped_charge = false },
389
- ["FAB_1500"] = { explosive = 675, shaped_charge = false },
390
-
391
- --*** UNGUIDED BOMBS WITH PENETRATOR / ANTI-RUNWAY ***
392
- ["Durandal"] = { explosive = 64, shaped_charge = false },
393
- ["BLU107B_DURANDAL"] = { explosive = 64, shaped_charge = false },
394
- ["BAP_100"] = { explosive = 32, shaped_charge = false },
395
- ["BAP-100"] = { explosive = 32, shaped_charge = false },
396
- ["BAT-120"] = { explosive = 32, shaped_charge = false },
397
- ["TYPE-200A"] = { explosive = 107, shaped_charge = false },
398
- ["BetAB_500"] = { explosive = 98, shaped_charge = false },
399
- ["BetAB_500ShP"] = { explosive = 107, shaped_charge = false },
400
-
401
- --*** GUIDED BOMBS (GBU) ***
402
- ["GBU_10"] = { explosive = 582, shaped_charge = false },
403
- ["GBU_12"] = { explosive = 100, shaped_charge = false },
404
- ["GBU_16"] = { explosive = 274, shaped_charge = false },
405
- ["GBU_24"] = { explosive = 582, shaped_charge = false },
406
- ["KAB_1500Kr"] = { explosive = 675, shaped_charge = false },
407
- ["KAB_500Kr"] = { explosive = 213, shaped_charge = false },
408
- ["KAB_500"] = { explosive = 213, shaped_charge = false },
409
-
410
- --*** CLUSTER BOMBS (CBU) ***
411
- --I don't have most of these so can't test them with debug on
412
- ["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
413
- ["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
414
- ["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
415
- ["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
416
- ["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
417
- ["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
418
- ["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
419
- ["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
420
- ["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
421
- ["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)
422
- ["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
423
- ["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
424
- ["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
425
- ["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
426
- ["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
427
- ["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
428
- ["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
429
- ["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
430
- ["RBK_500_255_PTO_1M"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 126, submunition_explosive = 0.5, submunition_name = "PTO_1M" },
431
- ["RBK_500_255_ShO"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 565, submunition_explosive = 0.1, submunition_name = "ShO" },
432
- --*** INS/GPS BOMBS (JDAM) ***
433
- ["GBU_31"] = { explosive = 582, shaped_charge = false },
434
- ["GBU_31_V_3B"] = { explosive = 582, shaped_charge = false },
435
- ["GBU_31_V_2B"] = { explosive = 582, shaped_charge = false },
436
- ["GBU_31_V_4B"] = { explosive = 582, shaped_charge = false },
437
- ["GBU_32_V_2B"] = { explosive = 202, shaped_charge = false },
438
- ["GBU_38"] = { explosive = 100, shaped_charge = false },
439
- ["GBU_54_V_1B"] = { explosive = 100, shaped_charge = false },
440
-
441
- --*** GLIDE BOMBS (JSOW) ***
442
- ["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
443
- ["AGM_154C"] = { explosive = 305, shaped_charge = false },
444
- ["AGM_154"] = { explosive = 305, shaped_charge = false },
445
- ["BK90_MJ1"] = { explosive = 0, shaped_charge = false },
446
- ["BK90_MJ1_MJ2"] = { explosive = 0, shaped_charge = false },
447
- ["BK90_MJ2"] = { explosive = 0, shaped_charge = false },
448
-
449
- ["LS-6-100"] = { explosive = 45, shaped_charge = false },
450
- ["LS-6-250"] = { explosive = 100, shaped_charge = false },
451
- ["LS-6-500"] = { explosive = 274, shaped_charge = false },
452
- ["GB-6"] = { explosive = 0, shaped_charge = false },
453
- ["GB-6-HE"] = { explosive = 0, shaped_charge = false },
454
- ["GB-6-SFW"] = { explosive = 0, shaped_charge = false },
455
-
456
- --*** AIR GROUND MISSILE (AGM) ***
457
- ["AGM_62"] = { explosive = 400, shaped_charge = false },
458
- ["AGM_65D"] = { explosive = 38, shaped_charge = true },
459
- ["AGM_65E"] = { explosive = 80, shaped_charge = true },
460
- ["AGM_65F"] = { explosive = 80, shaped_charge = true },
461
- ["AGM_65G"] = { explosive = 80, shaped_charge = true },
462
- ["AGM_65H"] = { explosive = 38, shaped_charge = true },
463
- ["AGM_65K"] = { explosive = 80, shaped_charge = true },
464
- ["AGM_65L"] = { explosive = 80, shaped_charge = true },
465
- ["AGM_123"] = { explosive = 274, shaped_charge = false },
466
- ["AGM_130"] = { explosive = 582, shaped_charge = false },
467
- ["AGM_119"] = { explosive = 176, shaped_charge = false },
468
- ["AGM_114"] = { explosive = 10, shaped_charge = true },
469
- ["AGM_114K"] = { explosive = 10, shaped_charge = true },
470
-
471
- ["Rb 05A"] = { explosive = 217, shaped_charge = false },
472
- ["RB75"] = { explosive = 38, shaped_charge = false },
473
- ["RB75A"] = { explosive = 38, shaped_charge = false },
474
- ["RB75B"] = { explosive = 38, shaped_charge = false },
475
- ["RB75T"] = { explosive = 80, shaped_charge = false },
476
- ["HOT3_MBDA"] = { explosive = 15, shaped_charge = false },
477
- ["C-701T"] = { explosive = 38, shaped_charge = false },
478
- ["C-701IR"] = { explosive = 38, shaped_charge = false },
479
-
480
- ["Vikhr_M"] = { explosive = 11, shaped_charge = false },
481
- ["Vikhr_9M127_1"] = { explosive = 11, shaped_charge = false },
482
- ["AT_6"] = { explosive = 11, shaped_charge = false },
483
- ["Ataka_9M120"] = { explosive = 11, shaped_charge = false },
484
- ["Ataka_9M120F"] = { explosive = 11, shaped_charge = false },
485
- ["P_9M117"] = { explosive = 0, shaped_charge = false },
486
-
487
- ["KH-66_Grom"] = { explosive = 108, shaped_charge = false },
488
- ["X_23"] = { explosive = 111, shaped_charge = false },
489
- ["X_23L"] = { explosive = 111, shaped_charge = false },
490
- ["X_28"] = { explosive = 160, shaped_charge = false },
491
- ["X_25ML"] = { explosive = 89, shaped_charge = false },
492
- ["X_25MR"] = { explosive = 140, shaped_charge = false },
493
- ["X_29L"] = { explosive = 320, shaped_charge = false },
494
- ["X_29T"] = { explosive = 320, shaped_charge = false },
495
- ["X_29TE"] = { explosive = 320, shaped_charge = false },
496
-
497
- --*** ANTI-RADAR MISSILE (ARM) ***
498
- ["AGM_88C"] = { explosive = 89, shaped_charge = false },
499
- ["AGM_88"] = { explosive = 89, shaped_charge = false },
500
- ["AGM_122"] = { explosive = 15, shaped_charge = false },
501
- ["LD-10"] = { explosive = 89, shaped_charge = false },
502
- ["AGM_45A"] = { explosive = 38, shaped_charge = false },
503
- ["X_58"] = { explosive = 140, shaped_charge = false },
504
- ["X_25MP"] = { explosive = 89, shaped_charge = false },
505
-
506
- --*** ANTI-SHIP MISSILE (ASh) ***
507
- ["AGM_84D"] = { explosive = 488, shaped_charge = false },
508
- ["Rb 15F"] = { explosive = 500, shaped_charge = false },
509
- ["C-802AK"] = { explosive = 500, shaped_charge = false },
510
-
511
- --*** CRUISE MISSILE ***
512
- ["CM-802AKG"] = { explosive = 488, shaped_charge = false },
513
- ["AGM_84E"] = { explosive = 488, shaped_charge = false },
514
- ["AGM_84H"] = { explosive = 488, shaped_charge = false },
515
- ["X_59M"] = { explosive = 488, shaped_charge = false },
516
-
517
- --*** ROCKETS ***
518
- ["HYDRA_70M15"] = { explosive = 5, shaped_charge = false },
519
- ["HYDRA_70_MK1"] = { explosive = 5, shaped_charge = false },
520
- ["HYDRA_70_MK5"] = { explosive = 8, shaped_charge = false },
521
- ["HYDRA_70_M151"] = { explosive = 5, shaped_charge = false },
522
- ["HYDRA_70_M151_M433"] = { explosive = 5, shaped_charge = false },
523
- ["HYDRA_70_M229"] = { explosive = 10, shaped_charge = false },
524
- ["FFAR Mk1 HE"] = { explosive = 5, shaped_charge = false },
525
- ["FFAR Mk5 HEAT"] = { explosive = 8, shaped_charge = false },
526
- ["HVAR"] = { explosive = 5, shaped_charge = false },
527
- ["Zuni_127"] = { explosive = 8, shaped_charge = false },
528
- ["ARAKM70BHE"] = { explosive = 5, shaped_charge = false },
529
- ["ARAKM70BAP"] = { explosive = 8, shaped_charge = false },
530
- ["SNEB_TYPE251_F1B"] = { explosive = 4, shaped_charge = false },
531
- ["SNEB_TYPE252_F1B"] = { explosive = 4, shaped_charge = false },
532
- ["SNEB_TYPE253_F1B"] = { explosive = 5, shaped_charge = false },
533
- ["SNEB_TYPE256_F1B"] = { explosive = 6, shaped_charge = false },
534
- ["SNEB_TYPE257_F1B"] = { explosive = 8, shaped_charge = false },
535
- ["SNEB_TYPE251_F4B"] = { explosive = 4, shaped_charge = false },
536
- ["SNEB_TYPE252_F4B"] = { explosive = 4, shaped_charge = false },
537
- ["SNEB_TYPE253_F4B"] = { explosive = 5, shaped_charge = false },
538
- ["SNEB_TYPE256_F4B"] = { explosive = 6, shaped_charge = false },
539
- ["SNEB_TYPE257_F4B"] = { explosive = 8, shaped_charge = false },
540
- ["SNEB_TYPE251_H1"] = { explosive = 4, shaped_charge = false },
541
- ["SNEB_TYPE252_H1"] = { explosive = 4, shaped_charge = false },
542
- ["SNEB_TYPE253_H1"] = { explosive = 5, shaped_charge = false },
543
- ["SNEB_TYPE256_H1"] = { explosive = 6, shaped_charge = false },
544
- ["SNEB_TYPE257_H1"] = { explosive = 8, shaped_charge = false },
545
- ["MATRA_F4_SNEBT251"] = { explosive = 8, shaped_charge = false },
546
- ["MATRA_F4_SNEBT253"] = { explosive = 8, shaped_charge = false },
547
- ["MATRA_F4_SNEBT256"] = { explosive = 8, shaped_charge = false },
548
- ["MATRA_F1_SNEBT253"] = { explosive = 8, shaped_charge = false },
549
- ["MATRA_F1_SNEBT256"] = { explosive = 8, shaped_charge = false },
550
- ["TELSON8_SNEBT251"] = { explosive = 4, shaped_charge = false },
551
- ["TELSON8_SNEBT253"] = { explosive = 8, shaped_charge = false },
552
- ["TELSON8_SNEBT256"] = { explosive = 4, shaped_charge = false },
553
- ["TELSON8_SNEBT257"] = { explosive = 6, shaped_charge = false },
554
- ["ARF8M3API"] = { explosive = 8, shaped_charge = false },
555
- ["UG_90MM"] = { explosive = 8, shaped_charge = false },
556
- ["S-24A"] = { explosive = 24, shaped_charge = false },
557
- ["S-25OF"] = { explosive = 194, shaped_charge = false },
558
- ["S-25OFM"] = { explosive = 150, shaped_charge = false },
559
- ["S-25O"] = { explosive = 150, shaped_charge = false },
560
- ["S-25-O"] = { explosive = 150, shaped_charge = false },
561
- ["S_25L"] = { explosive = 190, shaped_charge = false },
562
- ["S-5M"] = { explosive = 1, shaped_charge = false },
563
- ["C_5"] = { explosive = 8, shaped_charge = false },
564
- ["C5"] = { explosive = 5, shaped_charge = false },
565
- ["C_8"] = { explosive = 4, shaped_charge = false },
566
- ["C_8OFP2"] = { explosive = 3, shaped_charge = false },
567
- ["C_13"] = { explosive = 21, shaped_charge = false },
568
- ["C_24"] = { explosive = 123, shaped_charge = false },
569
- ["C_25"] = { explosive = 151, shaped_charge = false },
570
-
571
- --*** LASER ROCKETS ***
572
- ["AGR_20"] = { explosive = 8, shaped_charge = false },
573
- ["AGR_20A"] = { explosive = 8, shaped_charge = false },
574
- ["AGR_20_M282"] = { explosive = 8, shaped_charge = false },
575
- ["Hydra_70_M282_MPP"] = { explosive = 5, shaped_charge = true },
576
- ["BRM-1_90MM"] = { explosive = 8, shaped_charge = false },
577
- }
578
-
579
-
580
-
581
-
582
-
583
-
584
- local effectSmokeId = 1
585
-
586
- ----[[ ##### HELPER/UTILITY FUNCTIONS ##### ]]----
587
-
588
- local function tableHasKey(table, key)
589
- return table[key] ~= nil
590
- end
591
-
592
- local function debugMsg(str)
593
- if splash_damage_options.debug == true then
594
- debugCounter = (debugCounter or 0) + 1
595
- local uniqueStr = str .. " [" .. timer.getTime() .. " - " .. debugCounter .. "]"
596
- trigger.action.outText(uniqueStr, 5)
597
- env.info("DEBUG: " .. uniqueStr)
598
- end
599
- end
600
-
601
- local function gameMsg(str)
602
- if splash_damage_options.game_messages == true then
603
- trigger.action.outText(str, 5)
604
- end
605
- end
606
-
607
- local function getDistance(point1, point2)
608
- local x1 = point1.x
609
- local y1 = point1.y
610
- local z1 = point1.z
611
- local x2 = point2.x
612
- local y2 = point2.y
613
- local z2 = point2.z
614
- local dX = math.abs(x1 - x2)
615
- local dZ = math.abs(z1 - z2)
616
- local distance = math.sqrt(dX * dX + dZ * dZ)
617
- return distance
618
- end
619
-
620
- local function getDistance3D(point1, point2)
621
- local x1 = point1.x
622
- local y1 = point1.y
623
- local z1 = point1.z
624
- local x2 = point2.x
625
- local y2 = point2.y
626
- local z2 = point2.z
627
- local dX = math.abs(x1 - x2)
628
- local dY = math.abs(y1 - y2)
629
- local dZ = math.abs(z1 - z2)
630
- local distance = math.sqrt(dX * dX + dZ * dZ + dY * dY)
631
- return distance
632
- end
633
-
634
- local function vec3Mag(speedVec)
635
- return math.sqrt(speedVec.x^2 + speedVec.y^2 + speedVec.z^2)
636
- end
637
-
638
- local function lookahead(speedVec)
639
- local speed = vec3Mag(speedVec)
640
- local dist = speed * refreshRate * 1.5
641
- return dist
642
- end
643
-
644
- --Cluster-specific helper functions from Rockeye script
645
- local function normalizeVector(vec)
646
- local mag = math.sqrt(vec.x^2 + vec.z^2)
647
- if mag > 0 then
648
- return { x = vec.x / mag, z = vec.z / mag }
649
- else
650
- return { x = 1, z = 0 }
651
- end
652
- end
653
- local function calculate_drop_angle(velocity)
654
- local horizontal_speed = math.sqrt((velocity.x or 0)^2 + (velocity.z or 0)^2)
655
- local vertical_speed = math.abs(velocity.y or 0)
656
- if horizontal_speed == 0 then return 90 end
657
- local angle_rad = math.atan(vertical_speed / horizontal_speed)
658
- return math.deg(angle_rad)
659
- end
660
- local function calculate_dispersion(velocity, burst_altitude)
661
- local velocity_magnitude = math.sqrt((velocity.x or 0)^2 + (velocity.z or 0)^2)
662
- local drop_angle = calculate_drop_angle(velocity)
663
- local length = splash_damage_options.cluster_base_length * (1 + velocity_magnitude / 200)
664
- local width = splash_damage_options.cluster_base_width * (1 + burst_altitude / 6000)
665
- local length_jitter = length * (0.85 + math.random() * 0.3)
666
- local width_jitter = width * (0.85 + math.random() * 0.3)
667
- return math.max(splash_damage_options.cluster_min_length, math.min(splash_damage_options.cluster_max_length, length_jitter)),
668
- math.max(splash_damage_options.cluster_min_width, math.min(splash_damage_options.cluster_max_width, width_jitter))
669
- end
670
-
671
-
672
-
673
-
674
-
675
-
676
-
677
-
678
-
679
- ----[[ ##### End of HELPER/UTILITY FUNCTIONS ##### ]]----
680
- giantExplosionTargets = {}
681
- cargoEffectsQueue = {}
682
- WpnHandler = {}
683
- tracked_target_position = nil --Store the last known position of TargetUnit for giant explosion
684
- tracked_weapons = {}
685
- local processedUnitsGlobal = {}
686
-
687
- function scanGiantExplosionTargets()
688
- giantExplosionTargets = {}
689
- local function findTargets(obj)
690
- if obj:isExist() then
691
- local name = obj:getName()
692
- if string.find(name, "GiantExplosionTarget") then
693
- local flagName = string.gsub(name, "Target", "")
694
- table.insert(giantExplosionTargets, {
695
- name = name,
696
- flag = flagName,
697
- obj = obj,
698
- pos = obj:getPoint(),
699
- static = splash_damage_options.giant_explosion_target_static
700
- })
701
- end
702
- end
703
- return true
704
- end
705
- world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, {id = world.VolumeType.ALL}, findTargets)
706
- if not splash_damage_options.giant_explosion_target_static then
707
- timer.scheduleFunction(updateGiantExplosionPositions, {}, timer.getTime() + 1.0)
708
- end
709
- end
710
-
711
- function updateTargetPosition()
712
- for name, target in pairs(giantExplosionTargets) do
713
- if target.obj:isExist() then
714
- target.pos = target.obj:getPosition().p
715
- end
716
- end
717
- return timer.getTime() + 1.0
718
- end
719
-
720
-
721
- --Giant Explosion Function
722
- function triggerGiantExplosion(params)
723
- if not splash_damage_options.giant_explosion_enabled then
724
- debugMsg("Giant Explosion is disabled in options.")
725
- return
726
- end
727
-
728
- local initialPos = params.pos or {x = 0, y = 0, z = 0}
729
- local explosionPower = params.power or splash_damage_options.giant_explosion_power
730
- local sizeScale = params.scale or splash_damage_options.giant_explosion_scale
731
- local totalDuration = params.duration or splash_damage_options.giant_explosion_duration
732
- local explosionCount = params.count or splash_damage_options.giant_explosion_count
733
-
734
- if not initialPos.x or not initialPos.y or not initialPos.z then
735
- gameMsg("Error: Invalid position for giant explosion!")
736
- debugMsg("No valid initial position set for giant explosion!")
737
- return
738
- end
739
-
740
- debugMsg("Triggering giant fireball at X: " .. initialPos.x .. ", Y: " .. initialPos.y .. ", Z: " .. initialPos.z)
741
-
742
- local function scheduleExplosion(pos, delay)
743
- if not pos or not pos.x or not pos.y or not pos.z then
744
- debugMsg("Error: Invalid position for explosion - pos: " .. tostring(pos))
745
- return
746
- end
747
- timer.scheduleFunction(function(p)
748
- if p and p.x and p.y and p.z then
749
- trigger.action.explosion(p, explosionPower)
750
- end
751
- end, pos, timer.getTime() + delay)
752
- end
753
-
754
- -- Pre-explosion scan for cargo units
755
- local scanRadius = 1500 * sizeScale -- 1500m base radius, scaled by sizeScale
756
- local preExplosionTargets = {}
757
- if splash_damage_options.enable_cargo_effects then
758
- local volS = {
759
- id = world.VolumeType.SPHERE,
760
- params = { point = initialPos, radius = scanRadius }
761
- }
762
- local ifFound = function(foundObject)
763
- if foundObject:isExist() then
764
- local category = foundObject:getCategory()
765
- if (category == Object.Category.UNIT and foundObject:getDesc().category == Unit.Category.GROUND_UNIT) or
766
- category == Object.Category.STATIC then
767
- table.insert(preExplosionTargets, {
768
- name = foundObject:getTypeName(),
769
- health = foundObject:getLife() or 0,
770
- position = foundObject:getPoint(),
771
- maxHealth = (category == Object.Category.UNIT and foundObject:getDesc().life) or foundObject:getLife() or 0,
772
- unit = foundObject
773
- })
774
- end
775
- end
776
- return true
777
- end
778
- world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, volS, ifFound)
779
- debugMsg("Pre-explosion scan for Giant Explosion: " .. #preExplosionTargets .. " targets found within " .. scanRadius .. "m")
780
- end
781
- -- Trigger the explosion
782
- local maxRadius = 200 * sizeScale
783
- local maxHeight = 500 * sizeScale
784
- local adjustedExplosionCount = math.floor(explosionCount * (sizeScale ^ 2.5))
785
- local stepTime = totalDuration / adjustedExplosionCount
786
- local variance = 0.25 --Fixed at 25%
787
-
788
- for i = 1, adjustedExplosionCount do
789
- local progress = i / adjustedExplosionCount
790
- local currentRadius = maxRadius * progress
791
- local r = currentRadius * (0.9 + math.random() * 0.1)
792
- local theta = math.random() * 2 * math.pi
793
- local phi = math.acos(math.random())
794
-
795
- local offsetX = r * math.sin(phi) * math.cos(theta)
796
- local offsetZ = r * math.sin(phi) * math.sin(theta)
797
- local offsetY = r * math.cos(phi)
798
-
799
- offsetX = offsetX * (1 + (math.random() - 0.5) * variance)
800
- offsetZ = offsetZ * (1 + (math.random() - 0.5) * variance)
801
- offsetY = offsetY * (1 + (math.random() - 0.5) * variance * 0.5)
802
-
803
- local blastPos = {
804
- x = initialPos.x + offsetX,
805
- y = land.getHeight({x = initialPos.x, y = initialPos.z}) + offsetY,
806
- z = initialPos.z + offsetZ
807
- }
808
- if blastPos.y < land.getHeight({x = blastPos.x, y = blastPos.z}) then
809
- blastPos.y = land.getHeight({x = blastPos.x, y = blastPos.z})
810
- end
811
-
812
- local delay = (i - 1) * stepTime + (math.random() - 0.5) * stepTime * variance
813
- scheduleExplosion(blastPos, delay)
814
- end
815
-
816
- gameMsg("Expanding giant fireball over " .. totalDuration .. "s (scale " .. sizeScale .. ")!")
817
-
818
- -- Post-explosion scan and cargo cook-off queuing
819
- if splash_damage_options.enable_cargo_effects then
820
- timer.scheduleFunction(function(args)
821
- local centerPos = args[1]
822
- local radius = args[2]
823
- local preTargets = args[3]
824
-
825
- local postExplosionTargets = {}
826
- local volS = {
827
- id = world.VolumeType.SPHERE,
828
- params = { point = centerPos, radius = radius }
829
- }
830
- local ifFound = function(foundObject)
831
- if foundObject:isExist() then
832
- local category = foundObject:getCategory()
833
- if (category == Object.Category.UNIT and foundObject:getDesc().category == Unit.Category.GROUND_UNIT) or
834
- category == Object.Category.STATIC then
835
- table.insert(postExplosionTargets, {
836
- name = foundObject:getTypeName(),
837
- health = foundObject:getLife() or 0,
838
- position = foundObject:getPoint(),
839
- maxHealth = (category == Object.Category.UNIT and foundObject:getDesc().life) or foundObject:getLife() or 0
840
- })
841
- end
842
- end
843
- return true
844
- end
845
- world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, volS, ifFound)
846
- debugMsg("Post-explosion scan for Giant Explosion: " .. #postExplosionTargets .. " targets found within " .. radius .. "m")
847
-
848
- -- Compare pre- and post-explosion targets
849
- for _, preTarget in ipairs(preTargets) do
850
- local found = false
851
- local postHealth = 0
852
- for _, postTarget in ipairs(postExplosionTargets) do
853
- if preTarget.name == postTarget.name and getDistance(preTarget.position, postTarget.position) < 1 then
854
- found = true
855
- postHealth = postTarget.health
856
- break
857
- end
858
- end
859
-
860
- local cargoData = cargoUnits[preTarget.name]
861
- if cargoData and (not found or postHealth <= 0) then
862
- local distance = getDistance(initialPos, preTarget.position)
863
- if distance <= radius then
864
- local cargoPower = cargoData.cargoExplosionPower or explosionPower
865
- table.insert(cargoEffectsQueue, {
866
- name = preTarget.name,
867
- distance = distance,
868
- coords = preTarget.position,
869
- power = cargoPower,
870
- explosion = cargoData.cargoExplosion,
871
- cookOff = cargoData.cargoCookOff,
872
- cookOffCount = cargoData.cookOffCount,
873
- cookOffPower = cargoData.cookOffPower,
874
- cookOffDuration = cargoData.cookOffDuration,
875
- cookOffRandomTiming = cargoData.cookOffRandomTiming,
876
- cookOffPowerRandom = cargoData.cookOffPowerRandom,
877
- isTanker = cargoData.isTanker,
878
- flameSize = cargoData.flameSize,
879
- flameDuration = cargoData.flameDuration
880
- })
881
- debugMsg("Queued cargo effect for " .. preTarget.name .. " destroyed by Giant Explosion at " .. string.format("%.1f", distance) .. "m")
882
- end
883
- end
884
- end
885
-
886
- -- Process queued cargo effects with prioritized flames
887
- if #cargoEffectsQueue > 0 then
888
- local flameIndex = 0 -- Separate index for flames
889
- local otherIndex = 0 -- Index for explosions, cook-offs, debris
890
- local processedCargoUnits = {}
891
- local flamePositions = {}
892
- for _, effect in ipairs(cargoEffectsQueue) do
893
- local unitKey = effect.name .. "_" .. effect.coords.x .. "_" .. effect.coords.z
894
- if not processedUnitsGlobal[unitKey] and not processedCargoUnits[unitKey] then
895
- -- Handle tanker flames first with minimal delay
896
- if effect.isTanker and effect.explosion then
897
- debugMsg("Triggering cargo explosion for tanker " .. effect.name .. " at " .. string.format("%.1f", effect.distance) .. "m with power " .. effect.power .. " scheduled at " .. flameIndex .. "s")
898
- timer.scheduleFunction(function(params)
899
- 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])
900
- trigger.action.explosion(params[1], params[2])
901
- end, {effect.coords, effect.power}, timer.getTime() + flameIndex + 0.1)
902
-
903
- local flameSize = effect.flameSize or 3
904
- local flameDuration = effect.flameDuration
905
- local flameDensity = 1.0
906
- local effectId = effectSmokeId
907
- effectSmokeId = effectSmokeId + 1
908
- local isDuplicate = false
909
- for _, pos in pairs(flamePositions) do
910
- if getDistance3D(effect.coords, pos) < 3 then
911
- isDuplicate = true
912
- 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))
913
- break
914
- end
915
- end
916
- if not isDuplicate then
917
- debugMsg("Adding flame effect for tanker " .. effect.name .. " at " .. string.format("%.1f", effect.distance) .. "m (Size: " .. flameSize .. ", Duration: " .. flameDuration .. "s, ID: " .. effectId .. ") scheduled at " .. flameIndex .. "s")
918
- timer.scheduleFunction(function(params)
919
- local terrainHeight = land.getHeight({x = params[1].x, y = params[1].z})
920
- local adjustedCoords = {x = params[1].x, y = terrainHeight + 2, z = params[1].z}
921
- debugMsg("Spawning flame effect at X: " .. string.format("%.0f", adjustedCoords.x) .. ", Y: " .. string.format("%.0f", adjustedCoords.y) .. ", Z: " .. string.format("%.0f", adjustedCoords.z))
922
- trigger.action.explosion(adjustedCoords, 10) -- Small trigger explosion
923
- trigger.action.effectSmokeBig(adjustedCoords, params[2], params[3], params[4])
924
- end, {effect.coords, flameSize, flameDensity, effectId}, timer.getTime() + flameIndex + 0.2)
925
- timer.scheduleFunction(function(id)
926
- debugMsg("Stopping flame effect for " .. effect.name .. " (ID: " .. id .. ")")
927
- trigger.action.effectSmokeStop(id)
928
- end, effectId, timer.getTime() + flameIndex + flameDuration + 0.2)
929
- table.insert(flamePositions, effect.coords)
930
- end
931
- flameIndex = flameIndex + 0.5 -- Fast spacing for flames (0.5s)
932
- end
933
- -- Handle non-tanker explosions, cook-offs, and debris
934
- if not effect.isTanker or (effect.explosion and not effect.isTanker) then
935
- if effect.explosion then
936
- debugMsg("Triggering cargo explosion for " .. effect.name .. " at " .. string.format("%.1f", effect.distance) .. "m with power " .. effect.power .. " scheduled at " .. otherIndex .. "s")
937
- timer.scheduleFunction(function(params)
938
- 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])
939
- trigger.action.explosion(params[1], params[2])
940
- end, {effect.coords, effect.power}, timer.getTime() + otherIndex + 0.1)
941
- end
942
- if effect.cookOff and effect.cookOffCount > 0 then
943
- debugMsg("Scheduling " .. effect.cookOffCount .. " cook-off explosions for " .. effect.name .. " at " .. string.format("%.1f", effect.distance) .. "m over " .. effect.cookOffDuration .. "s starting at " .. otherIndex .. "s")
944
- for i = 1, effect.cookOffCount do
945
- local delay = effect.cookOffRandomTiming and math.random() * effect.cookOffDuration or (i - 1) * (effect.cookOffDuration / effect.cookOffCount)
946
- local basePower = effect.cookOffPower
947
- local powerVariation = effect.cookOffPowerRandom / 100
948
- local cookOffPower = effect.cookOffPowerRandom == 0 and basePower or basePower * (1 + powerVariation * (math.random() * 2 - 1))
949
- 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))
950
- timer.scheduleFunction(function(params)
951
- debugMsg("Executing cook-off 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])
952
- trigger.action.explosion(params[1], params[2])
953
- end, {effect.coords, cookOffPower}, timer.getTime() + otherIndex + delay)
954
- end
955
- if splash_damage_options.debris_effects then
956
- local debrisCount = math.random(splash_damage_options.debris_count_min, splash_damage_options.debris_count_max)
957
- for j = 1, debrisCount do
958
- local theta = math.random() * 2 * math.pi
959
- local phi = math.acos(math.random() * 2 - 1)
960
- local minDist = splash_damage_options.debris_max_distance * 0.1
961
- local maxDist = splash_damage_options.debris_max_distance
962
- local r = math.random() * (maxDist - minDist) + minDist
963
- local debrisX = effect.coords.x + r * math.sin(phi) * math.cos(theta)
964
- local debrisZ = effect.coords.z + r * math.sin(phi) * math.sin(theta)
965
- local terrainY = land.getHeight({x = debrisX, y = debrisZ})
966
- local debrisY = terrainY + math.random() * maxDist
967
- local debrisPos = {x = debrisX, y = debrisY, z = debrisZ}
968
- local debrisPower = splash_damage_options.debris_power
969
- local debrisDelay = (j - 1) * (effect.cookOffDuration / debrisCount)
970
- timer.scheduleFunction(function(debrisArgs)
971
- debugMsg("Debris explosion at X: " .. string.format("%.0f", debrisArgs[1].x) .. ", Y: " .. string.format("%.0f", debrisArgs[1].y) .. ", Z: " .. string.format("%.0f", debrisArgs[1].z) .. " with power " .. debrisArgs[2])
972
- trigger.action.explosion(debrisArgs[1], debrisArgs[2])
973
- end, {debrisPos, debrisPower}, timer.getTime() + otherIndex + debrisDelay)
974
- end
975
- end
976
- end
977
- otherIndex = otherIndex + 1 -- Slower spacing for non-flame effects (1s)
978
- end
979
- processedCargoUnits[unitKey] = true
980
- processedUnitsGlobal[unitKey] = true
981
- end
982
- end
983
- cargoEffectsQueue = {} -- Clear the queue after processing
984
- end
985
- end, {initialPos, scanRadius, preExplosionTargets}, timer.getTime() + totalDuration + 1.0)
986
- end
987
- end
988
-
989
- --Flag Checker for mission editor
990
- function checkGiantExplosionFlag()
991
- for name, target in pairs(giantExplosionTargets) do
992
- local flagName = name:gsub("GiantExplosionTarget", "GiantExplosionTarget")
993
- local flagValue = trigger.misc.getUserFlag(flagName)
994
- --commenting out as it spams every second
995
- debugMsg("Checking flag " .. flagName .. ": " .. flagValue)
996
- if flagValue == 1 then
997
- debugMsg("Triggering explosion for " .. name .. " at X:" .. target.pos.x .. " Y:" .. target.pos.y .. " Z:" .. target.pos.z)
998
- triggerGiantExplosion({
999
- pos = target.pos,
1000
- power = splash_damage_options.giant_explosion_power,
1001
- scale = splash_damage_options.giant_explosion_scale,
1002
- duration = splash_damage_options.giant_explosion_duration,
1003
- count = splash_damage_options.giant_explosion_count
1004
- })
1005
- trigger.action.setUserFlag(flagName, 2)
1006
- end
1007
- end
1008
- return timer.getTime() + splash_damage_options.giant_explosion_poll_rate
1009
- end
1010
-
1011
- function getWeaponExplosive(name)
1012
- local weaponData = explTable[name]
1013
- if weaponData then
1014
- return weaponData.explosive, weaponData.shaped_charge
1015
- else
1016
- return 0, false
1017
- end
1018
- end
1019
-
1020
- function track_wpns_cluster_scan(args)
1021
- local parentPos = args[1]
1022
- local parentDir = args[2]
1023
- local parentName = args[3]
1024
- local subName = args[4]
1025
- local subCount = args[5]
1026
- local subPower = args[6]
1027
- local parentVel = args[7]
1028
- local attempt = args[8] or 1
1029
- local maxAttempts = 3
1030
- local scanVol = {
1031
- id = world.VolumeType.SPHERE,
1032
- params = { point = parentPos, radius = 400 }
1033
- }
1034
- local bombletsFound = {}
1035
- local allWeaponsFound = {}
1036
- --General scan for all weapons
1037
- world.searchObjects(Object.Category.WEAPON, scanVol, function(wpn)
1038
- if wpn:isExist() then
1039
- local wpnId = wpn.id_
1040
- local wpnType = wpn:getTypeName()
1041
- local wpnPos = wpn:getPosition().p
1042
- table.insert(allWeaponsFound, { id = wpnId, type = wpnType, x = wpnPos.x, y = wpnPos.y, z = wpnPos.z })
1043
- if wpnType == subName and not tracked_weapons[wpnId] then
1044
- tracked_weapons[wpnId] = {
1045
- wpn = wpn,
1046
- pos = wpnPos,
1047
- speed = wpn:getVelocity(),
1048
- name = wpnType,
1049
- parent = parentName,
1050
- parentVelocity = parentVel
1051
- }
1052
- table.insert(bombletsFound, wpnId)
1053
- 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 .. ")")
1054
- end
1055
- end
1056
- return true
1057
- end)
1058
- --Log results
1059
- debugMsg("Scanned for submunition '" .. subName .. "' bomblets from '" .. parentName .. "': " .. #bombletsFound .. " found (Attempt " .. attempt .. ")")
1060
- if #allWeaponsFound > 0 then
1061
- local msg = "General scan for '" .. parentName .. "': " .. #allWeaponsFound .. " bomblets released, expected " .. subCount .. " '" .. subName .. "'"
1062
- local typeMismatch = false
1063
- for _, wpn in ipairs(allWeaponsFound) do
1064
- if wpn.type ~= subName then
1065
- typeMismatch = true
1066
- break
1067
- end
1068
- end
1069
- if typeMismatch then
1070
- msg = msg .. " - Mismatch detected! Actual bomblets: "
1071
- for _, wpn in ipairs(allWeaponsFound) do
1072
- msg = msg .. "'" .. wpn.type .. "' (X: " .. string.format("%.0f", wpn.x) .. ", Y: " .. string.format("%.0f", wpn.y) .. ", Z: " .. string.format("%.0f", wpn.z) .. ") "
1073
- end
1074
- msg = msg .. "Script may need changing."
1075
- end
1076
- debugMsg(msg)
1077
- elseif #bombletsFound == 0 and #allWeaponsFound == 0 then
1078
- debugMsg("No bomblets of any type detected for '" .. parentName .. "' (Attempt " .. attempt .. ")")
1079
- end
1080
- --Retry if no expected submunitions found
1081
- if #bombletsFound == 0 and attempt < maxAttempts then
1082
- debugMsg("No expected submunition '" .. subName .. "' found on attempt " .. attempt .. ", retrying in 0.5s")
1083
- timer.scheduleFunction(track_wpns_cluster_scan, {parentPos, parentDir, parentName, subName, subCount, subPower, parentVel, attempt + 1}, timer.getTime() + 0.5)
1084
- elseif #bombletsFound == 0 and attempt == maxAttempts then
1085
- debugMsg("No submunition '" .. subName .. "' spawned by DCS for '" .. parentName .. "' after " .. maxAttempts .. " attempts - skipping additional explosions")
1086
- end
1087
- end
1088
-
1089
- ----[[ ##### Updated track_wpns() Function ##### ]]----
1090
- local recentExplosions = {}
1091
- function track_wpns()
1092
- local weaponsToRemove = {} --Delay removal to ensure all weapons are checked
1093
- for wpn_id_, wpnData in pairs(tracked_weapons) do
1094
- local status, err = pcall(function()
1095
- if wpnData.wpn:isExist() then
1096
- --Update position, direction, speed
1097
- wpnData.pos = wpnData.wpn:getPosition().p
1098
- wpnData.dir = wpnData.wpn:getPosition().x
1099
- wpnData.speed = wpnData.wpn:getVelocity()
1100
- --[[
1101
-
1102
-
1103
- --Tick-by-tick tracking from weapon's actual position
1104
- local tickVol = {
1105
- id = world.VolumeType.SPHERE,
1106
- params = {
1107
- point = wpnData.pos, --Real weapon position
1108
- radius = 150 --150m radius
1109
- }
1110
- }
1111
- local tickTargets = {}
1112
- world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, function(obj)
1113
- if obj:isExist() then
1114
- table.insert(tickTargets, {
1115
- name = obj:getTypeName(),
1116
- distance = getDistance3D(wpnData.pos, obj:getPoint()), --3D distance
1117
- position = obj:getPoint(),
1118
- health = obj:getLife() or 0
1119
- })
1120
- end
1121
- return true
1122
- end)
1123
- 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")
1124
- for i, target in ipairs(tickTargets) do
1125
- 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)
1126
- end
1127
-
1128
-
1129
- ]]--
1130
-
1131
- --Scan potential blast zone in the last frame before impact
1132
- if splash_damage_options.track_pre_explosion then
1133
- local ip = land.getIP(wpnData.pos, wpnData.dir, lookahead(wpnData.speed))
1134
- local predictedImpact = ip or wpnData.pos
1135
-
1136
- local base_explosive, isShapedCharge = getWeaponExplosive(wpnData.name)
1137
- base_explosive = base_explosive * splash_damage_options.overall_scaling
1138
- if splash_damage_options.rocket_multiplier and wpnData.cat == Weapon.Category.ROCKET then
1139
- base_explosive = base_explosive * splash_damage_options.rocket_multiplier
1140
- end
1141
-
1142
- local explosionPower = base_explosive
1143
- if splash_damage_options.apply_shaped_charge_effects and isShapedCharge then
1144
- explosionPower = explosionPower * splash_damage_options.shaped_charge_multiplier
1145
- end
1146
-
1147
- local blastRadius = splash_damage_options.blast_search_radius * 2 --Wider post-scan (180m default)
1148
- if splash_damage_options.use_dynamic_blast_radius then
1149
- blastRadius = math.pow(explosionPower, 1/3) * 10 * splash_damage_options.dynamic_blast_radius_modifier
1150
- end
1151
-
1152
- --Tight scan while weapon exists
1153
- --local tightRadius = 50
1154
- --if splash_damage_options.use_dynamic_blast_radius then
1155
- --tightRadius = math.pow(explosionPower, 1/3) * 5 * splash_damage_options.dynamic_blast_radius_modifier
1156
- --end
1157
- local tightRadius = blastRadius --Use already calculated blastRadius
1158
- local volS = {
1159
- id = world.VolumeType.SPHERE,
1160
- params = {
1161
- point = wpnData.pos, --Use current pos
1162
- radius = tightRadius
1163
- }
1164
- }
1165
- local tightTargets = {}
1166
- local ifFound = function(foundObject, targets, center)
1167
- if foundObject:isExist() then
1168
- local category = foundObject:getCategory()
1169
- if (category == Object.Category.UNIT and (foundObject:getDesc().category == Unit.Category.GROUND_UNIT or foundObject:getDesc().category == Unit.Category.AIRPLANE)) or
1170
- category == Object.Category.STATIC then
1171
- table.insert(targets, {
1172
- name = foundObject:getTypeName(),
1173
- distance = getDistance(center, foundObject:getPoint()),
1174
- health = foundObject:getLife() or 0,
1175
- position = foundObject:getPoint(),
1176
- maxHealth = (category == Object.Category.UNIT and foundObject:getDesc().life) or foundObject:getLife() or 0,
1177
- unit = foundObject
1178
- })
1179
- end
1180
- end
1181
- return true
1182
- end
1183
- if splash_damage_options.track_pre_explosion_debug then
1184
- debugMsg("Scanning tight radius " .. tightRadius .. "m at current pos while weapon exists")
1185
- end
1186
- world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, volS, function(obj) ifFound(obj, tightTargets, wpnData.pos) end)
1187
- wpnData.tightTargets = tightTargets --Store for impact
1188
-
1189
- --Wider scan for lastKnownTargets
1190
- volS.params.point = predictedImpact
1191
- volS.params.radius = blastRadius
1192
- local foundTargets = {}
1193
- world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, volS, function(obj) ifFound(obj, foundTargets, predictedImpact) end)
1194
- wpnData.lastKnownTargets = foundTargets
1195
- end
1196
- --Submunition impact handling
1197
- local weaponData = explTable[wpnData.parent or wpnData.name] or { submunition_name = "unknown" }
1198
- if wpnData.name == weaponData.submunition_name then
1199
- local groundHeight = land.getHeight({x = wpnData.pos.x, y = wpnData.pos.z})
1200
- if wpnData.pos.y - groundHeight < 50 then --Impact threshold like old script
1201
- 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))
1202
- local parentWeaponData = explTable[wpnData.parent] or { submunition_count = 30, submunition_explosive = 1 }
1203
- local submunitionCount = parentWeaponData.submunition_count or 30
1204
- local submunitionPower = (parentWeaponData.submunition_explosive or 1) * splash_damage_options.cluster_bomblet_damage_modifier * splash_damage_options.overall_scaling
1205
- if splash_damage_options.cluster_bomblet_reductionmodifier then
1206
- if submunitionCount > 35 then
1207
- local reductionFactor = (60 - 35) / (247 - 35)
1208
- submunitionCount = 35 + math.floor((submunitionCount - 35) * reductionFactor)
1209
- if submunitionCount > 60 then submunitionCount = 60 end
1210
- end
1211
- end
1212
- --Use parent velocity if available, else submunition speed
1213
- local parentDir = wpnData.parentVelocity or wpnData.speed
1214
- local dispersionLength, dispersionWidth = calculate_dispersion(parentDir, 2000) --Match original 2000m
1215
- local dirMag = math.sqrt(parentDir.x^2 + parentDir.z^2)
1216
- local dir = dirMag > 0 and {x = parentDir.x / dirMag, z = parentDir.z / dirMag} or {x = 1, z = 0}
1217
- 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")
1218
- for i = 1, submunitionCount do
1219
- local theta = math.random() * 2 * math.pi
1220
- local r = math.sqrt(math.random())
1221
- local xOffset = r * dispersionLength * 0.5 * math.cos(theta)
1222
- local zOffset = r * dispersionWidth * 0.5 * math.sin(theta)
1223
- local subPos = {
1224
- x = wpnData.pos.x + (xOffset * dir.x - zOffset * dir.z),
1225
- z = wpnData.pos.z + (xOffset * dir.z + zOffset * dir.x)
1226
- }
1227
- subPos.y = land.getHeight({x = subPos.x, y = subPos.z})
1228
- debugMsg("Triggering bomblet #" .. i .. " for submunition '" .. wpnData.name .. "' at X: " .. string.format("%.0f", subPos.x) .. ", Z: " .. string.format("%.0f", subPos.z) .. " with power " .. submunitionPower)
1229
- trigger.action.explosion(subPos, submunitionPower)
1230
- end
1231
- table.insert(weaponsToRemove, wpn_id_)
1232
- end
1233
- end
1234
- else
1235
- --Weapon has impacted
1236
- debugMsg("Weapon " .. wpnData.name .. " no longer exists at " .. timer.getTime() .. "s")
1237
- local ip = land.getIP(wpnData.pos, wpnData.dir, lookahead(wpnData.speed)) --terrain intersection point with weapon's nose. Only search out 20 meters though.
1238
- local explosionPoint
1239
- if not ip then --use last calculated IP
1240
- explosionPoint = wpnData.pos
1241
- else --use intersection point
1242
- explosionPoint = ip
1243
- end
1244
- local chosenTargets = wpnData.tightTargets or {}
1245
- local safeToBlast = true
1246
- if splash_damage_options.ordnance_protection then
1247
- local checkVol = { id = world.VolumeType.SPHERE, params = { point = explosionPoint, radius = splash_damage_options.ordnance_protection_radius } }
1248
- 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")
1249
- world.searchObjects(Object.Category.WEAPON, checkVol, function(obj)
1250
- if obj:isExist() and tracked_weapons[obj.id_] then
1251
- safeToBlast = false
1252
- debugMsg("Skipping explosion for '" .. wpnData.name .. "' - nearby bomb '" .. tracked_weapons[obj.id_].name .. "' within " .. splash_damage_options.ordnance_protection_radius .. "m")
1253
- return false
1254
- end
1255
- return true
1256
- end)
1257
- end
1258
- if safeToBlast then
1259
- debugMsg("FinalPos Check for '" .. wpnData.name .. "': X: " .. string.format("%.0f", explosionPoint.x) .. ", Y: " .. string.format("%.0f", explosionPoint.y) .. ", Z: " .. string.format("%.0f", explosionPoint.z) .. ")")
1260
- local base_explosive, isShapedCharge = getWeaponExplosive(wpnData.name)
1261
- base_explosive = base_explosive * splash_damage_options.overall_scaling
1262
- if splash_damage_options.rocket_multiplier and wpnData.cat == Weapon.Category.ROCKET then
1263
- base_explosive = base_explosive * splash_damage_options.rocket_multiplier
1264
- end
1265
-
1266
- local explosionPower = base_explosive
1267
- if splash_damage_options.apply_shaped_charge_effects and isShapedCharge then
1268
- explosionPower = explosionPower * splash_damage_options.shaped_charge_multiplier
1269
- end
1270
-
1271
- local blastRadius = splash_damage_options.blast_search_radius * 2 --Wider post-scan (180m default)
1272
- if splash_damage_options.use_dynamic_blast_radius then
1273
- blastRadius = math.pow(explosionPower, 1/3) * 10 * splash_damage_options.dynamic_blast_radius_modifier
1274
- end
1275
-
1276
-
1277
- --Store pre-explosion state of all tracked weapons for detection
1278
- local preExplosionWeapons = {}
1279
- if splash_damage_options.ordnance_protection and splash_damage_options.detect_ordnance_destruction and splash_damage_options.larger_explosions then
1280
- for id, data in pairs(tracked_weapons) do
1281
- if data.wpn:isExist() then
1282
- preExplosionWeapons[id] = {
1283
- name = data.name,
1284
- pos = data.wpn:getPosition().p,
1285
- distance = getDistance3D(explosionPoint, data.wpn:getPosition().p),
1286
- explosive = getWeaponExplosive(data.name) --Store the explosive power
1287
- }
1288
- end
1289
- end
1290
- end
1291
- --Cluster Bomb Handling
1292
- local weaponData = explTable[wpnData.name] or { explosive = 0, shaped_charge = false }
1293
- local isCluster = weaponData.cluster or false
1294
- if splash_damage_options.cluster_enabled and isCluster then
1295
- local submunitionCount = weaponData.submunition_count or 30
1296
- local submunitionPower = (weaponData.submunition_explosive or 1) * splash_damage_options.cluster_bomblet_damage_modifier * splash_damage_options.overall_scaling
1297
- local submunitionName = weaponData.submunition_name or "unknown"
1298
- --Apply bomblet reduction logic if enabled
1299
- if splash_damage_options.cluster_bomblet_reductionmodifier then
1300
- if submunitionCount > 35 then
1301
- local reductionFactor = (60 - 35) / (247 - 35)
1302
- submunitionCount = 35 + math.floor((submunitionCount - 35) * reductionFactor)
1303
- if submunitionCount > 60 then submunitionCount = 60 end --Cap at 60
1304
- end
1305
- end
1306
- --Extended scan with general bomblet detection
1307
- timer.scheduleFunction(track_wpns_cluster_scan, {explosionPoint, wpnData.dir, wpnData.name, submunitionName, submunitionCount, submunitionPower, wpnData.speed}, timer.getTime() + 0.3)
1308
- else
1309
- --Standard explosion handling
1310
- if splash_damage_options.larger_explosions then
1311
- debugMsg("Triggering initial explosion for '" .. wpnData.name .. "' at power " .. explosionPower)
1312
- trigger.action.explosion(explosionPoint, explosionPower)
1313
- table.insert(recentExplosions, { pos = explosionPoint, time = timer.getTime(), radius = blastRadius })
1314
- debugMsg("Added to recentExplosions for '" .. wpnData.name .. "': X: " .. explosionPoint.x .. ", Y: " .. explosionPoint.y .. ", Z: " .. explosionPoint.z .. ", Time: " .. timer.getTime())
1315
- end
1316
- blastWave(explosionPoint, splash_damage_options.blast_search_radius, wpnData.ordnance, explosionPower, isShapedCharge)
1317
- end
1318
- --detect_ordnance_destruction comes before recent_large_explosion_snap in original
1319
- if splash_damage_options.ordnance_protection and splash_damage_options.detect_ordnance_destruction and splash_damage_options.larger_explosions then
1320
- timer.scheduleFunction(function(args)
1321
- local explosionPoint = args[1]
1322
- local blastRadius = args[2]
1323
- local triggeringWeapon = args[3]
1324
- local preExplosionWeapons = args[4]
1325
- for id, preData in pairs(preExplosionWeapons) do
1326
- if tracked_weapons[id] and not tracked_weapons[id].wpn:isExist() then
1327
- if preData.distance <= blastRadius then
1328
- 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)
1329
- gameMsg(msg)
1330
- debugMsg(msg)
1331
- env.info(msg)
1332
- if splash_damage_options.snap_to_ground_if_destroyed_by_large_explosion then
1333
- local groundPos = {
1334
- x = preData.pos.x,
1335
- y = land.getHeight({x = preData.pos.x, y = preData.pos.z}),
1336
- z = preData.pos.z
1337
- }
1338
- local destroyedWeaponPower, isShapedCharge = preData.explosive
1339
- destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.overall_scaling
1340
- if splash_damage_options.rocket_multiplier and tracked_weapons[id].cat == Weapon.Category.ROCKET then
1341
- destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.rocket_multiplier
1342
- end
1343
- if splash_damage_options.apply_shaped_charge_effects and isShapedCharge then
1344
- destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.shaped_charge_multiplier
1345
- end
1346
- 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)
1347
- trigger.action.explosion(groundPos, destroyedWeaponPower)
1348
- end
1349
- end
1350
- end
1351
- end
1352
- end, {explosionPoint, blastRadius, wpnData.name, preExplosionWeapons}, timer.getTime() + 0.2)
1353
- end
1354
- --recent_large_explosion_snap comes after main explosion and detect_ordnance_destruction
1355
- if splash_damage_options.ordnance_protection and splash_damage_options.larger_explosions and splash_damage_options.recent_large_explosion_snap and splash_damage_options.snap_to_ground_if_destroyed_by_large_explosion then
1356
- local currentTime = timer.getTime()
1357
- for id, data in pairs(tracked_weapons) do
1358
- if id ~= wpn_id_ and not data.wpn:isExist() then
1359
- local terrainHeight = land.getHeight({x = data.pos.x, y = data.pos.z})
1360
- local weaponHeight = data.pos.y - terrainHeight --Calculate height above ground
1361
- local isMidAir = weaponHeight > 5 --Still checks if above ground
1362
- local snapTriggered = false
1363
- for _, explosion in ipairs(recentExplosions) do
1364
- local timeDiff = currentTime - explosion.time
1365
- local distance = getDistance3D(data.pos, explosion.pos)
1366
- 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")
1367
- if timeDiff <= splash_damage_options.recent_large_explosion_time and distance <= splash_damage_options.recent_large_explosion_range then
1368
- if isMidAir and weaponHeight <= splash_damage_options.max_snapped_height then --New height check
1369
- local groundPos = { x = data.pos.x, y = terrainHeight, z = data.pos.z }
1370
- local destroyedWeaponPower, isShapedCharge = getWeaponExplosive(data.name)
1371
- destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.overall_scaling
1372
- if splash_damage_options.rocket_multiplier and data.cat == Weapon.Category.ROCKET then
1373
- destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.rocket_multiplier
1374
- end
1375
- if splash_damage_options.apply_shaped_charge_effects and isShapedCharge then
1376
- destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.shaped_charge_multiplier
1377
- end
1378
- 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 .. " (Height: " .. string.format("%.0f", weaponHeight) .. "m)")
1379
- trigger.action.explosion(groundPos, destroyedWeaponPower)
1380
- snapTriggered = true
1381
- table.insert(weaponsToRemove, id)
1382
- break
1383
- elseif isMidAir then
1384
- debugMsg("Weapon " .. data.name .. " destroyed above max_snapped_height (" .. splash_damage_options.max_snapped_height .. "m) at " .. string.format("%.0f", weaponHeight) .. "m, skipping snap")
1385
- else
1386
- 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")
1387
- snapTriggered = true
1388
- break
1389
- end
1390
- end
1391
- end
1392
- if not snapTriggered then
1393
- if isMidAir then
1394
- 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")
1395
- else
1396
- debugMsg("Weapon " .. data.name .. " impacted ground, not processed by recent large explosion settings")
1397
- end
1398
- end
1399
- end
1400
- end
1401
- local newExplosions = {}
1402
- for _, explosion in ipairs(recentExplosions) do
1403
- if currentTime - explosion.time <= splash_damage_options.recent_large_explosion_time then
1404
- table.insert(newExplosions, explosion)
1405
- end
1406
- end
1407
- recentExplosions = newExplosions
1408
- end
1409
- --Mark units as destroyed to avoid MiST accessing them
1410
- local destroyedUnits = {}
1411
- for _, target in ipairs(chosenTargets) do
1412
- if target.unit:isExist() and target.health > 0 and target.unit:getLife() <= 0 then
1413
- destroyedUnits[target.name] = true
1414
- debugMsg("Marked " .. target.name .. " as destroyed pre-impact")
1415
- end
1416
- end
1417
- --Schedule explosion handling with original 0.1-second delay, enhanced error handling
1418
- timer.scheduleFunction(function(args)
1419
- local finalPos = args[1]
1420
- local explosionPoint = args[2]
1421
- local explosionPower = args[3]
1422
- local isShapedCharge = args[4]
1423
- local blastRadius = args[5]
1424
- local chosenTargets = args[6]
1425
- local weaponName = args[7]
1426
- local wpnData = args[8]
1427
- if splash_damage_options.debug then
1428
- debugMsg("Starting impact handling for " .. weaponName .. " at " .. timer.getTime() .. "s")
1429
- end
1430
- local status, err = pcall(function()
1431
- --Log pre-explosion targets
1432
- if splash_damage_options.track_pre_explosion then
1433
- if #chosenTargets > 0 then
1434
- local msg = "Targets in blast zone for " .. weaponName .. " BEFORE explosion (last frame, using finalPos):\n"
1435
- for i, target in ipairs(chosenTargets) do
1436
- msg = msg .. "- " .. target.name .. " (Dist: " .. string.format("%.1f", target.distance) .. "m, Health: " .. target.health .. ")\n"
1437
- end
1438
- debugMsg(msg)
1439
- env.info("SplashDamage Pre-Explosion (Last Frame): " .. msg)
1440
- else
1441
- debugMsg("No targets in blast zone for " .. weaponName .. " BEFORE explosion (last frame)")
1442
- env.info("SplashDamage Pre-Explosion (Last Frame): No targets in blast zone for " .. weaponName)
1443
- end
1444
- end
1445
-
1446
- blastWave(explosionPoint, splash_damage_options.blast_search_radius, wpnData.ordnance, explosionPower, isShapedCharge)
1447
-
1448
- --Post-explosion analysis and queue cargo effects
1449
- if splash_damage_options.track_pre_explosion then
1450
- timer.scheduleFunction(function(innerArgs)
1451
- local impactPoint = innerArgs[1]
1452
- local blastRadius = innerArgs[2]
1453
- local preExplosionTargets = innerArgs[3] or {}
1454
- local weaponName = innerArgs[4]
1455
- local weaponPower = innerArgs[5]
1456
- if splash_damage_options.debug == true then
1457
- debugMsg("Starting post-explosion analysis for " .. weaponName .. " at " .. timer.getTime() .. "s")
1458
- end
1459
-
1460
- --Scan all units in wider radius
1461
- local postExplosionTargets = {}
1462
- local volS = {
1463
- id = world.VolumeType.SPHERE,
1464
- params = {
1465
- point = impactPoint,
1466
- radius = blastRadius
1467
- }
1468
- }
1469
-
1470
- local ifFound = function(foundObject)
1471
- if foundObject:isExist() then
1472
- local category = foundObject:getCategory()
1473
- if (category == Object.Category.UNIT and (foundObject:getDesc().category == Unit.Category.GROUND_UNIT or foundObject:getDesc().category == Unit.Category.AIRPLANE)) or
1474
- category == Object.Category.STATIC then
1475
- table.insert(postExplosionTargets, {
1476
- name = foundObject:getTypeName(),
1477
- health = foundObject:getLife() or 0,
1478
- position = foundObject:getPoint(),
1479
- maxHealth = (category == Object.Category.UNIT and foundObject:getDesc().life) or foundObject:getLife() or 0
1480
- })
1481
- end
1482
- end
1483
- return true
1484
- end
1485
-
1486
- world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, volS, ifFound)
1487
-
1488
- local msg = "Post-explosion analysis for " .. weaponName .. ":\n"
1489
-
1490
- --Match pre-detected units
1491
- for _, preTarget in ipairs(preExplosionTargets) do
1492
- local found = false
1493
- local postHealth = 0
1494
- local postPosition = nil
1495
- for _, postTarget in ipairs(postExplosionTargets) do
1496
- if preTarget.name == postTarget.name and getDistance(preTarget.position, postTarget.position) < 1 then
1497
- found = true
1498
- postHealth = postTarget.health
1499
- postPosition = postTarget.position
1500
- break
1501
- end
1502
- end
1503
-
1504
- local healthPercent = preTarget.maxHealth > 0 and (postHealth / preTarget.maxHealth * 100) or 0
1505
- local status = ""
1506
-
1507
- if not found or postHealth <= 0 then
1508
- status = "WAS FULLY DESTROYED"
1509
- elseif healthPercent < splash_damage_options.cargo_damage_threshold then
1510
- status = "WAS DAMAGED BELOW THRESHOLD"
1511
- else
1512
- status = "SURVIVED (Health: " .. postHealth .. ")"
1513
- end
1514
-
1515
- --Always include coords in status message
1516
- local coords = found and postPosition or preTarget.position
1517
- local statusMsg = status .. " AT " .. string.format("X: %.0f, Y: %.0f, Z: %.0f", coords.x, coords.y, coords.z) .. " (Pre: " .. preTarget.health .. ", Post: " .. postHealth .. ")"
1518
- --Check if target is in cargoUnits and within blast radius
1519
- local cargoData = cargoUnits[preTarget.name]
1520
- if cargoData and preTarget.distance <= blastRadius and
1521
- (not found or postHealth <= 0 or healthPercent < splash_damage_options.cargo_damage_threshold) then
1522
-
1523
- if splash_damage_options.enable_cargo_effects then
1524
- local cargoPower = cargoData.cargoExplosionPower or weaponPower --Use fixed power or fallback
1525
- table.insert(cargoEffectsQueue, {
1526
- name = preTarget.name,
1527
- distance = preTarget.distance,
1528
- coords = coords,
1529
- power = cargoPower,
1530
- explosion = cargoData.cargoExplosion,
1531
- cookOff = cargoData.cargoCookOff,
1532
- cookOffCount = cargoData.cookOffCount,
1533
- cookOffPower = cargoData.cookOffPower,
1534
- cookOffDuration = cargoData.cookOffDuration,
1535
- cookOffRandomTiming = cargoData.cookOffRandomTiming,
1536
- cookOffPowerRandom = cargoData.cookOffPowerRandom,
1537
- isTanker = cargoData.isTanker,
1538
- flameSize = cargoData.flameSize,
1539
- flameDuration = cargoData.flameDuration
1540
- })
1541
- statusMsg = statusMsg .. " WITH CARGO EXPLOSION (Power: " .. cargoPower .. ")"
1542
- if cargoData.cargoCookOff and cargoData.cookOffCount > 0 then
1543
- statusMsg = statusMsg .. " WITH COOK-OFF (" .. cargoData.cookOffCount .. " blasts over " .. cargoData.cookOffDuration .. "s)"
1544
- end
1545
- end
1546
- end
1547
-
1548
- msg = msg .. "- " .. preTarget.name .. " " .. statusMsg .. "\n"
1549
- end
1550
- --Check for additional units
1551
- for _, postTarget in ipairs(postExplosionTargets) do
1552
- local isPreDetected = false
1553
- for _, preTarget in ipairs(preExplosionTargets) do
1554
- if preTarget.name == postTarget.name and getDistance(preTarget.position, postTarget.position) < 1 then
1555
- isPreDetected = true
1556
- break
1557
- end
1558
- end
1559
- if not isPreDetected then
1560
- local coords = postTarget.position
1561
- local healthPercent = postTarget.maxHealth > 0 and (postTarget.health / postTarget.maxHealth * 100) or 0
1562
- local status = postTarget.health <= 0 and "WAS FULLY DESTROYED" or
1563
- (healthPercent < splash_damage_options.cargo_damage_threshold and "WAS DAMAGED BELOW THRESHOLD" or
1564
- "SURVIVED (Health: " .. postTarget.health .. ")")
1565
- local statusMsg = status .. " AT " .. string.format("X: %.0f, Y: %.0f, Z: %.0f", coords.x, coords.y, coords.z) .. " (Pre: Unknown, Post: " .. postTarget.health .. ")"
1566
- local cargoData = cargoUnits[postTarget.name]
1567
- if cargoData and (postTarget.health <= 0 or healthPercent < splash_damage_options.cargo_damage_threshold) then
1568
- if splash_damage_options.enable_cargo_effects then
1569
- local cargoPower = cargoData.cargoExplosionPower or weaponPower --Use fixed power or fallback
1570
- local distance = getDistance(impactPoint, coords)
1571
- table.insert(cargoEffectsQueue, {
1572
- name = postTarget.name,
1573
- distance = distance,
1574
- coords = coords,
1575
- power = cargoPower,
1576
- explosion = cargoData.cargoExplosion,
1577
- cookOff = cargoData.cargoCookOff,
1578
- cookOffCount = cargoData.cookOffCount,
1579
- cookOffPower = cargoData.cookOffPower,
1580
- cookOffDuration = cargoData.cookOffDuration,
1581
- cookOffRandomTiming = cargoData.cookOffRandomTiming,
1582
- cookOffPowerRandom = cargoData.cookOffPowerRandom,
1583
- isTanker = cargoData.isTanker,
1584
- flameSize = cargoData.flameSize,
1585
- flameDuration = cargoData.flameDuration
1586
- })
1587
- statusMsg = statusMsg .. " WITH CARGO EXPLOSION (Power: " .. cargoPower .. ")"
1588
- if cargoData.cargoCookOff and cargoData.cookOffCount > 0 then
1589
- statusMsg = statusMsg .. " WITH COOK-OFF (" .. cargoData.cookOffCount .. " blasts over " .. cargoData.cookOffDuration .. "s)"
1590
- end
1591
- end
1592
- end
1593
- msg = msg .. "- " .. postTarget.name .. " " .. statusMsg .. "\n"
1594
- end
1595
- end
1596
-
1597
- --Schedule all queued cargo effects
1598
- if #cargoEffectsQueue > 0 then
1599
- local effectIndex = 0
1600
- local processedCargoUnits = {} --Track processed units
1601
- local flamePositions = {} --Track flame coords with 3m radius
1602
- for _, effect in ipairs(cargoEffectsQueue) do
1603
- local unitKey = effect.name .. "_" .. effect.coords.x .. "_" .. effect.coords.z
1604
- if not processedUnitsGlobal[unitKey] and not processedCargoUnits[unitKey] then
1605
- if effect.explosion then
1606
- debugMsg("Triggering cargo explosion for " .. effect.name .. " at " .. string.format("%.1f", effect.distance) .. "m with power " .. effect.power .. " scheduled at " .. effectIndex .. "s")
1607
- timer.scheduleFunction(function(params)
1608
- 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])
1609
- trigger.action.explosion(params[1], params[2])
1610
- end, {effect.coords, effect.power}, timer.getTime() + effectIndex + 0.1) --Slight delay for visibility
1611
- if effect.isTanker then
1612
- local flameSize = effect.flameSize or 3
1613
- local flameDuration = effect.flameDuration --Use cargoUnits value directly, no default
1614
- local flameDensity = 1.0 --Max density for visibility
1615
- local effectId = effectSmokeId
1616
- effectSmokeId = effectSmokeId + 1
1617
- --Check for nearby flames within 3m
1618
- local isDuplicate = false
1619
- for _, pos in pairs(flamePositions) do
1620
- if getDistance3D(effect.coords, pos) < 3 then
1621
- isDuplicate = true
1622
- 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))
1623
- break
1624
- end
1625
- end
1626
- if not isDuplicate then
1627
- 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")
1628
- timer.scheduleFunction(function(params)
1629
- --Adjust Y-coordinate to terrain height + offset
1630
- local terrainHeight = land.getHeight({x = params[1].x, y = params[1].z})
1631
- local adjustedCoords = {x = params[1].x, y = terrainHeight + 2, z = params[1].z}
1632
- debugMsg("Spawning flame effect at X: " .. string.format("%.0f", adjustedCoords.x) .. ", Y: " .. string.format("%.0f", adjustedCoords.y) .. ", Z: " .. string.format("%.0f", adjustedCoords.z))
1633
- trigger.action.explosion(adjustedCoords, 10) --Small explosion to force visibility
1634
- trigger.action.effectSmokeBig(adjustedCoords, params[2], params[3], params[4])
1635
- end, {effect.coords, flameSize, flameDensity, effectId}, timer.getTime() + effectIndex + 0.2) --Slight delay
1636
- timer.scheduleFunction(function(id)
1637
- debugMsg("Stopping flame effect for " .. effect.name .. " (ID: " .. id .. ")")
1638
- trigger.action.effectSmokeStop(id)
1639
- end, effectId, timer.getTime() + effectIndex + flameDuration + 0.2)
1640
- table.insert(flamePositions, effect.coords)
1641
- end
1642
- end
1643
- end
1644
- debugMsg("Checking cook-off for " .. effect.name .. ": cookOff=" .. tostring(effect.cookOff) .. ", count=" .. tostring(effect.cookOffCount))
1645
- if effect.cookOff and effect.cookOffCount > 0 then
1646
- 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")
1647
- for i = 1, effect.cookOffCount do
1648
- local delay = effect.cookOffRandomTiming and math.random() * effect.cookOffDuration or (i - 1) * (effect.cookOffDuration / effect.cookOffCount)
1649
- local basePower = effect.cookOffPower
1650
- local powerVariation = effect.cookOffPowerRandom / 100
1651
- local cookOffPower = effect.cookOffPowerRandom == 0 and basePower or basePower * (1 + powerVariation * (math.random() * 2 - 1))
1652
- 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))
1653
- timer.scheduleFunction(function(params)
1654
- local pos = params[1]
1655
- local power = params[2]
1656
- debugMsg("Executing cook-off at " .. string.format("X: %.0f, Y: %.0f, Z: %.0f", pos.x, pos.y, pos.z) .. " with power " .. power)
1657
- trigger.action.explosion(pos, power)
1658
- end, {effect.coords, cookOffPower}, timer.getTime() + effectIndex + delay)
1659
- end
1660
- --Debris burst only if cook-off is true and enabled
1661
- if splash_damage_options.debris_effects then
1662
- local debrisCount = math.random(splash_damage_options.debris_count_min, splash_damage_options.debris_count_max)
1663
- for j = 1, debrisCount do
1664
- --Random spherical offset
1665
- local theta = math.random() * 2 * math.pi --Horizontal angle
1666
- local phi = math.acos(math.random() * 2 - 1) --Vertical angle for sphere
1667
- local minDist = splash_damage_options.debris_max_distance * 0.1 --10% of max
1668
- local maxDist = splash_damage_options.debris_max_distance
1669
- local r = math.random() * (maxDist - minDist) + minDist --10% to full max distance
1670
- local debrisX = effect.coords.x + r * math.sin(phi) * math.cos(theta)
1671
- local debrisZ = effect.coords.z + r * math.sin(phi) * math.sin(theta)
1672
- local terrainY = land.getHeight({x = debrisX, y = debrisZ})
1673
- local debrisY = terrainY + math.random() * maxDist --0 to max_distance above ground
1674
- local debrisPos = {x = debrisX, y = debrisY, z = debrisZ}
1675
- local debrisPower = splash_damage_options.debris_power
1676
- local debrisDelay = (j - 1) * (effect.cookOffDuration / debrisCount) --Spread over cook-off duration
1677
- timer.scheduleFunction(function(debrisArgs)
1678
- local dPos = debrisArgs[1]
1679
- local dPower = debrisArgs[2]
1680
- 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)
1681
- trigger.action.explosion(dPos, dPower)
1682
- end, {debrisPos, debrisPower}, timer.getTime() + effectIndex + debrisDelay)
1683
- end
1684
- end
1685
- end
1686
-
1687
-
1688
- processedCargoUnits[unitKey] = true
1689
- processedUnitsGlobal[unitKey] = true
1690
- effectIndex = effectIndex + 3 --3 secs spacing if not random
1691
- end
1692
- end
1693
- --Clear the queue after scheduling
1694
- cargoEffectsQueue = {}
1695
- end
1696
-
1697
- debugMsg(msg)
1698
- env.info("SplashDamage Post-Explosion: " .. msg)
1699
- end, {finalPos, blastRadius, chosenTargets, weaponName, explosionPower}, timer.getTime() + 1)
1700
- end
1701
- end)
1702
- if not status then
1703
- debugMsg("Impact handling error for '" .. weaponName .. "': " .. err)
1704
- end
1705
- end, {explosionPoint, explosionPoint, explosionPower, isShapedCharge, blastRadius, chosenTargets, wpnData.name, wpnData}, timer.getTime() + 0.1)
1706
- else
1707
- debugMsg("Explosion skipped due to ordnance protection for '" .. wpnData.name .. "'")
1708
- if splash_damage_options.larger_explosions then
1709
- table.insert(recentExplosions, { pos = explosionPoint, time = timer.getTime(), radius = blastRadius })
1710
- debugMsg("Skipped explosion logged for snap check for '" .. wpnData.name .. "': X: " .. explosionPoint.x .. ", Y: " .. explosionPoint.y .. ", Z: " .. explosionPoint.z .. ", Time: " .. timer.getTime())
1711
- end
1712
- end
1713
- table.insert(weaponsToRemove, wpn_id_)
1714
- end
1715
- end)
1716
- if not status then
1717
- debugMsg("Error in track_wpns for '" .. (wpnData.name or "unknown weapon") .. "': " .. err)
1718
- end
1719
- end
1720
- --Perform all removals after iteration
1721
- for _, id in ipairs(weaponsToRemove) do
1722
- tracked_weapons[id] = nil
1723
- end
1724
- return timer.getTime() + refreshRate
1725
- end
1726
- function onWpnEvent(event)
1727
- if event.id == world.event.S_EVENT_SHOT then
1728
- if event.weapon then
1729
- local ordnance = event.weapon
1730
- local typeName = trim(ordnance:getTypeName())
1731
- if splash_damage_options.debug then
1732
- env.info("Weapon fired: [" .. typeName .. "]")
1733
- debugMsg("Weapon fired: [" .. typeName .. "]")
1734
- end
1735
- if string.find(typeName, "weapons.shells") then
1736
- if splash_damage_options.debug then
1737
- debugMsg("Event shot, but not tracking: " .. typeName)
1738
- env.info("SplashDamage: event shot, but not tracking: " .. typeName .. " (" .. event.initiator:getTypeName() .. ")")
1739
- end
1740
- return
1741
- end
1742
-
1743
- --Check if weapon is in explTable before tracking
1744
- if not explTable[typeName] then
1745
- env.info("SplashDamage: " .. typeName .. " missing from script (" .. event.initiator:getTypeName() .. ")")
1746
- if splash_damage_options.weapon_missing_message == true then
1747
- trigger.action.outText("SplashDamage: " .. typeName .. " missing from script (" .. (event.initiator and event.initiator:isExist() and event.initiator:getTypeName() or "no initiator") .. ")", 3)
1748
- -- if mist and mist.utils and mist.utils.tableShow then --Only if MiST is present
1749
- -- local success, desc = pcall(mist.utils.tableShow, ordnance:getDesc())
1750
- -- if success then
1751
- -- debugMsg("desc for [" .. typeName .. "]: " .. desc)
1752
- -- else
1753
- -- debugMsg("Could not retrieve description for [" .. typeName .. "]. Object may no longer exist.")
1754
- -- end
1755
- -- end
1756
- env.info("Current keys in explTable:")
1757
- for k, v in pairs(explTable) do
1758
- env.info("Key: [" .. k .. "]")
1759
- end
1760
- end
1761
- return --Skip tracking this weapon since its not in the table
1762
- end
1763
-
1764
- if (ordnance:getDesc().category ~= 0) and event.initiator then
1765
- if ordnance:getDesc().category == 1 then
1766
- if (ordnance:getDesc().MissileCategory ~= 1 and ordnance:getDesc().MissileCategory ~= 2) then
1767
- 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() }
1768
- end
1769
- else
1770
- 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() }
1771
- end
1772
- end
1773
- end
1774
- end
1775
- end
1776
-
1777
- local function protectedCall(...)
1778
- local status, retval = pcall(...)
1779
- if not status then
1780
- env.warning("Splash damage script error... gracefully caught! " .. retval, true)
1781
- end
1782
- end
1783
-
1784
- function WpnHandler:onEvent(event)
1785
- protectedCall(onWpnEvent, event)
1786
- end
1787
-
1788
- function explodeObject(args)
1789
- local point = args[1]
1790
- local distance = args[2]
1791
- local power = args[3]
1792
- trigger.action.explosion(point, power)
1793
- end
1794
-
1795
- function blastWave(_point, _radius, weapon, power, isShapedCharge)
1796
- if isShapedCharge then
1797
- _radius = _radius * splash_damage_options.shaped_charge_multiplier
1798
- end
1799
- if splash_damage_options.use_dynamic_blast_radius then
1800
- local dynamicRadius = math.pow(power, 1/3) * 5 * splash_damage_options.dynamic_blast_radius_modifier
1801
- if isShapedCharge then
1802
- _radius = dynamicRadius * splash_damage_options.shaped_charge_multiplier
1803
- else
1804
- _radius = dynamicRadius
1805
- end
1806
- end
1807
-
1808
- local foundUnits = {}
1809
- local volS = {
1810
- id = world.VolumeType.SPHERE,
1811
- params = {
1812
- point = _point,
1813
- radius = _radius
1814
- }
1815
- }
1816
-
1817
- local ifFound = function(foundObject, val)
1818
- if foundObject:getDesc().category == Unit.Category.GROUND_UNIT and foundObject:getCategory() == Object.Category.UNIT then
1819
- foundUnits[#foundUnits + 1] = foundObject
1820
- end
1821
- if foundObject:getDesc().category == Unit.Category.GROUND_UNIT then
1822
- if splash_damage_options.blast_stun == true then
1823
- --suppressUnit(foundObject, 2, weapon)
1824
- end
1825
- end
1826
- if splash_damage_options.wave_explosions then
1827
- local obj = foundObject
1828
- local obj_location = obj:getPoint()
1829
- local dist = getDistance(_point, obj_location)
1830
- local timing = dist / 500
1831
- if obj:isExist() and tableHasKey(obj:getDesc(), "box") then
1832
- local length = (obj:getDesc().box.max.x + math.abs(obj:getDesc().box.min.x))
1833
- local height = (obj:getDesc().box.max.y + math.abs(obj:getDesc().box.min.y))
1834
- local depth = (obj:getDesc().box.max.z + math.abs(obj:getDesc().box.min.z))
1835
- local _length = length
1836
- local _depth = depth
1837
- if depth > length then
1838
- _length = depth
1839
- _depth = length
1840
- end
1841
- local surface_distance = dist - _depth / 2
1842
- local scaled_power_factor = 0.006 * power + 1
1843
- local intensity = (power * scaled_power_factor) / (4 * math.pi * surface_distance^2)
1844
- local surface_area = _length * height
1845
- local damage_for_surface = intensity * surface_area
1846
- if damage_for_surface > splash_damage_options.cascade_damage_threshold then
1847
- local explosion_size = damage_for_surface
1848
- if obj:getDesc().category == Unit.Category.STRUCTURE then
1849
- explosion_size = intensity * splash_damage_options.static_damage_boost
1850
- end
1851
- if explosion_size > power then explosion_size = power end
1852
- local triggerExplosion = false
1853
- if splash_damage_options.always_cascade_explode then
1854
- triggerExplosion = true
1855
- else
1856
- if obj:getDesc().life then
1857
- local healthPercent = (obj:getLife() / obj:getDesc().life) * 100
1858
- if healthPercent <= splash_damage_options.cascade_explode_threshold then
1859
- triggerExplosion = true
1860
- end
1861
- --Queue cargo effects for units below
1862
- local cargoData = cargoUnits[obj:getTypeName()]
1863
- if cargoData and healthPercent <= splash_damage_options.cargo_damage_threshold and splash_damage_options.enable_cargo_effects then
1864
- local cargoPower = power * cargoData.cargoExplosionMult
1865
- table.insert(cargoEffectsQueue, {
1866
- name = obj:getTypeName(),
1867
- distance = dist,
1868
- coords = obj_location,
1869
- power = cargoPower,
1870
- explosion = cargoData.cargoExplosion,
1871
- cookOff = cargoData.cargoCookOff,
1872
- cookOffCount = cargoData.cookOffCount,
1873
- cookOffPower = cargoData.cookOffPower,
1874
- cookOffDuration = cargoData.cookOffDuration,
1875
- cookOffRandomTiming = cargoData.cookOffRandomTiming,
1876
- cookOffPowerRandom = cargoData.cookOffPowerRandom,
1877
- isTanker = cargoData.isTanker,
1878
- flameSize = cargoData.flameSize,
1879
- flameDuration = cargoData.flameDuration
1880
- })
1881
- end
1882
- else
1883
- triggerExplosion = true
1884
- end
1885
- if not triggerExplosion and obj:getDesc().category == Unit.Category.GROUND_UNIT then
1886
- local health = obj:getLife() or 0
1887
- if health <= 0 then
1888
- triggerExplosion = true
1889
- end
1890
- end
1891
- end
1892
- if triggerExplosion then
1893
- timer.scheduleFunction(explodeObject, {obj_location, dist, explosion_size * splash_damage_options.cascade_scaling}, timer.getTime() + timing)
1894
- end
1895
- end
1896
- end
1897
- end
1898
- return true
1899
- end
1900
-
1901
- world.searchObjects(Object.Category.UNIT, volS, ifFound)
1902
- world.searchObjects(Object.Category.STATIC, volS, ifFound)
1903
- world.searchObjects(Object.Category.SCENERY, volS, ifFound)
1904
- world.searchObjects(Object.Category.CARGO, volS, ifFound)
1905
- if splash_damage_options.damage_model then
1906
- timer.scheduleFunction(modelUnitDamage, foundUnits, timer.getTime() + 1.5)
1907
- end
1908
- end
1909
-
1910
- function modelUnitDamage(units)
1911
- for i, unit in ipairs(units) do
1912
- if unit:isExist() then
1913
- local health = (unit:getLife() / unit:getDesc().life) * 100
1914
- if unit:hasAttribute("Infantry") and health > 0 then
1915
- if health <= splash_damage_options.infantry_cant_fire_health then
1916
- unit:getController():setOption(AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD)
1917
- end
1918
- end
1919
- if unit:getDesc().category == Unit.Category.GROUND_UNIT and (not unit:hasAttribute("Infantry")) and health > 0 then
1920
- if health <= splash_damage_options.unit_cant_fire_health then
1921
- unit:getController():setOption(AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD)
1922
- gameMsg(unit:getTypeName() .. " weapons disabled")
1923
- end
1924
- if health <= splash_damage_options.unit_disabled_health and health > 0 then
1925
- unit:getController():setTask({id = 'Hold', params = {}})
1926
- unit:getController():setOnOff(false)
1927
- gameMsg(unit:getTypeName() .. " disabled")
1928
- end
1929
- end
1930
- end
1931
- end
1932
- end
1933
-
1934
- function updateSplashDamageSetting(setting, increment)
1935
- if not splash_damage_options[setting] then
1936
- env.info("Error: Setting " .. setting .. " does not exist.")
1937
- return
1938
- end
1939
-
1940
- local newValue = math.max(0, splash_damage_options[setting] + increment)
1941
- env.info("Updating " .. setting .. " from " .. tostring(splash_damage_options[setting]) .. " to " .. tostring(newValue))
1942
- splash_damage_options[setting] = newValue
1943
- trigger.action.outText("Updated " .. setting .. " to: " .. tostring(splash_damage_options[setting]), 5)
1944
- end
1945
-
1946
- function toggleSplashDamageSetting(setting)
1947
- splash_damage_options[setting] = not splash_damage_options[setting]
1948
- trigger.action.outText("Toggled " .. setting .. " to: " .. tostring(splash_damage_options[setting]), 5)
1949
-
1950
- if setting == "enable_radio_menu" then
1951
- if splash_damage_options.enable_radio_menu then
1952
- addSplashDamageMenu()
1953
- else
1954
- missionCommands.removeItem(splash_damage_menu)
1955
- splash_damage_menu = nil
1956
- end
1957
- end
1958
- end
1959
-
1960
- function addValueAdjustmentCommands(menu, setting)
1961
- missionCommands.addCommand("+0.1", menu, updateSplashDamageSetting, setting, 0.1)
1962
- missionCommands.addCommand("+1", menu, updateSplashDamageSetting, setting, 1)
1963
- missionCommands.addCommand("+10", menu, updateSplashDamageSetting, setting, 10)
1964
- missionCommands.addCommand("+100", menu, updateSplashDamageSetting, setting, 100)
1965
-
1966
- missionCommands.addCommand("-0.1", menu, updateSplashDamageSetting, setting, -0.1)
1967
- missionCommands.addCommand("-1", menu, updateSplashDamageSetting, setting, -1)
1968
- missionCommands.addCommand("-10", menu, updateSplashDamageSetting, setting, -10)
1969
- missionCommands.addCommand("-100", menu, updateSplashDamageSetting, setting, -100)
1970
- end
1971
-
1972
- function exitSplashDamageMenu()
1973
- if splash_damage_menu then
1974
- missionCommands.removeItem(splash_damage_menu)
1975
- splash_damage_menu = nil
1976
- end
1977
- end
1978
-
1979
- function addSplashDamageMenu()
1980
- if not splash_damage_options.enable_radio_menu then return end
1981
-
1982
- if splash_damage_menu then
1983
- missionCommands.removeItem(splash_damage_menu)
1984
- end
1985
-
1986
- splash_damage_menu = missionCommands.addSubMenu("Splash Damage Settings")
1987
-
1988
- --Page 1: Debug & General Settings
1989
- local debugGeneralMenu = missionCommands.addSubMenu("Debug & General Settings", splash_damage_menu)
1990
- missionCommands.addCommand("Toggle Game Messages", debugGeneralMenu, toggleSplashDamageSetting, "game_messages")
1991
- missionCommands.addCommand("Toggle Debug Messages", debugGeneralMenu, toggleSplashDamageSetting, "debug")
1992
- missionCommands.addCommand("Toggle Weapon Missing Messages", debugGeneralMenu, toggleSplashDamageSetting, "weapon_missing_message")
1993
- missionCommands.addCommand("Toggle Pre-Explosion Debug", debugGeneralMenu, toggleSplashDamageSetting, "track_pre_explosion_debug")
1994
- missionCommands.addCommand("Toggle Damage Model", debugGeneralMenu, toggleSplashDamageSetting, "damage_model")
1995
- missionCommands.addCommand("Toggle Blast Stun", debugGeneralMenu, toggleSplashDamageSetting, "blast_stun")
1996
- local unitDisabledMenu = missionCommands.addSubMenu("Unit Disabled Health", debugGeneralMenu)
1997
- addValueAdjustmentCommands(unitDisabledMenu, "unit_disabled_health")
1998
- local unitCantFireMenu = missionCommands.addSubMenu("Unit Cant Fire Health", debugGeneralMenu)
1999
- addValueAdjustmentCommands(unitCantFireMenu, "unit_cant_fire_health")
2000
- local infantryCantFireMenu = missionCommands.addSubMenu("Infantry Cant Fire Health", debugGeneralMenu)
2001
- addValueAdjustmentCommands(infantryCantFireMenu, "infantry_cant_fire_health")
2002
- local rocketMultiplierMenu = missionCommands.addSubMenu("Rocket Multiplier", debugGeneralMenu)
2003
- addValueAdjustmentCommands(rocketMultiplierMenu, "rocket_multiplier")
2004
- --Page 2/3: Explosions
2005
- local explosionCargoMenu = missionCommands.addSubMenu("Explosion Settings", splash_damage_menu)
2006
- local staticDamageMenu = missionCommands.addSubMenu("Static Damage Boost", explosionCargoMenu)
2007
- addValueAdjustmentCommands(staticDamageMenu, "static_damage_boost")
2008
- missionCommands.addCommand("Toggle Wave Explosions", explosionCargoMenu, toggleSplashDamageSetting, "wave_explosions")
2009
- missionCommands.addCommand("Toggle Larger Explosions", explosionCargoMenu, toggleSplashDamageSetting, "larger_explosions")
2010
- local blastRadiusMenu = missionCommands.addSubMenu("Blast Search Radius", explosionCargoMenu)
2011
- addValueAdjustmentCommands(blastRadiusMenu, "blast_search_radius")
2012
-
2013
- local overallScalingMenu = missionCommands.addSubMenu("Overall Scaling", explosionCargoMenu)
2014
- addValueAdjustmentCommands(overallScalingMenu, "overall_scaling")
2015
- missionCommands.addCommand("Toggle Shaped Charge Effects", explosionCargoMenu, toggleSplashDamageSetting, "apply_shaped_charge_effects")
2016
- local shapedChargeMenu = missionCommands.addSubMenu("Shaped Charge Multiplier", explosionCargoMenu)
2017
- addValueAdjustmentCommands(shapedChargeMenu, "shaped_charge_multiplier")
2018
- missionCommands.addCommand("Toggle Dynamic Blast Radius", explosionCargoMenu, toggleSplashDamageSetting, "use_dynamic_blast_radius")
2019
- local dynamicBlastMenu = missionCommands.addSubMenu("Dynamic Blast Radius Modifier", explosionCargoMenu)
2020
- addValueAdjustmentCommands(dynamicBlastMenu, "dynamic_blast_radius_modifier")
2021
-
2022
- local explosionCargoMenu = missionCommands.addSubMenu("Cascade Settings", splash_damage_menu)
2023
- local cascadeScalingMenu = missionCommands.addSubMenu("Cascade Scaling", explosionCargoMenu)
2024
- addValueAdjustmentCommands(cascadeScalingMenu, "cascade_scaling")
2025
- local cascadeExplodeThresholdMenu = missionCommands.addSubMenu("Cascade Explode Threshold", explosionCargoMenu)
2026
- addValueAdjustmentCommands(cascadeExplodeThresholdMenu, "cascade_explode_threshold")
2027
- local cascadeThresholdMenu = missionCommands.addSubMenu("Cascade Damage Threshold", explosionCargoMenu)
2028
- addValueAdjustmentCommands(cascadeThresholdMenu, "cascade_damage_threshold")
2029
-
2030
- --Page 4: Cargo and Ordnance Protection
2031
- local explosionCargoMenu = missionCommands.addSubMenu("Cargo and Ordnance", splash_damage_menu)
2032
- missionCommands.addCommand("Toggle Always Cascade Explode", explosionCargoMenu, toggleSplashDamageSetting, "always_cascade_explode")
2033
- missionCommands.addCommand("Toggle Tracking & Cargo Effects", explosionCargoMenu, toggleSplashDamageSetting, "track_pre_explosion")
2034
- local cargoThresholdMenu = missionCommands.addSubMenu("Cargo Damage Threshold", explosionCargoMenu)
2035
- addValueAdjustmentCommands(cargoThresholdMenu, "cargo_damage_threshold")
2036
- missionCommands.addCommand("Toggle Ordnance Protection", explosionCargoMenu, toggleSplashDamageSetting, "ordnance_protection")
2037
- local ordnanceRadiusMenu = missionCommands.addSubMenu("Ordnance Protection Radius", explosionCargoMenu)
2038
- addValueAdjustmentCommands(ordnanceRadiusMenu, "ordnance_protection_radius")
2039
- missionCommands.addCommand("Toggle Snap To Ground If Destroyed By LE", explosionCargoMenu, toggleSplashDamageSetting, "snap_to_ground_if_destroyed_by_large_explosion")
2040
- local ordnanceRadiusMenu = missionCommands.addSubMenu("Ordnance Protection Radius", explosionCargoMenu)
2041
- local cargoThresholdMenu = missionCommands.addSubMenu("Max Snap Height", explosionCargoMenu)
2042
- addValueAdjustmentCommands(cargoThresholdMenu, "max_snapped_height")
2043
- missionCommands.addCommand("Toggle Recent Expl Track Snap", explosionCargoMenu, toggleSplashDamageSetting, "recent_large_explosion_snap")
2044
-
2045
-
2046
- --Page 5: Debris Settings
2047
- local debrisMenu = missionCommands.addSubMenu("Debris Settings", splash_damage_menu)
2048
- missionCommands.addCommand("Toggle Debris Effects", debrisMenu, toggleSplashDamageSetting, "debris_effects")
2049
- local debrisCountMinMenu = missionCommands.addSubMenu("Min Debris Count", debrisMenu)
2050
- addValueAdjustmentCommands(debrisCountMinMenu, "debris_count_min")
2051
- local debrisCountMaxMenu = missionCommands.addSubMenu("Max Debris Count", debrisMenu)
2052
- addValueAdjustmentCommands(debrisCountMaxMenu, "debris_count_max")
2053
- local debrisDistanceMenu = missionCommands.addSubMenu("Max Debris Distance", debrisMenu)
2054
- addValueAdjustmentCommands(debrisDistanceMenu, "debris_max_distance")
2055
- local debrisPowerMenu = missionCommands.addSubMenu("Debris Power", debrisMenu)
2056
- addValueAdjustmentCommands(debrisPowerMenu, "debris_power")
2057
-
2058
- --Page 6: Cluster Settings
2059
- local clusterMenu = missionCommands.addSubMenu("Cluster Settings", splash_damage_menu)
2060
- missionCommands.addCommand("Toggle Cluster Enabled", clusterMenu, toggleSplashDamageSetting, "cluster_enabled")
2061
- local clusterBaseLengthMenu = missionCommands.addSubMenu("Cluster Base Length", clusterMenu)
2062
- addValueAdjustmentCommands(clusterBaseLengthMenu, "cluster_base_length")
2063
- local clusterBaseWidthMenu = missionCommands.addSubMenu("Cluster Base Width", clusterMenu)
2064
- addValueAdjustmentCommands(clusterBaseWidthMenu, "cluster_base_width")
2065
- local clusterMaxLengthMenu = missionCommands.addSubMenu("Cluster Max Length", clusterMenu)
2066
- addValueAdjustmentCommands(clusterMaxLengthMenu, "cluster_max_length")
2067
- local clusterMaxWidthMenu = missionCommands.addSubMenu("Cluster Max Width", clusterMenu)
2068
- addValueAdjustmentCommands(clusterMaxWidthMenu, "cluster_max_width")
2069
- local clusterMinLengthMenu = missionCommands.addSubMenu("Cluster Min Length", clusterMenu)
2070
- addValueAdjustmentCommands(clusterMinLengthMenu, "cluster_min_length")
2071
- local clusterMinWidthMenu = missionCommands.addSubMenu("Cluster Min Width", clusterMenu)
2072
- addValueAdjustmentCommands(clusterMinWidthMenu, "cluster_min_width")
2073
- missionCommands.addCommand("Toggle Bomblet Reduction Modifier", clusterMenu, toggleSplashDamageSetting, "cluster_bomblet_reductionmodifier")
2074
- local clusterBombletDamageMenu = missionCommands.addSubMenu("Bomblet Damage Modifier", clusterMenu)
2075
- addValueAdjustmentCommands(clusterBombletDamageMenu, "cluster_bomblet_damage_modifier")
2076
-
2077
- --Page 7: Giant Explosion Settings
2078
- local giantExplosionMenu = missionCommands.addSubMenu("Giant Explosion Settings", splash_damage_menu)
2079
- missionCommands.addCommand("Toggle Giant Explosion", giantExplosionMenu, toggleSplashDamageSetting, "giant_explosion_enabled")
2080
- missionCommands.addCommand("Toggle Static Target", giantExplosionMenu, toggleSplashDamageSetting, "giant_explosion_target_static")
2081
- for name, target in pairs(giantExplosionTargets) do
2082
- local displayName = name:gsub("GiantExplosionTarget", "GiantExplosionTarget")
2083
- missionCommands.addCommand("Detonate " .. displayName, giantExplosionMenu, function()
2084
- trigger.action.setUserFlag(displayName, 1)
2085
- end)
2086
- end
2087
- missionCommands.addCommand("Detonate All Giant Targets", giantExplosionMenu, function()
2088
- for name, target in pairs(giantExplosionTargets) do
2089
- local flagName = name:gsub("GiantExplosionTarget", "GiantExplosionTarget")
2090
- trigger.action.setUserFlag(flagName, 1)
2091
- end
2092
- end)
2093
- local powerMenu = missionCommands.addSubMenu("Explosion Power", giantExplosionMenu)
2094
- addValueAdjustmentCommands(powerMenu, "giant_explosion_power")
2095
- local scaleMenu = missionCommands.addSubMenu("Size Scale", giantExplosionMenu)
2096
- missionCommands.addCommand("+0.1", scaleMenu, updateSplashDamageSetting, "giant_explosion_scale", 0.1)
2097
- missionCommands.addCommand("+0.5", scaleMenu, updateSplashDamageSetting, "giant_explosion_scale", 0.5)
2098
- missionCommands.addCommand("-0.1", scaleMenu, updateSplashDamageSetting, "giant_explosion_scale", -0.1)
2099
- missionCommands.addCommand("-0.5", scaleMenu, updateSplashDamageSetting, "giant_explosion_scale", -0.5)
2100
- local durationMenu = missionCommands.addSubMenu("Duration", giantExplosionMenu)
2101
- missionCommands.addCommand("+0.25s", durationMenu, updateSplashDamageSetting, "giant_explosion_duration", 0.25)
2102
- missionCommands.addCommand("-0.25s", durationMenu, updateSplashDamageSetting, "giant_explosion_duration", -0.25)
2103
- local countMenu = missionCommands.addSubMenu("Explosion Count", giantExplosionMenu)
2104
- addValueAdjustmentCommands(countMenu, "giant_explosion_count")
2105
-
2106
- end
2107
-
2108
- if (script_enable == 1) then
2109
- gameMsg("SPLASH DAMAGE 3.1 SCRIPT RUNNING")
2110
- env.info("SPLASH DAMAGE 3.1 SCRIPT RUNNING")
2111
-
2112
- timer.scheduleFunction(function()
2113
- protectedCall(track_wpns)
2114
- return timer.getTime() + refreshRate
2115
- end, {}, timer.getTime() + refreshRate)
2116
-
2117
- if splash_damage_options.giant_explosion_enabled then
2118
- giantExplosionTargets = {} -- Ensure it’s fresh
2119
- local targetCount = 0
2120
- for coa = 0, 2 do
2121
- local groups = coalition.getGroups(coa)
2122
- if groups then
2123
- for _, group in pairs(groups) do
2124
- local units = group:getUnits()
2125
- if units then
2126
- for _, unit in pairs(units) do
2127
- local name = unit:getName()
2128
- if name:find("GiantExplosionTarget") then
2129
- local pos = unit:getPosition().p
2130
- giantExplosionTargets[name] = {obj = unit, pos = pos}
2131
- if splash_damage_options.giant_explosion_target_static then
2132
- giantExplosionTargets[name].pos = pos
2133
- end
2134
- debugMsg("Found GiantExplosionTarget: " .. name .. " at X:" .. pos.x .. " Y:" .. pos.y .. " Z:" .. pos.z)
2135
- targetCount = targetCount + 1
2136
- end
2137
- end
2138
- end
2139
- end
2140
- end
2141
- end
2142
- debugMsg("Total GiantExplosionTargets found: " .. targetCount)
2143
- timer.scheduleFunction(checkGiantExplosionFlag, {}, timer.getTime() + splash_damage_options.giant_explosion_poll_rate)
2144
- if not splash_damage_options.giant_explosion_target_static then
2145
- timer.scheduleFunction(updateTargetPosition, {}, timer.getTime() + 1.0)
2146
- end
2147
- end
2148
-
2149
- world.addEventHandler(WpnHandler)
2150
- addSplashDamageMenu()
2151
- end
1
+ --[[
2
+ 04 April 2025 (Stevey666) - 3.1
3
+ - Set default cluster munitions option to false, set this to true in the options if you want it
4
+ - Added missing radio commands for Cascade Scaling
5
+ - Adjust default cascading to 2 (from 1)
6
+ - Adjusted Ural-4320 to be a tanker and ammo carrier for cargo cookoff
7
+ - Prevent weapons not in the list from being tracked
8
+ - Moved some logging behind the debug mode flag
9
+ - Ordnance Protection, added a max height ordnance protection will snap explosion to ground
10
+ - Ordnance Protection, fixed enable/disable option
11
+ - Added Giant Explosion feature
12
+ - Adjusted some hydra70 values on recom. from ETBSmorgan
13
+
14
+
15
+ 09 March 2025 (Stevey666) - 3.0
16
+ - 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
17
+ - 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)
18
+ - Added vehicle scanning around a weapon to allow for..
19
+ - Cook offs - you can set vehicles that will cook off i.e ammo trucks, number of explosions, debris explosions, power adjustable
20
+ - Fuel/Tanker explosion and flames - when a fuel tanker blows it will through up a big flame - adjustable in the scripts
21
+ - Added section for vehicles for the above
22
+ - Added radio commands for everything
23
+ - Added in cluster munitions changes (note: barely tested, its not particularly accurate or that useful at this point so leaving disabled)
24
+ - Potential bug - testing, stacking too many units together may cause a MIST error if you're using mist
25
+
26
+ - Setting this as 3.0 as I'd like to be responsive to requests, updates etc - creating a new fork to track this
27
+
28
+
29
+ 10 Feb 2025 (Stevey666) - 2.0.7
30
+ - Fixed AGM 154/Adjusted weapons
31
+ - Added overall damage scaling
32
+ - Added modifier for shaped charges (i.e. Mavericks), adjusted weapon list accordingly
33
+ - Adjusted blast radius and damage calculations, created option for dynamic blast radius
34
+ - Adjusted cascading explosions, added additional "cascade_scaling" modifier and cascade explode threshold modifier. Units wont explode on initial impact unless health drops under threshold
35
+ - Added always_cascade_explode option so you can set it to the old ways of making everything in the blast wave go kaboom
36
+ - Added in game radio commands to change the new options ingame without having to reload everything in mission editor to test it out
37
+
38
+ 12 November 2024 (by JGi | Quéton 1-1)
39
+ - Tweak down radius 100>90 (Thanks Arhibeau)
40
+ - Tweak down some values
41
+
42
+ 20 January 2024 (by JGi | Quéton 1-1)
43
+ - Added missing weapons to explTable
44
+ - Sort weapons in explTable by type
45
+ - Added aircraft type in log when missing
46
+
47
+ 03 May 2023 (KERV)
48
+ Correction AGM 154 (https://forum.dcs.world/topic/289290-splash-damage-20-script-make-explosions-better/page/5/#comment-5207760)
49
+
50
+ 06 March 2023 (Kerv)
51
+ - Add some data for new ammunition
52
+
53
+ 16 April 2022
54
+ spencershepard (GRIMM):
55
+ - Added new/missing weapons to explTable
56
+ - Added new option rocket_multiplier
57
+
58
+ 31 December 2021
59
+ spencershepard (GRIMM):
60
+ - Added many new weapons
61
+ - Added filter for weapons.shells events
62
+ - Fixed mission weapon message option
63
+ - Changed default for damage_model option
64
+
65
+ 21 December 2021
66
+ spencershepard (GRIMM):
67
+ SPLASH DAMAGE 2.0:
68
+ - Added blast wave effect to add timed and scaled secondary explosions on top of game objects
69
+ - Object geometry within blast wave changes damage intensity
70
+ - Damage boost for structures since they are hard to kill, even if very close to large explosions
71
+ - Increased some rocket values in explTable
72
+ - Missing weapons from explTable will display message to user and log to DCS.log so that we can add what's missing
73
+ - Damage model for ground units that will disable their weapons and ability to move with partial damage before they are killed
74
+ - Added options table to allow easy adjustments before release
75
+ - General refactoring and restructure
76
+
77
+ 28 October 2020
78
+ FrozenDroid:
79
+ - Uncommented error logging, actually made it an error log which shows a message box on error.
80
+ - Fixed the too restrictive weapon filter (took out the HE warhead requirement)
81
+
82
+ 2 October 2020
83
+ FrozenDroid:
84
+ - Added error handling to all event handler and scheduled functions. Lua script errors can no longer bring the server down.
85
+ - 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...?)
86
+ --]]
87
+
88
+ ----[[ ##### SCRIPT CONFIGURATION ##### ]]----
89
+ splash_damage_options = {
90
+ --debug options
91
+ ["game_messages"] = false, --enable some messages on screen
92
+ ["debug"] = false, --enable debugging messages
93
+ ["weapon_missing_message"] = false, --false disables messages alerting you to weapons missing from the explTable
94
+ ["track_pre_explosion_debug"] = false, --Toggle to enable/disable pre-explosion tracking debugging
95
+
96
+ ["enable_radio_menu"] = true, --enables the in-game radio menu for modifying settings
97
+
98
+ ["static_damage_boost"] = 2000, --apply extra damage to Unit.Category.STRUCTUREs with wave explosions
99
+ ["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
100
+ ["larger_explosions"] = true, --secondary explosions on top of weapon impact points, dictated by the values in the explTable
101
+ ["damage_model"] = true, --allow blast wave to affect ground unit movement and weapons
102
+ ["blast_search_radius"] = 90, --this is the max size of any blast wave radius, since we will only find objects within this zone
103
+ ["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.
104
+ ["blast_stun"] = false, --not implemented
105
+ ["unit_disabled_health"] = 30, --if health is below this value after our explosions, disable its movement
106
+ ["unit_cant_fire_health"] = 40, --if health is below this value after our explosions, set ROE to HOLD to simulate damage weapon systems
107
+ ["infantry_cant_fire_health"] = 60, --if health is below this value after our explosions, set ROE to HOLD to simulate severe injury
108
+
109
+ ["rocket_multiplier"] = 1.3, --multiplied by the explTable value for rockets
110
+ ["overall_scaling"] = 1, --overall scaling for explosive power
111
+
112
+ ["apply_shaped_charge_effects"] = true, --apply reduction in blastwave etc for shaped charge munitions
113
+ ["shaped_charge_multiplier"] = 0.2, --multiplier that reduces blast radius and explosion power for shaped charge munitions.
114
+
115
+ ["use_dynamic_blast_radius"] = true, --if true, blast radius is calculated from explosion power; if false, blast_search_radius (90) is used
116
+ ["dynamic_blast_radius_modifier"] = 2, --multiplier for the blast radius
117
+
118
+ ["cascade_scaling"] = 2, --multiplier for secondary (cascade) blast damage, 1 damage fades out too soon, 2 or 3 damage seems a good balance
119
+ ["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
120
+ ["always_cascade_explode"] = false, --switch if you want everything to explode like with the original script
121
+
122
+
123
+ --track_pre_explosion/enable_cargo_effects should both be the same value
124
+ ["track_pre_explosion"] = true, --Toggle to enable/disable pre-explosion tracking
125
+ ["enable_cargo_effects"] = true, --Toggle for enabling/disabling cargo explosions and cook-offs
126
+ ["cargo_damage_threshold"] = 60, --Health % below which cargo explodes (0 = destroyed only)
127
+ ["debris_effects"] = true, --Enable debris from cargo cook-offs
128
+ ["debris_power"] = 1, --Power of each debris explosion
129
+ ["debris_count_min"] = 6, --Minimum debris pieces per cook-off
130
+ ["debris_count_max"] = 12, --Maximum debris pieces per cook-off
131
+ ["debris_max_distance"] = 10, --Max distance debris can travel (meters), the min distance from the vehicle will be 10% of this
132
+
133
+ ["ordnance_protection"] = true, --Toggle ordinance protection features
134
+ ["ordnance_protection_radius"] = 10, --Distance in meters to protect nearby bombs
135
+ ["detect_ordnance_destruction"] = true, --Toggle detection of ordnance destroyed by large explosions
136
+ ["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
137
+ ["max_snapped_height"] = 80, --max height it will snap to ground from
138
+ ["recent_large_explosion_snap"] = true, --enable looking for a recent large_explosion generated by the script
139
+ ["recent_large_explosion_range"] = 100, --range its looking for in meters for a recent large_explosion generated by the script
140
+ ["recent_large_explosion_time"] = 4, --in seconds how long ago there was a recent large_explosion generated by the script
141
+
142
+ --Cluster bomb settings
143
+ ["cluster_enabled"] = false,
144
+ ["cluster_base_length"] = 150, --Base forward spread (meters)
145
+ ["cluster_base_width"] = 200, --Base lateral spread (meters)
146
+ ["cluster_max_length"] = 300, --Max forward spread (meters)
147
+ ["cluster_max_width"] = 400, --Max lateral spread (meters)
148
+ ["cluster_min_length"] = 100, --Min forward spread
149
+ ["cluster_min_width"] = 150, --Min lateral spread
150
+ ["cluster_bomblet_reductionmodifier"] = true, --Use equation to reduce number of bomblets (to make it look better)
151
+ ["cluster_bomblet_damage_modifier"] = 1, --Adjustable global modifier for bomblet explosive power
152
+
153
+ --Giant Explosion Options - Remember, any target you want to blow up needs to be named "GiantExplosionTarget(X)" (X) being any value/name etc
154
+ ["giant_explosion_enabled"] = true, --Toggle to enable/disable Giant Explosion
155
+ ["giant_explosion_power"] = 6000, --Power in kg of TNT (default 8 tons)
156
+ ["giant_explosion_scale"] = 1, --Size scale factor (default 1)
157
+ ["giant_explosion_duration"] = 3.0, --Total duration in seconds (default 3s)
158
+ ["giant_explosion_count"] = 250, --Number of explosions (default 300)
159
+ ["giant_explosion_target_static"] = true, --Toggle to true for static targets (store position once), false for dynamic (update every second)
160
+ ["giant_explosion_poll_rate"] = 1, --Polling rate in seconds for flag checks (default 1s)
161
+ }
162
+
163
+ local script_enable = 1
164
+ refreshRate = 0.1
165
+ ----[[ ##### End of SCRIPT CONFIGURATION ##### ]]----
166
+
167
+ --Helper function: Trim whitespace.
168
+ local function trim(s)
169
+ return s:match("^%s*(.-)%s*$")
170
+ end
171
+
172
+ cargoUnits = {
173
+
174
+ --[[
175
+ flamesize:
176
+
177
+ 1 = small smoke and fire
178
+ 2 = medium smoke and fire
179
+ 3 = large smoke and fire
180
+ 4 = huge smoke and fire
181
+ 5 = small smoke
182
+ 6 = medium smoke
183
+ 7 = large smoke
184
+ 8 = huge smoke
185
+ ]]--
186
+
187
+ --1) M92 R11 Volvo driveable (Fuel Truck Tanker)
188
+ ["r11_volvo_drivable"] = {
189
+ cargoExplosion = true,
190
+ cargoExplosionMult = 2.0,
191
+ cargoExplosionPower = 200,
192
+ cargoCookOff = false,
193
+ cookOffCount = 0,
194
+ cookOffPower = 0,
195
+ cookOffDuration = 0,
196
+ cookOffRandomTiming = false,
197
+ cookOffPowerRandom = 50,
198
+ isTanker = true,
199
+ flameSize = 3,
200
+ flameDuration = 5,
201
+ },
202
+
203
+ --2) Refueler ATMZ-5
204
+ ["ATMZ-5"] = {
205
+ cargoExplosion = true,
206
+ cargoExplosionMult = 2.0,
207
+ cargoExplosionPower = 200,
208
+ cargoCookOff = false,
209
+ cookOffCount = 0,
210
+ cookOffPower = 0,
211
+ cookOffDuration = 0,
212
+ cookOffRandomTiming = false,
213
+ cookOffPowerRandom = 50,
214
+ isTanker = true,
215
+ flameSize = 3,
216
+ flameDuration = 5,
217
+ },
218
+
219
+ --3) Refueler ATZ-10
220
+ ["ATZ-10"] = {
221
+ cargoExplosion = true,
222
+ cargoExplosionMult = 2,
223
+ cargoExplosionPower = 200,
224
+ cargoCookOff = false,
225
+ cookOffCount = 0,
226
+ cookOffPower = 0,
227
+ cookOffDuration = 0,
228
+ cookOffRandomTiming = false,
229
+ cookOffPowerRandom = 50,
230
+ isTanker = true,
231
+ flameSize = 3,
232
+ flameDuration = 5,
233
+ },
234
+
235
+ --4) Refueler ATZ-5
236
+ ["ATZ-5"] = {
237
+ cargoExplosion = true,
238
+ cargoExplosionMult = 1.8,
239
+ cargoExplosionPower = 200,
240
+ cargoCookOff = false,
241
+ cookOffCount = 0,
242
+ cookOffPower = 0,
243
+ cookOffDuration = 0,
244
+ cookOffRandomTiming = false,
245
+ cookOffPowerRandom = 50,
246
+ isTanker = true,
247
+ flameSize = 3,
248
+ flameDuration = 5,
249
+ },
250
+
251
+ --5) Refueler M978 HEMTT (Fuel truck tanker)
252
+ ["M978 HEMTT Tanker"] = {
253
+ cargoExplosion = true,
254
+ cargoExplosionMult = 2.0,
255
+ cargoExplosionPower = 200,
256
+ cargoCookOff = false,
257
+ cookOffCount = 0,
258
+ cookOffPower = 0,
259
+ cookOffDuration = 0,
260
+ cookOffRandomTiming = false,
261
+ cookOffPowerRandom = 50,
262
+ isTanker = true,
263
+ flameSize = 3,
264
+ flameDuration = 5,
265
+ },
266
+
267
+ --##### AMMO CARRIERS #####
268
+ ["GAZ-66"] = {
269
+ cargoExplosion = true,
270
+ cargoExplosionMult = 1,
271
+ cargoExplosionPower = 200,
272
+ cargoCookOff = true,
273
+ cookOffCount = 4,
274
+ cookOffPower = 1,
275
+ cookOffDuration = 20,
276
+ cookOffRandomTiming = true,
277
+ cookOffPowerRandom = 50,
278
+ isTanker = false,
279
+ flameSize = 1,
280
+ flameDuration = 30,
281
+ },
282
+ --#Technically this is both ammo and fuel looking at the model
283
+ ["Ural-4320"] = {
284
+ cargoExplosion = true,
285
+ cargoExplosionMult = 1,
286
+ cargoExplosionPower = 200,
287
+ cargoCookOff = true,
288
+ cookOffCount = 4,
289
+ cookOffPower = 1,
290
+ cookOffDuration = 20,
291
+ cookOffRandomTiming = true,
292
+ cookOffPowerRandom = 50,
293
+ isTanker = true,
294
+ flameSize = 1,
295
+ flameDuration = 30,
296
+ },
297
+
298
+ ["ZIL-135"] = {
299
+ cargoExplosion = true,
300
+ cargoExplosionMult = 1,
301
+ cargoExplosionPower = 200,
302
+ cargoCookOff = true,
303
+ cookOffCount = 5,
304
+ cookOffPower = 1,
305
+ cookOffDuration = 20,
306
+ cookOffRandomTiming = true,
307
+ cookOffPowerRandom = 50,
308
+ isTanker = false,
309
+ flameSize = 1,
310
+ flameDuration = 30,
311
+ },
312
+ }
313
+
314
+ --Weapon Explosive Table
315
+ explTable = {
316
+ --*** WWII BOMBS ***
317
+ ["British_GP_250LB_Bomb_Mk1"] = { explosive = 100, shaped_charge = false },
318
+ ["British_GP_250LB_Bomb_Mk4"] = { explosive = 100, shaped_charge = false },
319
+ ["British_GP_250LB_Bomb_Mk5"] = { explosive = 100, shaped_charge = false },
320
+ ["British_GP_500LB_Bomb_Mk1"] = { explosive = 213, shaped_charge = false },
321
+ ["British_GP_500LB_Bomb_Mk4"] = { explosive = 213, shaped_charge = false },
322
+ ["British_GP_500LB_Bomb_Mk4_Short"] = { explosive = 213, shaped_charge = false },
323
+ ["British_GP_500LB_Bomb_Mk5"] = { explosive = 213, shaped_charge = false },
324
+ ["British_MC_250LB_Bomb_Mk1"] = { explosive = 100, shaped_charge = false },
325
+ ["British_MC_250LB_Bomb_Mk2"] = { explosive = 100, shaped_charge = false },
326
+ ["British_MC_500LB_Bomb_Mk1_Short"] = { explosive = 213, shaped_charge = false },
327
+ ["British_MC_500LB_Bomb_Mk2"] = { explosive = 213, shaped_charge = false },
328
+ ["British_SAP_250LB_Bomb_Mk5"] = { explosive = 100, shaped_charge = false },
329
+ ["British_SAP_500LB_Bomb_Mk5"] = { explosive = 213, shaped_charge = false },
330
+ ["British_AP_25LBNo1_3INCHNo1"] = { explosive = 4, shaped_charge = false },
331
+ ["British_HE_60LBSAPNo2_3INCHNo1"] = { explosive = 4, shaped_charge = false },
332
+ ["British_HE_60LBFNo1_3INCHNo1"] = { explosive = 4, shaped_charge = false },
333
+
334
+ ["SC_50"] = { explosive = 20, shaped_charge = false },
335
+ ["ER_4_SC50"] = { explosive = 20, shaped_charge = false },
336
+ ["SC_250_T1_L2"] = { explosive = 100, shaped_charge = false },
337
+ ["SC_501_SC250"] = { explosive = 100, shaped_charge = false },
338
+ ["Schloss500XIIC1_SC_250_T3_J"] = { explosive = 100, shaped_charge = false },
339
+ ["SC_501_SC500"] = { explosive = 213, shaped_charge = false },
340
+ ["SC_500_L2"] = { explosive = 213, shaped_charge = false },
341
+ ["SD_250_Stg"] = { explosive = 100, shaped_charge = false },
342
+ ["SD_500_A"] = { explosive = 213, shaped_charge = false },
343
+
344
+ --*** WWII CBU ***
345
+ ["AB_250_2_SD_2"] = { explosive = 100, shaped_charge = false },
346
+ ["AB_250_2_SD_10A"] = { explosive = 100, shaped_charge = false },
347
+ ["AB_500_1_SD_10A"] = { explosive = 213, shaped_charge = false },
348
+
349
+ --*** WWII ROCKETS ***
350
+ ["3xM8_ROCKETS_IN_TUBES"] = { explosive = 4, shaped_charge = false },
351
+ ["WGr21"] = { explosive = 4, shaped_charge = false },
352
+
353
+ --*** UNGUIDED BOMBS (UGB) ***
354
+ ["M_117"] = { explosive = 201, shaped_charge = false },
355
+ ["AN_M30A1"] = { explosive = 45, shaped_charge = false },
356
+ ["AN_M57"] = { explosive = 100, shaped_charge = false },
357
+ ["AN_M64"] = { explosive = 121, shaped_charge = false },
358
+ ["AN_M65"] = { explosive = 400, shaped_charge = false },
359
+ ["AN_M66"] = { explosive = 800, shaped_charge = false },
360
+ ["AN-M66A2"] = { explosive = 536, shaped_charge = false },
361
+ ["AN-M81"] = { explosive = 100, shaped_charge = false },
362
+ ["AN-M88"] = { explosive = 100, shaped_charge = false },
363
+
364
+ ["Mk_81"] = { explosive = 60, shaped_charge = false },
365
+ ["MK-81SE"] = { explosive = 60, shaped_charge = false },
366
+ ["Mk_82"] = { explosive = 100, shaped_charge = false },
367
+ ["MK_82AIR"] = { explosive = 100, shaped_charge = false },
368
+ ["MK_82SNAKEYE"] = { explosive = 100, shaped_charge = false },
369
+ ["Mk_83"] = { explosive = 274, shaped_charge = false },
370
+ ["Mk_84"] = { explosive = 582, shaped_charge = false },
371
+
372
+ ["HEBOMB"] = { explosive = 40, shaped_charge = false },
373
+ ["HEBOMBD"] = { explosive = 40, shaped_charge = false },
374
+
375
+ ["SAMP125LD"] = { explosive = 60, shaped_charge = false },
376
+ ["SAMP250LD"] = { explosive = 118, shaped_charge = false },
377
+ ["SAMP250HD"] = { explosive = 118, shaped_charge = false },
378
+ ["SAMP400LD"] = { explosive = 274, shaped_charge = false },
379
+ ["SAMP400HD"] = { explosive = 274, shaped_charge = false },
380
+
381
+ ["BR_250"] = { explosive = 100, shaped_charge = false },
382
+ ["BR_500"] = { explosive = 100, shaped_charge = false },
383
+
384
+ ["FAB_100"] = { explosive = 45, shaped_charge = false },
385
+ ["FAB_250"] = { explosive = 118, shaped_charge = false },
386
+ ["FAB_250M54TU"] = { explosive = 118, shaped_charge = false },
387
+ ["FAB-250-M62"] = { explosive = 118, shaped_charge = false },
388
+ ["FAB_500"] = { explosive = 213, shaped_charge = false },
389
+ ["FAB_1500"] = { explosive = 675, shaped_charge = false },
390
+
391
+ --*** UNGUIDED BOMBS WITH PENETRATOR / ANTI-RUNWAY ***
392
+ ["Durandal"] = { explosive = 64, shaped_charge = false },
393
+ ["BLU107B_DURANDAL"] = { explosive = 64, shaped_charge = false },
394
+ ["BAP_100"] = { explosive = 32, shaped_charge = false },
395
+ ["BAP-100"] = { explosive = 32, shaped_charge = false },
396
+ ["BAT-120"] = { explosive = 32, shaped_charge = false },
397
+ ["TYPE-200A"] = { explosive = 107, shaped_charge = false },
398
+ ["BetAB_500"] = { explosive = 98, shaped_charge = false },
399
+ ["BetAB_500ShP"] = { explosive = 107, shaped_charge = false },
400
+
401
+ --*** GUIDED BOMBS (GBU) ***
402
+ ["GBU_10"] = { explosive = 582, shaped_charge = false },
403
+ ["GBU_12"] = { explosive = 100, shaped_charge = false },
404
+ ["GBU_16"] = { explosive = 274, shaped_charge = false },
405
+ ["GBU_24"] = { explosive = 582, shaped_charge = false },
406
+ ["KAB_1500Kr"] = { explosive = 675, shaped_charge = false },
407
+ ["KAB_500Kr"] = { explosive = 213, shaped_charge = false },
408
+ ["KAB_500"] = { explosive = 213, shaped_charge = false },
409
+
410
+ --*** CLUSTER BOMBS (CBU) ***
411
+ --I don't have most of these so can't test them with debug on
412
+ ["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
413
+ ["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
414
+ ["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
415
+ ["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
416
+ ["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
417
+ ["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
418
+ ["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
419
+ ["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
420
+ ["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
421
+ ["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)
422
+ ["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
423
+ ["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
424
+ ["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
425
+ ["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
426
+ ["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
427
+ ["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
428
+ ["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
429
+ ["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
430
+ ["RBK_500_255_PTO_1M"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 126, submunition_explosive = 0.5, submunition_name = "PTO_1M" },
431
+ ["RBK_500_255_ShO"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 565, submunition_explosive = 0.1, submunition_name = "ShO" },
432
+ --*** INS/GPS BOMBS (JDAM) ***
433
+ ["GBU_31"] = { explosive = 582, shaped_charge = false },
434
+ ["GBU_31_V_3B"] = { explosive = 582, shaped_charge = false },
435
+ ["GBU_31_V_2B"] = { explosive = 582, shaped_charge = false },
436
+ ["GBU_31_V_4B"] = { explosive = 582, shaped_charge = false },
437
+ ["GBU_32_V_2B"] = { explosive = 202, shaped_charge = false },
438
+ ["GBU_38"] = { explosive = 100, shaped_charge = false },
439
+ ["GBU_54_V_1B"] = { explosive = 100, shaped_charge = false },
440
+
441
+ --*** GLIDE BOMBS (JSOW) ***
442
+ ["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
443
+ ["AGM_154C"] = { explosive = 305, shaped_charge = false },
444
+ ["AGM_154"] = { explosive = 305, shaped_charge = false },
445
+ ["BK90_MJ1"] = { explosive = 0, shaped_charge = false },
446
+ ["BK90_MJ1_MJ2"] = { explosive = 0, shaped_charge = false },
447
+ ["BK90_MJ2"] = { explosive = 0, shaped_charge = false },
448
+
449
+ ["LS-6-100"] = { explosive = 45, shaped_charge = false },
450
+ ["LS-6-250"] = { explosive = 100, shaped_charge = false },
451
+ ["LS-6-500"] = { explosive = 274, shaped_charge = false },
452
+ ["GB-6"] = { explosive = 0, shaped_charge = false },
453
+ ["GB-6-HE"] = { explosive = 0, shaped_charge = false },
454
+ ["GB-6-SFW"] = { explosive = 0, shaped_charge = false },
455
+
456
+ --*** AIR GROUND MISSILE (AGM) ***
457
+ ["AGM_62"] = { explosive = 400, shaped_charge = false },
458
+ ["AGM_65D"] = { explosive = 38, shaped_charge = true },
459
+ ["AGM_65E"] = { explosive = 80, shaped_charge = true },
460
+ ["AGM_65F"] = { explosive = 80, shaped_charge = true },
461
+ ["AGM_65G"] = { explosive = 80, shaped_charge = true },
462
+ ["AGM_65H"] = { explosive = 38, shaped_charge = true },
463
+ ["AGM_65K"] = { explosive = 80, shaped_charge = true },
464
+ ["AGM_65L"] = { explosive = 80, shaped_charge = true },
465
+ ["AGM_123"] = { explosive = 274, shaped_charge = false },
466
+ ["AGM_130"] = { explosive = 582, shaped_charge = false },
467
+ ["AGM_119"] = { explosive = 176, shaped_charge = false },
468
+ ["AGM_114"] = { explosive = 10, shaped_charge = true },
469
+ ["AGM_114K"] = { explosive = 10, shaped_charge = true },
470
+
471
+ ["Rb 05A"] = { explosive = 217, shaped_charge = false },
472
+ ["RB75"] = { explosive = 38, shaped_charge = false },
473
+ ["RB75A"] = { explosive = 38, shaped_charge = false },
474
+ ["RB75B"] = { explosive = 38, shaped_charge = false },
475
+ ["RB75T"] = { explosive = 80, shaped_charge = false },
476
+ ["HOT3_MBDA"] = { explosive = 15, shaped_charge = false },
477
+ ["C-701T"] = { explosive = 38, shaped_charge = false },
478
+ ["C-701IR"] = { explosive = 38, shaped_charge = false },
479
+
480
+ ["Vikhr_M"] = { explosive = 11, shaped_charge = false },
481
+ ["Vikhr_9M127_1"] = { explosive = 11, shaped_charge = false },
482
+ ["AT_6"] = { explosive = 11, shaped_charge = false },
483
+ ["Ataka_9M120"] = { explosive = 11, shaped_charge = false },
484
+ ["Ataka_9M120F"] = { explosive = 11, shaped_charge = false },
485
+ ["P_9M117"] = { explosive = 0, shaped_charge = false },
486
+
487
+ ["KH-66_Grom"] = { explosive = 108, shaped_charge = false },
488
+ ["X_23"] = { explosive = 111, shaped_charge = false },
489
+ ["X_23L"] = { explosive = 111, shaped_charge = false },
490
+ ["X_28"] = { explosive = 160, shaped_charge = false },
491
+ ["X_25ML"] = { explosive = 89, shaped_charge = false },
492
+ ["X_25MR"] = { explosive = 140, shaped_charge = false },
493
+ ["X_29L"] = { explosive = 320, shaped_charge = false },
494
+ ["X_29T"] = { explosive = 320, shaped_charge = false },
495
+ ["X_29TE"] = { explosive = 320, shaped_charge = false },
496
+
497
+ --*** ANTI-RADAR MISSILE (ARM) ***
498
+ ["AGM_88C"] = { explosive = 89, shaped_charge = false },
499
+ ["AGM_88"] = { explosive = 89, shaped_charge = false },
500
+ ["AGM_122"] = { explosive = 15, shaped_charge = false },
501
+ ["LD-10"] = { explosive = 89, shaped_charge = false },
502
+ ["AGM_45A"] = { explosive = 38, shaped_charge = false },
503
+ ["X_58"] = { explosive = 140, shaped_charge = false },
504
+ ["X_25MP"] = { explosive = 89, shaped_charge = false },
505
+
506
+ --*** ANTI-SHIP MISSILE (ASh) ***
507
+ ["AGM_84D"] = { explosive = 488, shaped_charge = false },
508
+ ["Rb 15F"] = { explosive = 500, shaped_charge = false },
509
+ ["C-802AK"] = { explosive = 500, shaped_charge = false },
510
+
511
+ --*** CRUISE MISSILE ***
512
+ ["CM-802AKG"] = { explosive = 488, shaped_charge = false },
513
+ ["AGM_84E"] = { explosive = 488, shaped_charge = false },
514
+ ["AGM_84H"] = { explosive = 488, shaped_charge = false },
515
+ ["X_59M"] = { explosive = 488, shaped_charge = false },
516
+
517
+ --*** ROCKETS ***
518
+ ["HYDRA_70M15"] = { explosive = 5, shaped_charge = false },
519
+ ["HYDRA_70_MK1"] = { explosive = 5, shaped_charge = false },
520
+ ["HYDRA_70_MK5"] = { explosive = 8, shaped_charge = false },
521
+ ["HYDRA_70_M151"] = { explosive = 5, shaped_charge = false },
522
+ ["HYDRA_70_M151_M433"] = { explosive = 5, shaped_charge = false },
523
+ ["HYDRA_70_M229"] = { explosive = 10, shaped_charge = false },
524
+ ["FFAR Mk1 HE"] = { explosive = 5, shaped_charge = false },
525
+ ["FFAR Mk5 HEAT"] = { explosive = 8, shaped_charge = false },
526
+ ["HVAR"] = { explosive = 5, shaped_charge = false },
527
+ ["Zuni_127"] = { explosive = 8, shaped_charge = false },
528
+ ["ARAKM70BHE"] = { explosive = 5, shaped_charge = false },
529
+ ["ARAKM70BAP"] = { explosive = 8, shaped_charge = false },
530
+ ["SNEB_TYPE251_F1B"] = { explosive = 4, shaped_charge = false },
531
+ ["SNEB_TYPE252_F1B"] = { explosive = 4, shaped_charge = false },
532
+ ["SNEB_TYPE253_F1B"] = { explosive = 5, shaped_charge = false },
533
+ ["SNEB_TYPE256_F1B"] = { explosive = 6, shaped_charge = false },
534
+ ["SNEB_TYPE257_F1B"] = { explosive = 8, shaped_charge = false },
535
+ ["SNEB_TYPE251_F4B"] = { explosive = 4, shaped_charge = false },
536
+ ["SNEB_TYPE252_F4B"] = { explosive = 4, shaped_charge = false },
537
+ ["SNEB_TYPE253_F4B"] = { explosive = 5, shaped_charge = false },
538
+ ["SNEB_TYPE256_F4B"] = { explosive = 6, shaped_charge = false },
539
+ ["SNEB_TYPE257_F4B"] = { explosive = 8, shaped_charge = false },
540
+ ["SNEB_TYPE251_H1"] = { explosive = 4, shaped_charge = false },
541
+ ["SNEB_TYPE252_H1"] = { explosive = 4, shaped_charge = false },
542
+ ["SNEB_TYPE253_H1"] = { explosive = 5, shaped_charge = false },
543
+ ["SNEB_TYPE256_H1"] = { explosive = 6, shaped_charge = false },
544
+ ["SNEB_TYPE257_H1"] = { explosive = 8, shaped_charge = false },
545
+ ["MATRA_F4_SNEBT251"] = { explosive = 8, shaped_charge = false },
546
+ ["MATRA_F4_SNEBT253"] = { explosive = 8, shaped_charge = false },
547
+ ["MATRA_F4_SNEBT256"] = { explosive = 8, shaped_charge = false },
548
+ ["MATRA_F1_SNEBT253"] = { explosive = 8, shaped_charge = false },
549
+ ["MATRA_F1_SNEBT256"] = { explosive = 8, shaped_charge = false },
550
+ ["TELSON8_SNEBT251"] = { explosive = 4, shaped_charge = false },
551
+ ["TELSON8_SNEBT253"] = { explosive = 8, shaped_charge = false },
552
+ ["TELSON8_SNEBT256"] = { explosive = 4, shaped_charge = false },
553
+ ["TELSON8_SNEBT257"] = { explosive = 6, shaped_charge = false },
554
+ ["ARF8M3API"] = { explosive = 8, shaped_charge = false },
555
+ ["UG_90MM"] = { explosive = 8, shaped_charge = false },
556
+ ["S-24A"] = { explosive = 24, shaped_charge = false },
557
+ ["S-25OF"] = { explosive = 194, shaped_charge = false },
558
+ ["S-25OFM"] = { explosive = 150, shaped_charge = false },
559
+ ["S-25O"] = { explosive = 150, shaped_charge = false },
560
+ ["S-25-O"] = { explosive = 150, shaped_charge = false },
561
+ ["S_25L"] = { explosive = 190, shaped_charge = false },
562
+ ["S-5M"] = { explosive = 1, shaped_charge = false },
563
+ ["C_5"] = { explosive = 8, shaped_charge = false },
564
+ ["C5"] = { explosive = 5, shaped_charge = false },
565
+ ["C_8"] = { explosive = 4, shaped_charge = false },
566
+ ["C_8OFP2"] = { explosive = 3, shaped_charge = false },
567
+ ["C_13"] = { explosive = 21, shaped_charge = false },
568
+ ["C_24"] = { explosive = 123, shaped_charge = false },
569
+ ["C_25"] = { explosive = 151, shaped_charge = false },
570
+
571
+ --*** LASER ROCKETS ***
572
+ ["AGR_20"] = { explosive = 8, shaped_charge = false },
573
+ ["AGR_20A"] = { explosive = 8, shaped_charge = false },
574
+ ["AGR_20_M282"] = { explosive = 8, shaped_charge = false },
575
+ ["Hydra_70_M282_MPP"] = { explosive = 5, shaped_charge = true },
576
+ ["BRM-1_90MM"] = { explosive = 8, shaped_charge = false },
577
+ }
578
+
579
+
580
+
581
+
582
+
583
+
584
+ local effectSmokeId = 1
585
+
586
+ ----[[ ##### HELPER/UTILITY FUNCTIONS ##### ]]----
587
+
588
+ local function tableHasKey(table, key)
589
+ return table[key] ~= nil
590
+ end
591
+
592
+ local function debugMsg(str)
593
+ if splash_damage_options.debug == true then
594
+ debugCounter = (debugCounter or 0) + 1
595
+ local uniqueStr = str .. " [" .. timer.getTime() .. " - " .. debugCounter .. "]"
596
+ trigger.action.outText(uniqueStr, 5)
597
+ env.info("DEBUG: " .. uniqueStr)
598
+ end
599
+ end
600
+
601
+ local function gameMsg(str)
602
+ if splash_damage_options.game_messages == true then
603
+ trigger.action.outText(str, 5)
604
+ end
605
+ end
606
+
607
+ local function getDistance(point1, point2)
608
+ local x1 = point1.x
609
+ local y1 = point1.y
610
+ local z1 = point1.z
611
+ local x2 = point2.x
612
+ local y2 = point2.y
613
+ local z2 = point2.z
614
+ local dX = math.abs(x1 - x2)
615
+ local dZ = math.abs(z1 - z2)
616
+ local distance = math.sqrt(dX * dX + dZ * dZ)
617
+ return distance
618
+ end
619
+
620
+ local function getDistance3D(point1, point2)
621
+ local x1 = point1.x
622
+ local y1 = point1.y
623
+ local z1 = point1.z
624
+ local x2 = point2.x
625
+ local y2 = point2.y
626
+ local z2 = point2.z
627
+ local dX = math.abs(x1 - x2)
628
+ local dY = math.abs(y1 - y2)
629
+ local dZ = math.abs(z1 - z2)
630
+ local distance = math.sqrt(dX * dX + dZ * dZ + dY * dY)
631
+ return distance
632
+ end
633
+
634
+ local function vec3Mag(speedVec)
635
+ return math.sqrt(speedVec.x^2 + speedVec.y^2 + speedVec.z^2)
636
+ end
637
+
638
+ local function lookahead(speedVec)
639
+ local speed = vec3Mag(speedVec)
640
+ local dist = speed * refreshRate * 1.5
641
+ return dist
642
+ end
643
+
644
+ --Cluster-specific helper functions from Rockeye script
645
+ local function normalizeVector(vec)
646
+ local mag = math.sqrt(vec.x^2 + vec.z^2)
647
+ if mag > 0 then
648
+ return { x = vec.x / mag, z = vec.z / mag }
649
+ else
650
+ return { x = 1, z = 0 }
651
+ end
652
+ end
653
+ local function calculate_drop_angle(velocity)
654
+ local horizontal_speed = math.sqrt((velocity.x or 0)^2 + (velocity.z or 0)^2)
655
+ local vertical_speed = math.abs(velocity.y or 0)
656
+ if horizontal_speed == 0 then return 90 end
657
+ local angle_rad = math.atan(vertical_speed / horizontal_speed)
658
+ return math.deg(angle_rad)
659
+ end
660
+ local function calculate_dispersion(velocity, burst_altitude)
661
+ local velocity_magnitude = math.sqrt((velocity.x or 0)^2 + (velocity.z or 0)^2)
662
+ local drop_angle = calculate_drop_angle(velocity)
663
+ local length = splash_damage_options.cluster_base_length * (1 + velocity_magnitude / 200)
664
+ local width = splash_damage_options.cluster_base_width * (1 + burst_altitude / 6000)
665
+ local length_jitter = length * (0.85 + math.random() * 0.3)
666
+ local width_jitter = width * (0.85 + math.random() * 0.3)
667
+ return math.max(splash_damage_options.cluster_min_length, math.min(splash_damage_options.cluster_max_length, length_jitter)),
668
+ math.max(splash_damage_options.cluster_min_width, math.min(splash_damage_options.cluster_max_width, width_jitter))
669
+ end
670
+
671
+
672
+
673
+
674
+
675
+
676
+
677
+
678
+
679
+ ----[[ ##### End of HELPER/UTILITY FUNCTIONS ##### ]]----
680
+ giantExplosionTargets = {}
681
+ cargoEffectsQueue = {}
682
+ WpnHandler = {}
683
+ tracked_target_position = nil --Store the last known position of TargetUnit for giant explosion
684
+ tracked_weapons = {}
685
+ local processedUnitsGlobal = {}
686
+
687
+ function scanGiantExplosionTargets()
688
+ giantExplosionTargets = {}
689
+ local function findTargets(obj)
690
+ if obj:isExist() then
691
+ local name = obj:getName()
692
+ if string.find(name, "GiantExplosionTarget") then
693
+ local flagName = string.gsub(name, "Target", "")
694
+ table.insert(giantExplosionTargets, {
695
+ name = name,
696
+ flag = flagName,
697
+ obj = obj,
698
+ pos = obj:getPoint(),
699
+ static = splash_damage_options.giant_explosion_target_static
700
+ })
701
+ end
702
+ end
703
+ return true
704
+ end
705
+ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, {id = world.VolumeType.ALL}, findTargets)
706
+ if not splash_damage_options.giant_explosion_target_static then
707
+ timer.scheduleFunction(updateGiantExplosionPositions, {}, timer.getTime() + 1.0)
708
+ end
709
+ end
710
+
711
+ function updateTargetPosition()
712
+ for name, target in pairs(giantExplosionTargets) do
713
+ if target.obj:isExist() then
714
+ target.pos = target.obj:getPosition().p
715
+ end
716
+ end
717
+ return timer.getTime() + 1.0
718
+ end
719
+
720
+
721
+ --Giant Explosion Function
722
+ function triggerGiantExplosion(params)
723
+ if not splash_damage_options.giant_explosion_enabled then
724
+ debugMsg("Giant Explosion is disabled in options.")
725
+ return
726
+ end
727
+
728
+ local initialPos = params.pos or {x = 0, y = 0, z = 0}
729
+ local explosionPower = params.power or splash_damage_options.giant_explosion_power
730
+ local sizeScale = params.scale or splash_damage_options.giant_explosion_scale
731
+ local totalDuration = params.duration or splash_damage_options.giant_explosion_duration
732
+ local explosionCount = params.count or splash_damage_options.giant_explosion_count
733
+
734
+ if not initialPos.x or not initialPos.y or not initialPos.z then
735
+ gameMsg("Error: Invalid position for giant explosion!")
736
+ debugMsg("No valid initial position set for giant explosion!")
737
+ return
738
+ end
739
+
740
+ debugMsg("Triggering giant fireball at X: " .. initialPos.x .. ", Y: " .. initialPos.y .. ", Z: " .. initialPos.z)
741
+
742
+ local function scheduleExplosion(pos, delay)
743
+ if not pos or not pos.x or not pos.y or not pos.z then
744
+ debugMsg("Error: Invalid position for explosion - pos: " .. tostring(pos))
745
+ return
746
+ end
747
+ timer.scheduleFunction(function(p)
748
+ if p and p.x and p.y and p.z then
749
+ trigger.action.explosion(p, explosionPower)
750
+ end
751
+ end, pos, timer.getTime() + delay)
752
+ end
753
+
754
+ -- Pre-explosion scan for cargo units
755
+ local scanRadius = 1500 * sizeScale -- 1500m base radius, scaled by sizeScale
756
+ local preExplosionTargets = {}
757
+ if splash_damage_options.enable_cargo_effects then
758
+ local volS = {
759
+ id = world.VolumeType.SPHERE,
760
+ params = { point = initialPos, radius = scanRadius }
761
+ }
762
+ local ifFound = function(foundObject)
763
+ if foundObject:isExist() then
764
+ local category = foundObject:getCategory()
765
+ if (category == Object.Category.UNIT and foundObject:getDesc().category == Unit.Category.GROUND_UNIT) or
766
+ category == Object.Category.STATIC then
767
+ table.insert(preExplosionTargets, {
768
+ name = foundObject:getTypeName(),
769
+ health = foundObject:getLife() or 0,
770
+ position = foundObject:getPoint(),
771
+ maxHealth = (category == Object.Category.UNIT and foundObject:getDesc().life) or foundObject:getLife() or 0,
772
+ unit = foundObject
773
+ })
774
+ end
775
+ end
776
+ return true
777
+ end
778
+ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, volS, ifFound)
779
+ debugMsg("Pre-explosion scan for Giant Explosion: " .. #preExplosionTargets .. " targets found within " .. scanRadius .. "m")
780
+ end
781
+ -- Trigger the explosion
782
+ local maxRadius = 200 * sizeScale
783
+ local maxHeight = 500 * sizeScale
784
+ local adjustedExplosionCount = math.floor(explosionCount * (sizeScale ^ 2.5))
785
+ local stepTime = totalDuration / adjustedExplosionCount
786
+ local variance = 0.25 --Fixed at 25%
787
+
788
+ for i = 1, adjustedExplosionCount do
789
+ local progress = i / adjustedExplosionCount
790
+ local currentRadius = maxRadius * progress
791
+ local r = currentRadius * (0.9 + math.random() * 0.1)
792
+ local theta = math.random() * 2 * math.pi
793
+ local phi = math.acos(math.random())
794
+
795
+ local offsetX = r * math.sin(phi) * math.cos(theta)
796
+ local offsetZ = r * math.sin(phi) * math.sin(theta)
797
+ local offsetY = r * math.cos(phi)
798
+
799
+ offsetX = offsetX * (1 + (math.random() - 0.5) * variance)
800
+ offsetZ = offsetZ * (1 + (math.random() - 0.5) * variance)
801
+ offsetY = offsetY * (1 + (math.random() - 0.5) * variance * 0.5)
802
+
803
+ local blastPos = {
804
+ x = initialPos.x + offsetX,
805
+ y = land.getHeight({x = initialPos.x, y = initialPos.z}) + offsetY,
806
+ z = initialPos.z + offsetZ
807
+ }
808
+ if blastPos.y < land.getHeight({x = blastPos.x, y = blastPos.z}) then
809
+ blastPos.y = land.getHeight({x = blastPos.x, y = blastPos.z})
810
+ end
811
+
812
+ local delay = (i - 1) * stepTime + (math.random() - 0.5) * stepTime * variance
813
+ scheduleExplosion(blastPos, delay)
814
+ end
815
+
816
+ gameMsg("Expanding giant fireball over " .. totalDuration .. "s (scale " .. sizeScale .. ")!")
817
+
818
+ -- Post-explosion scan and cargo cook-off queuing
819
+ if splash_damage_options.enable_cargo_effects then
820
+ timer.scheduleFunction(function(args)
821
+ local centerPos = args[1]
822
+ local radius = args[2]
823
+ local preTargets = args[3]
824
+
825
+ local postExplosionTargets = {}
826
+ local volS = {
827
+ id = world.VolumeType.SPHERE,
828
+ params = { point = centerPos, radius = radius }
829
+ }
830
+ local ifFound = function(foundObject)
831
+ if foundObject:isExist() then
832
+ local category = foundObject:getCategory()
833
+ if (category == Object.Category.UNIT and foundObject:getDesc().category == Unit.Category.GROUND_UNIT) or
834
+ category == Object.Category.STATIC then
835
+ table.insert(postExplosionTargets, {
836
+ name = foundObject:getTypeName(),
837
+ health = foundObject:getLife() or 0,
838
+ position = foundObject:getPoint(),
839
+ maxHealth = (category == Object.Category.UNIT and foundObject:getDesc().life) or foundObject:getLife() or 0
840
+ })
841
+ end
842
+ end
843
+ return true
844
+ end
845
+ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, volS, ifFound)
846
+ debugMsg("Post-explosion scan for Giant Explosion: " .. #postExplosionTargets .. " targets found within " .. radius .. "m")
847
+
848
+ -- Compare pre- and post-explosion targets
849
+ for _, preTarget in ipairs(preTargets) do
850
+ local found = false
851
+ local postHealth = 0
852
+ for _, postTarget in ipairs(postExplosionTargets) do
853
+ if preTarget.name == postTarget.name and getDistance(preTarget.position, postTarget.position) < 1 then
854
+ found = true
855
+ postHealth = postTarget.health
856
+ break
857
+ end
858
+ end
859
+
860
+ local cargoData = cargoUnits[preTarget.name]
861
+ if cargoData and (not found or postHealth <= 0) then
862
+ local distance = getDistance(initialPos, preTarget.position)
863
+ if distance <= radius then
864
+ local cargoPower = cargoData.cargoExplosionPower or explosionPower
865
+ table.insert(cargoEffectsQueue, {
866
+ name = preTarget.name,
867
+ distance = distance,
868
+ coords = preTarget.position,
869
+ power = cargoPower,
870
+ explosion = cargoData.cargoExplosion,
871
+ cookOff = cargoData.cargoCookOff,
872
+ cookOffCount = cargoData.cookOffCount,
873
+ cookOffPower = cargoData.cookOffPower,
874
+ cookOffDuration = cargoData.cookOffDuration,
875
+ cookOffRandomTiming = cargoData.cookOffRandomTiming,
876
+ cookOffPowerRandom = cargoData.cookOffPowerRandom,
877
+ isTanker = cargoData.isTanker,
878
+ flameSize = cargoData.flameSize,
879
+ flameDuration = cargoData.flameDuration
880
+ })
881
+ debugMsg("Queued cargo effect for " .. preTarget.name .. " destroyed by Giant Explosion at " .. string.format("%.1f", distance) .. "m")
882
+ end
883
+ end
884
+ end
885
+
886
+ -- Process queued cargo effects with prioritized flames
887
+ if #cargoEffectsQueue > 0 then
888
+ local flameIndex = 0 -- Separate index for flames
889
+ local otherIndex = 0 -- Index for explosions, cook-offs, debris
890
+ local processedCargoUnits = {}
891
+ local flamePositions = {}
892
+ for _, effect in ipairs(cargoEffectsQueue) do
893
+ local unitKey = effect.name .. "_" .. effect.coords.x .. "_" .. effect.coords.z
894
+ if not processedUnitsGlobal[unitKey] and not processedCargoUnits[unitKey] then
895
+ -- Handle tanker flames first with minimal delay
896
+ if effect.isTanker and effect.explosion then
897
+ debugMsg("Triggering cargo explosion for tanker " .. effect.name .. " at " .. string.format("%.1f", effect.distance) .. "m with power " .. effect.power .. " scheduled at " .. flameIndex .. "s")
898
+ timer.scheduleFunction(function(params)
899
+ 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])
900
+ trigger.action.explosion(params[1], params[2])
901
+ end, {effect.coords, effect.power}, timer.getTime() + flameIndex + 0.1)
902
+
903
+ local flameSize = effect.flameSize or 3
904
+ local flameDuration = effect.flameDuration
905
+ local flameDensity = 1.0
906
+ local effectId = effectSmokeId
907
+ effectSmokeId = effectSmokeId + 1
908
+ local isDuplicate = false
909
+ for _, pos in pairs(flamePositions) do
910
+ if getDistance3D(effect.coords, pos) < 3 then
911
+ isDuplicate = true
912
+ 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))
913
+ break
914
+ end
915
+ end
916
+ if not isDuplicate then
917
+ debugMsg("Adding flame effect for tanker " .. effect.name .. " at " .. string.format("%.1f", effect.distance) .. "m (Size: " .. flameSize .. ", Duration: " .. flameDuration .. "s, ID: " .. effectId .. ") scheduled at " .. flameIndex .. "s")
918
+ timer.scheduleFunction(function(params)
919
+ local terrainHeight = land.getHeight({x = params[1].x, y = params[1].z})
920
+ local adjustedCoords = {x = params[1].x, y = terrainHeight + 2, z = params[1].z}
921
+ debugMsg("Spawning flame effect at X: " .. string.format("%.0f", adjustedCoords.x) .. ", Y: " .. string.format("%.0f", adjustedCoords.y) .. ", Z: " .. string.format("%.0f", adjustedCoords.z))
922
+ trigger.action.explosion(adjustedCoords, 10) -- Small trigger explosion
923
+ trigger.action.effectSmokeBig(adjustedCoords, params[2], params[3], params[4])
924
+ end, {effect.coords, flameSize, flameDensity, effectId}, timer.getTime() + flameIndex + 0.2)
925
+ timer.scheduleFunction(function(id)
926
+ debugMsg("Stopping flame effect for " .. effect.name .. " (ID: " .. id .. ")")
927
+ trigger.action.effectSmokeStop(id)
928
+ end, effectId, timer.getTime() + flameIndex + flameDuration + 0.2)
929
+ table.insert(flamePositions, effect.coords)
930
+ end
931
+ flameIndex = flameIndex + 0.5 -- Fast spacing for flames (0.5s)
932
+ end
933
+ -- Handle non-tanker explosions, cook-offs, and debris
934
+ if not effect.isTanker or (effect.explosion and not effect.isTanker) then
935
+ if effect.explosion then
936
+ debugMsg("Triggering cargo explosion for " .. effect.name .. " at " .. string.format("%.1f", effect.distance) .. "m with power " .. effect.power .. " scheduled at " .. otherIndex .. "s")
937
+ timer.scheduleFunction(function(params)
938
+ 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])
939
+ trigger.action.explosion(params[1], params[2])
940
+ end, {effect.coords, effect.power}, timer.getTime() + otherIndex + 0.1)
941
+ end
942
+ if effect.cookOff and effect.cookOffCount > 0 then
943
+ debugMsg("Scheduling " .. effect.cookOffCount .. " cook-off explosions for " .. effect.name .. " at " .. string.format("%.1f", effect.distance) .. "m over " .. effect.cookOffDuration .. "s starting at " .. otherIndex .. "s")
944
+ for i = 1, effect.cookOffCount do
945
+ local delay = effect.cookOffRandomTiming and math.random() * effect.cookOffDuration or (i - 1) * (effect.cookOffDuration / effect.cookOffCount)
946
+ local basePower = effect.cookOffPower
947
+ local powerVariation = effect.cookOffPowerRandom / 100
948
+ local cookOffPower = effect.cookOffPowerRandom == 0 and basePower or basePower * (1 + powerVariation * (math.random() * 2 - 1))
949
+ 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))
950
+ timer.scheduleFunction(function(params)
951
+ debugMsg("Executing cook-off 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])
952
+ trigger.action.explosion(params[1], params[2])
953
+ end, {effect.coords, cookOffPower}, timer.getTime() + otherIndex + delay)
954
+ end
955
+ if splash_damage_options.debris_effects then
956
+ local debrisCount = math.random(splash_damage_options.debris_count_min, splash_damage_options.debris_count_max)
957
+ for j = 1, debrisCount do
958
+ local theta = math.random() * 2 * math.pi
959
+ local phi = math.acos(math.random() * 2 - 1)
960
+ local minDist = splash_damage_options.debris_max_distance * 0.1
961
+ local maxDist = splash_damage_options.debris_max_distance
962
+ local r = math.random() * (maxDist - minDist) + minDist
963
+ local debrisX = effect.coords.x + r * math.sin(phi) * math.cos(theta)
964
+ local debrisZ = effect.coords.z + r * math.sin(phi) * math.sin(theta)
965
+ local terrainY = land.getHeight({x = debrisX, y = debrisZ})
966
+ local debrisY = terrainY + math.random() * maxDist
967
+ local debrisPos = {x = debrisX, y = debrisY, z = debrisZ}
968
+ local debrisPower = splash_damage_options.debris_power
969
+ local debrisDelay = (j - 1) * (effect.cookOffDuration / debrisCount)
970
+ timer.scheduleFunction(function(debrisArgs)
971
+ debugMsg("Debris explosion at X: " .. string.format("%.0f", debrisArgs[1].x) .. ", Y: " .. string.format("%.0f", debrisArgs[1].y) .. ", Z: " .. string.format("%.0f", debrisArgs[1].z) .. " with power " .. debrisArgs[2])
972
+ trigger.action.explosion(debrisArgs[1], debrisArgs[2])
973
+ end, {debrisPos, debrisPower}, timer.getTime() + otherIndex + debrisDelay)
974
+ end
975
+ end
976
+ end
977
+ otherIndex = otherIndex + 1 -- Slower spacing for non-flame effects (1s)
978
+ end
979
+ processedCargoUnits[unitKey] = true
980
+ processedUnitsGlobal[unitKey] = true
981
+ end
982
+ end
983
+ cargoEffectsQueue = {} -- Clear the queue after processing
984
+ end
985
+ end, {initialPos, scanRadius, preExplosionTargets}, timer.getTime() + totalDuration + 1.0)
986
+ end
987
+ end
988
+
989
+ --Flag Checker for mission editor
990
+ function checkGiantExplosionFlag()
991
+ for name, target in pairs(giantExplosionTargets) do
992
+ local flagName = name:gsub("GiantExplosionTarget", "GiantExplosionTarget")
993
+ local flagValue = trigger.misc.getUserFlag(flagName)
994
+ --commenting out as it spams every second
995
+ debugMsg("Checking flag " .. flagName .. ": " .. flagValue)
996
+ if flagValue == 1 then
997
+ debugMsg("Triggering explosion for " .. name .. " at X:" .. target.pos.x .. " Y:" .. target.pos.y .. " Z:" .. target.pos.z)
998
+ triggerGiantExplosion({
999
+ pos = target.pos,
1000
+ power = splash_damage_options.giant_explosion_power,
1001
+ scale = splash_damage_options.giant_explosion_scale,
1002
+ duration = splash_damage_options.giant_explosion_duration,
1003
+ count = splash_damage_options.giant_explosion_count
1004
+ })
1005
+ trigger.action.setUserFlag(flagName, 2)
1006
+ end
1007
+ end
1008
+ return timer.getTime() + splash_damage_options.giant_explosion_poll_rate
1009
+ end
1010
+
1011
+ function getWeaponExplosive(name)
1012
+ local weaponData = explTable[name]
1013
+ if weaponData then
1014
+ return weaponData.explosive, weaponData.shaped_charge
1015
+ else
1016
+ return 0, false
1017
+ end
1018
+ end
1019
+
1020
+ function track_wpns_cluster_scan(args)
1021
+ local parentPos = args[1]
1022
+ local parentDir = args[2]
1023
+ local parentName = args[3]
1024
+ local subName = args[4]
1025
+ local subCount = args[5]
1026
+ local subPower = args[6]
1027
+ local parentVel = args[7]
1028
+ local attempt = args[8] or 1
1029
+ local maxAttempts = 3
1030
+ local scanVol = {
1031
+ id = world.VolumeType.SPHERE,
1032
+ params = { point = parentPos, radius = 400 }
1033
+ }
1034
+ local bombletsFound = {}
1035
+ local allWeaponsFound = {}
1036
+ --General scan for all weapons
1037
+ world.searchObjects(Object.Category.WEAPON, scanVol, function(wpn)
1038
+ if wpn:isExist() then
1039
+ local wpnId = wpn.id_
1040
+ local wpnType = wpn:getTypeName()
1041
+ local wpnPos = wpn:getPosition().p
1042
+ table.insert(allWeaponsFound, { id = wpnId, type = wpnType, x = wpnPos.x, y = wpnPos.y, z = wpnPos.z })
1043
+ if wpnType == subName and not tracked_weapons[wpnId] then
1044
+ tracked_weapons[wpnId] = {
1045
+ wpn = wpn,
1046
+ pos = wpnPos,
1047
+ speed = wpn:getVelocity(),
1048
+ name = wpnType,
1049
+ parent = parentName,
1050
+ parentVelocity = parentVel
1051
+ }
1052
+ table.insert(bombletsFound, wpnId)
1053
+ 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 .. ")")
1054
+ end
1055
+ end
1056
+ return true
1057
+ end)
1058
+ --Log results
1059
+ debugMsg("Scanned for submunition '" .. subName .. "' bomblets from '" .. parentName .. "': " .. #bombletsFound .. " found (Attempt " .. attempt .. ")")
1060
+ if #allWeaponsFound > 0 then
1061
+ local msg = "General scan for '" .. parentName .. "': " .. #allWeaponsFound .. " bomblets released, expected " .. subCount .. " '" .. subName .. "'"
1062
+ local typeMismatch = false
1063
+ for _, wpn in ipairs(allWeaponsFound) do
1064
+ if wpn.type ~= subName then
1065
+ typeMismatch = true
1066
+ break
1067
+ end
1068
+ end
1069
+ if typeMismatch then
1070
+ msg = msg .. " - Mismatch detected! Actual bomblets: "
1071
+ for _, wpn in ipairs(allWeaponsFound) do
1072
+ msg = msg .. "'" .. wpn.type .. "' (X: " .. string.format("%.0f", wpn.x) .. ", Y: " .. string.format("%.0f", wpn.y) .. ", Z: " .. string.format("%.0f", wpn.z) .. ") "
1073
+ end
1074
+ msg = msg .. "Script may need changing."
1075
+ end
1076
+ debugMsg(msg)
1077
+ elseif #bombletsFound == 0 and #allWeaponsFound == 0 then
1078
+ debugMsg("No bomblets of any type detected for '" .. parentName .. "' (Attempt " .. attempt .. ")")
1079
+ end
1080
+ --Retry if no expected submunitions found
1081
+ if #bombletsFound == 0 and attempt < maxAttempts then
1082
+ debugMsg("No expected submunition '" .. subName .. "' found on attempt " .. attempt .. ", retrying in 0.5s")
1083
+ timer.scheduleFunction(track_wpns_cluster_scan, {parentPos, parentDir, parentName, subName, subCount, subPower, parentVel, attempt + 1}, timer.getTime() + 0.5)
1084
+ elseif #bombletsFound == 0 and attempt == maxAttempts then
1085
+ debugMsg("No submunition '" .. subName .. "' spawned by DCS for '" .. parentName .. "' after " .. maxAttempts .. " attempts - skipping additional explosions")
1086
+ end
1087
+ end
1088
+
1089
+ ----[[ ##### Updated track_wpns() Function ##### ]]----
1090
+ local recentExplosions = {}
1091
+ function track_wpns()
1092
+ local weaponsToRemove = {} --Delay removal to ensure all weapons are checked
1093
+ for wpn_id_, wpnData in pairs(tracked_weapons) do
1094
+ local status, err = pcall(function()
1095
+ if wpnData.wpn:isExist() then
1096
+ --Update position, direction, speed
1097
+ wpnData.pos = wpnData.wpn:getPosition().p
1098
+ wpnData.dir = wpnData.wpn:getPosition().x
1099
+ wpnData.speed = wpnData.wpn:getVelocity()
1100
+ --[[
1101
+
1102
+
1103
+ --Tick-by-tick tracking from weapon's actual position
1104
+ local tickVol = {
1105
+ id = world.VolumeType.SPHERE,
1106
+ params = {
1107
+ point = wpnData.pos, --Real weapon position
1108
+ radius = 150 --150m radius
1109
+ }
1110
+ }
1111
+ local tickTargets = {}
1112
+ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, function(obj)
1113
+ if obj:isExist() then
1114
+ table.insert(tickTargets, {
1115
+ name = obj:getTypeName(),
1116
+ distance = getDistance3D(wpnData.pos, obj:getPoint()), --3D distance
1117
+ position = obj:getPoint(),
1118
+ health = obj:getLife() or 0
1119
+ })
1120
+ end
1121
+ return true
1122
+ end)
1123
+ 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")
1124
+ for i, target in ipairs(tickTargets) do
1125
+ 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)
1126
+ end
1127
+
1128
+
1129
+ ]]--
1130
+
1131
+ --Scan potential blast zone in the last frame before impact
1132
+ if splash_damage_options.track_pre_explosion then
1133
+ local ip = land.getIP(wpnData.pos, wpnData.dir, lookahead(wpnData.speed))
1134
+ local predictedImpact = ip or wpnData.pos
1135
+
1136
+ local base_explosive, isShapedCharge = getWeaponExplosive(wpnData.name)
1137
+ base_explosive = base_explosive * splash_damage_options.overall_scaling
1138
+ if splash_damage_options.rocket_multiplier and wpnData.cat == Weapon.Category.ROCKET then
1139
+ base_explosive = base_explosive * splash_damage_options.rocket_multiplier
1140
+ end
1141
+
1142
+ local explosionPower = base_explosive
1143
+ if splash_damage_options.apply_shaped_charge_effects and isShapedCharge then
1144
+ explosionPower = explosionPower * splash_damage_options.shaped_charge_multiplier
1145
+ end
1146
+
1147
+ local blastRadius = splash_damage_options.blast_search_radius * 2 --Wider post-scan (180m default)
1148
+ if splash_damage_options.use_dynamic_blast_radius then
1149
+ blastRadius = math.pow(explosionPower, 1/3) * 10 * splash_damage_options.dynamic_blast_radius_modifier
1150
+ end
1151
+
1152
+ --Tight scan while weapon exists
1153
+ --local tightRadius = 50
1154
+ --if splash_damage_options.use_dynamic_blast_radius then
1155
+ --tightRadius = math.pow(explosionPower, 1/3) * 5 * splash_damage_options.dynamic_blast_radius_modifier
1156
+ --end
1157
+ local tightRadius = blastRadius --Use already calculated blastRadius
1158
+ local volS = {
1159
+ id = world.VolumeType.SPHERE,
1160
+ params = {
1161
+ point = wpnData.pos, --Use current pos
1162
+ radius = tightRadius
1163
+ }
1164
+ }
1165
+ local tightTargets = {}
1166
+ local ifFound = function(foundObject, targets, center)
1167
+ if foundObject:isExist() then
1168
+ local category = foundObject:getCategory()
1169
+ if (category == Object.Category.UNIT and (foundObject:getDesc().category == Unit.Category.GROUND_UNIT or foundObject:getDesc().category == Unit.Category.AIRPLANE)) or
1170
+ category == Object.Category.STATIC then
1171
+ table.insert(targets, {
1172
+ name = foundObject:getTypeName(),
1173
+ distance = getDistance(center, foundObject:getPoint()),
1174
+ health = foundObject:getLife() or 0,
1175
+ position = foundObject:getPoint(),
1176
+ maxHealth = (category == Object.Category.UNIT and foundObject:getDesc().life) or foundObject:getLife() or 0,
1177
+ unit = foundObject
1178
+ })
1179
+ end
1180
+ end
1181
+ return true
1182
+ end
1183
+ if splash_damage_options.track_pre_explosion_debug then
1184
+ debugMsg("Scanning tight radius " .. tightRadius .. "m at current pos while weapon exists")
1185
+ end
1186
+ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, volS, function(obj) ifFound(obj, tightTargets, wpnData.pos) end)
1187
+ wpnData.tightTargets = tightTargets --Store for impact
1188
+
1189
+ --Wider scan for lastKnownTargets
1190
+ volS.params.point = predictedImpact
1191
+ volS.params.radius = blastRadius
1192
+ local foundTargets = {}
1193
+ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, volS, function(obj) ifFound(obj, foundTargets, predictedImpact) end)
1194
+ wpnData.lastKnownTargets = foundTargets
1195
+ end
1196
+ --Submunition impact handling
1197
+ local weaponData = explTable[wpnData.parent or wpnData.name] or { submunition_name = "unknown" }
1198
+ if wpnData.name == weaponData.submunition_name then
1199
+ local groundHeight = land.getHeight({x = wpnData.pos.x, y = wpnData.pos.z})
1200
+ if wpnData.pos.y - groundHeight < 50 then --Impact threshold like old script
1201
+ 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))
1202
+ local parentWeaponData = explTable[wpnData.parent] or { submunition_count = 30, submunition_explosive = 1 }
1203
+ local submunitionCount = parentWeaponData.submunition_count or 30
1204
+ local submunitionPower = (parentWeaponData.submunition_explosive or 1) * splash_damage_options.cluster_bomblet_damage_modifier * splash_damage_options.overall_scaling
1205
+ if splash_damage_options.cluster_bomblet_reductionmodifier then
1206
+ if submunitionCount > 35 then
1207
+ local reductionFactor = (60 - 35) / (247 - 35)
1208
+ submunitionCount = 35 + math.floor((submunitionCount - 35) * reductionFactor)
1209
+ if submunitionCount > 60 then submunitionCount = 60 end
1210
+ end
1211
+ end
1212
+ --Use parent velocity if available, else submunition speed
1213
+ local parentDir = wpnData.parentVelocity or wpnData.speed
1214
+ local dispersionLength, dispersionWidth = calculate_dispersion(parentDir, 2000) --Match original 2000m
1215
+ local dirMag = math.sqrt(parentDir.x^2 + parentDir.z^2)
1216
+ local dir = dirMag > 0 and {x = parentDir.x / dirMag, z = parentDir.z / dirMag} or {x = 1, z = 0}
1217
+ 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")
1218
+ for i = 1, submunitionCount do
1219
+ local theta = math.random() * 2 * math.pi
1220
+ local r = math.sqrt(math.random())
1221
+ local xOffset = r * dispersionLength * 0.5 * math.cos(theta)
1222
+ local zOffset = r * dispersionWidth * 0.5 * math.sin(theta)
1223
+ local subPos = {
1224
+ x = wpnData.pos.x + (xOffset * dir.x - zOffset * dir.z),
1225
+ z = wpnData.pos.z + (xOffset * dir.z + zOffset * dir.x)
1226
+ }
1227
+ subPos.y = land.getHeight({x = subPos.x, y = subPos.z})
1228
+ debugMsg("Triggering bomblet #" .. i .. " for submunition '" .. wpnData.name .. "' at X: " .. string.format("%.0f", subPos.x) .. ", Z: " .. string.format("%.0f", subPos.z) .. " with power " .. submunitionPower)
1229
+ trigger.action.explosion(subPos, submunitionPower)
1230
+ end
1231
+ table.insert(weaponsToRemove, wpn_id_)
1232
+ end
1233
+ end
1234
+ else
1235
+ --Weapon has impacted
1236
+ debugMsg("Weapon " .. wpnData.name .. " no longer exists at " .. timer.getTime() .. "s")
1237
+ local ip = land.getIP(wpnData.pos, wpnData.dir, lookahead(wpnData.speed)) --terrain intersection point with weapon's nose. Only search out 20 meters though.
1238
+ local explosionPoint
1239
+ if not ip then --use last calculated IP
1240
+ explosionPoint = wpnData.pos
1241
+ else --use intersection point
1242
+ explosionPoint = ip
1243
+ end
1244
+ local chosenTargets = wpnData.tightTargets or {}
1245
+ local safeToBlast = true
1246
+ if splash_damage_options.ordnance_protection then
1247
+ local checkVol = { id = world.VolumeType.SPHERE, params = { point = explosionPoint, radius = splash_damage_options.ordnance_protection_radius } }
1248
+ 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")
1249
+ world.searchObjects(Object.Category.WEAPON, checkVol, function(obj)
1250
+ if obj:isExist() and tracked_weapons[obj.id_] then
1251
+ safeToBlast = false
1252
+ debugMsg("Skipping explosion for '" .. wpnData.name .. "' - nearby bomb '" .. tracked_weapons[obj.id_].name .. "' within " .. splash_damage_options.ordnance_protection_radius .. "m")
1253
+ return false
1254
+ end
1255
+ return true
1256
+ end)
1257
+ end
1258
+ if safeToBlast then
1259
+ debugMsg("FinalPos Check for '" .. wpnData.name .. "': X: " .. string.format("%.0f", explosionPoint.x) .. ", Y: " .. string.format("%.0f", explosionPoint.y) .. ", Z: " .. string.format("%.0f", explosionPoint.z) .. ")")
1260
+ local base_explosive, isShapedCharge = getWeaponExplosive(wpnData.name)
1261
+ base_explosive = base_explosive * splash_damage_options.overall_scaling
1262
+ if splash_damage_options.rocket_multiplier and wpnData.cat == Weapon.Category.ROCKET then
1263
+ base_explosive = base_explosive * splash_damage_options.rocket_multiplier
1264
+ end
1265
+
1266
+ local explosionPower = base_explosive
1267
+ if splash_damage_options.apply_shaped_charge_effects and isShapedCharge then
1268
+ explosionPower = explosionPower * splash_damage_options.shaped_charge_multiplier
1269
+ end
1270
+
1271
+ local blastRadius = splash_damage_options.blast_search_radius * 2 --Wider post-scan (180m default)
1272
+ if splash_damage_options.use_dynamic_blast_radius then
1273
+ blastRadius = math.pow(explosionPower, 1/3) * 10 * splash_damage_options.dynamic_blast_radius_modifier
1274
+ end
1275
+
1276
+
1277
+ --Store pre-explosion state of all tracked weapons for detection
1278
+ local preExplosionWeapons = {}
1279
+ if splash_damage_options.ordnance_protection and splash_damage_options.detect_ordnance_destruction and splash_damage_options.larger_explosions then
1280
+ for id, data in pairs(tracked_weapons) do
1281
+ if data.wpn:isExist() then
1282
+ preExplosionWeapons[id] = {
1283
+ name = data.name,
1284
+ pos = data.wpn:getPosition().p,
1285
+ distance = getDistance3D(explosionPoint, data.wpn:getPosition().p),
1286
+ explosive = getWeaponExplosive(data.name) --Store the explosive power
1287
+ }
1288
+ end
1289
+ end
1290
+ end
1291
+ --Cluster Bomb Handling
1292
+ local weaponData = explTable[wpnData.name] or { explosive = 0, shaped_charge = false }
1293
+ local isCluster = weaponData.cluster or false
1294
+ if splash_damage_options.cluster_enabled and isCluster then
1295
+ local submunitionCount = weaponData.submunition_count or 30
1296
+ local submunitionPower = (weaponData.submunition_explosive or 1) * splash_damage_options.cluster_bomblet_damage_modifier * splash_damage_options.overall_scaling
1297
+ local submunitionName = weaponData.submunition_name or "unknown"
1298
+ --Apply bomblet reduction logic if enabled
1299
+ if splash_damage_options.cluster_bomblet_reductionmodifier then
1300
+ if submunitionCount > 35 then
1301
+ local reductionFactor = (60 - 35) / (247 - 35)
1302
+ submunitionCount = 35 + math.floor((submunitionCount - 35) * reductionFactor)
1303
+ if submunitionCount > 60 then submunitionCount = 60 end --Cap at 60
1304
+ end
1305
+ end
1306
+ --Extended scan with general bomblet detection
1307
+ timer.scheduleFunction(track_wpns_cluster_scan, {explosionPoint, wpnData.dir, wpnData.name, submunitionName, submunitionCount, submunitionPower, wpnData.speed}, timer.getTime() + 0.3)
1308
+ else
1309
+ --Standard explosion handling
1310
+ if splash_damage_options.larger_explosions then
1311
+ debugMsg("Triggering initial explosion for '" .. wpnData.name .. "' at power " .. explosionPower)
1312
+ trigger.action.explosion(explosionPoint, explosionPower)
1313
+ table.insert(recentExplosions, { pos = explosionPoint, time = timer.getTime(), radius = blastRadius })
1314
+ debugMsg("Added to recentExplosions for '" .. wpnData.name .. "': X: " .. explosionPoint.x .. ", Y: " .. explosionPoint.y .. ", Z: " .. explosionPoint.z .. ", Time: " .. timer.getTime())
1315
+ end
1316
+ blastWave(explosionPoint, splash_damage_options.blast_search_radius, wpnData.ordnance, explosionPower, isShapedCharge)
1317
+ end
1318
+ --detect_ordnance_destruction comes before recent_large_explosion_snap in original
1319
+ if splash_damage_options.ordnance_protection and splash_damage_options.detect_ordnance_destruction and splash_damage_options.larger_explosions then
1320
+ timer.scheduleFunction(function(args)
1321
+ local explosionPoint = args[1]
1322
+ local blastRadius = args[2]
1323
+ local triggeringWeapon = args[3]
1324
+ local preExplosionWeapons = args[4]
1325
+ for id, preData in pairs(preExplosionWeapons) do
1326
+ if tracked_weapons[id] and not tracked_weapons[id].wpn:isExist() then
1327
+ if preData.distance <= blastRadius then
1328
+ 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)
1329
+ gameMsg(msg)
1330
+ debugMsg(msg)
1331
+ env.info(msg)
1332
+ if splash_damage_options.snap_to_ground_if_destroyed_by_large_explosion then
1333
+ local groundPos = {
1334
+ x = preData.pos.x,
1335
+ y = land.getHeight({x = preData.pos.x, y = preData.pos.z}),
1336
+ z = preData.pos.z
1337
+ }
1338
+ local destroyedWeaponPower, isShapedCharge = preData.explosive
1339
+ destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.overall_scaling
1340
+ if splash_damage_options.rocket_multiplier and tracked_weapons[id].cat == Weapon.Category.ROCKET then
1341
+ destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.rocket_multiplier
1342
+ end
1343
+ if splash_damage_options.apply_shaped_charge_effects and isShapedCharge then
1344
+ destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.shaped_charge_multiplier
1345
+ end
1346
+ 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)
1347
+ trigger.action.explosion(groundPos, destroyedWeaponPower)
1348
+ end
1349
+ end
1350
+ end
1351
+ end
1352
+ end, {explosionPoint, blastRadius, wpnData.name, preExplosionWeapons}, timer.getTime() + 0.2)
1353
+ end
1354
+ --recent_large_explosion_snap comes after main explosion and detect_ordnance_destruction
1355
+ if splash_damage_options.ordnance_protection and splash_damage_options.larger_explosions and splash_damage_options.recent_large_explosion_snap and splash_damage_options.snap_to_ground_if_destroyed_by_large_explosion then
1356
+ local currentTime = timer.getTime()
1357
+ for id, data in pairs(tracked_weapons) do
1358
+ if id ~= wpn_id_ and not data.wpn:isExist() then
1359
+ local terrainHeight = land.getHeight({x = data.pos.x, y = data.pos.z})
1360
+ local weaponHeight = data.pos.y - terrainHeight --Calculate height above ground
1361
+ local isMidAir = weaponHeight > 5 --Still checks if above ground
1362
+ local snapTriggered = false
1363
+ for _, explosion in ipairs(recentExplosions) do
1364
+ local timeDiff = currentTime - explosion.time
1365
+ local distance = getDistance3D(data.pos, explosion.pos)
1366
+ 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")
1367
+ if timeDiff <= splash_damage_options.recent_large_explosion_time and distance <= splash_damage_options.recent_large_explosion_range then
1368
+ if isMidAir and weaponHeight <= splash_damage_options.max_snapped_height then --New height check
1369
+ local groundPos = { x = data.pos.x, y = terrainHeight, z = data.pos.z }
1370
+ local destroyedWeaponPower, isShapedCharge = getWeaponExplosive(data.name)
1371
+ destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.overall_scaling
1372
+ if splash_damage_options.rocket_multiplier and data.cat == Weapon.Category.ROCKET then
1373
+ destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.rocket_multiplier
1374
+ end
1375
+ if splash_damage_options.apply_shaped_charge_effects and isShapedCharge then
1376
+ destroyedWeaponPower = destroyedWeaponPower * splash_damage_options.shaped_charge_multiplier
1377
+ end
1378
+ 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 .. " (Height: " .. string.format("%.0f", weaponHeight) .. "m)")
1379
+ trigger.action.explosion(groundPos, destroyedWeaponPower)
1380
+ snapTriggered = true
1381
+ table.insert(weaponsToRemove, id)
1382
+ break
1383
+ elseif isMidAir then
1384
+ debugMsg("Weapon " .. data.name .. " destroyed above max_snapped_height (" .. splash_damage_options.max_snapped_height .. "m) at " .. string.format("%.0f", weaponHeight) .. "m, skipping snap")
1385
+ else
1386
+ 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")
1387
+ snapTriggered = true
1388
+ break
1389
+ end
1390
+ end
1391
+ end
1392
+ if not snapTriggered then
1393
+ if isMidAir then
1394
+ 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")
1395
+ else
1396
+ debugMsg("Weapon " .. data.name .. " impacted ground, not processed by recent large explosion settings")
1397
+ end
1398
+ end
1399
+ end
1400
+ end
1401
+ local newExplosions = {}
1402
+ for _, explosion in ipairs(recentExplosions) do
1403
+ if currentTime - explosion.time <= splash_damage_options.recent_large_explosion_time then
1404
+ table.insert(newExplosions, explosion)
1405
+ end
1406
+ end
1407
+ recentExplosions = newExplosions
1408
+ end
1409
+ --Mark units as destroyed to avoid MiST accessing them
1410
+ local destroyedUnits = {}
1411
+ for _, target in ipairs(chosenTargets) do
1412
+ if target.unit:isExist() and target.health > 0 and target.unit:getLife() <= 0 then
1413
+ destroyedUnits[target.name] = true
1414
+ debugMsg("Marked " .. target.name .. " as destroyed pre-impact")
1415
+ end
1416
+ end
1417
+ --Schedule explosion handling with original 0.1-second delay, enhanced error handling
1418
+ timer.scheduleFunction(function(args)
1419
+ local finalPos = args[1]
1420
+ local explosionPoint = args[2]
1421
+ local explosionPower = args[3]
1422
+ local isShapedCharge = args[4]
1423
+ local blastRadius = args[5]
1424
+ local chosenTargets = args[6]
1425
+ local weaponName = args[7]
1426
+ local wpnData = args[8]
1427
+ if splash_damage_options.debug then
1428
+ debugMsg("Starting impact handling for " .. weaponName .. " at " .. timer.getTime() .. "s")
1429
+ end
1430
+ local status, err = pcall(function()
1431
+ --Log pre-explosion targets
1432
+ if splash_damage_options.track_pre_explosion then
1433
+ if #chosenTargets > 0 then
1434
+ local msg = "Targets in blast zone for " .. weaponName .. " BEFORE explosion (last frame, using finalPos):\n"
1435
+ for i, target in ipairs(chosenTargets) do
1436
+ msg = msg .. "- " .. target.name .. " (Dist: " .. string.format("%.1f", target.distance) .. "m, Health: " .. target.health .. ")\n"
1437
+ end
1438
+ debugMsg(msg)
1439
+ env.info("SplashDamage Pre-Explosion (Last Frame): " .. msg)
1440
+ else
1441
+ debugMsg("No targets in blast zone for " .. weaponName .. " BEFORE explosion (last frame)")
1442
+ env.info("SplashDamage Pre-Explosion (Last Frame): No targets in blast zone for " .. weaponName)
1443
+ end
1444
+ end
1445
+
1446
+ blastWave(explosionPoint, splash_damage_options.blast_search_radius, wpnData.ordnance, explosionPower, isShapedCharge)
1447
+
1448
+ --Post-explosion analysis and queue cargo effects
1449
+ if splash_damage_options.track_pre_explosion then
1450
+ timer.scheduleFunction(function(innerArgs)
1451
+ local impactPoint = innerArgs[1]
1452
+ local blastRadius = innerArgs[2]
1453
+ local preExplosionTargets = innerArgs[3] or {}
1454
+ local weaponName = innerArgs[4]
1455
+ local weaponPower = innerArgs[5]
1456
+ if splash_damage_options.debug == true then
1457
+ debugMsg("Starting post-explosion analysis for " .. weaponName .. " at " .. timer.getTime() .. "s")
1458
+ end
1459
+
1460
+ --Scan all units in wider radius
1461
+ local postExplosionTargets = {}
1462
+ local volS = {
1463
+ id = world.VolumeType.SPHERE,
1464
+ params = {
1465
+ point = impactPoint,
1466
+ radius = blastRadius
1467
+ }
1468
+ }
1469
+
1470
+ local ifFound = function(foundObject)
1471
+ if foundObject:isExist() then
1472
+ local category = foundObject:getCategory()
1473
+ if (category == Object.Category.UNIT and (foundObject:getDesc().category == Unit.Category.GROUND_UNIT or foundObject:getDesc().category == Unit.Category.AIRPLANE)) or
1474
+ category == Object.Category.STATIC then
1475
+ table.insert(postExplosionTargets, {
1476
+ name = foundObject:getTypeName(),
1477
+ health = foundObject:getLife() or 0,
1478
+ position = foundObject:getPoint(),
1479
+ maxHealth = (category == Object.Category.UNIT and foundObject:getDesc().life) or foundObject:getLife() or 0
1480
+ })
1481
+ end
1482
+ end
1483
+ return true
1484
+ end
1485
+
1486
+ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, volS, ifFound)
1487
+
1488
+ local msg = "Post-explosion analysis for " .. weaponName .. ":\n"
1489
+
1490
+ --Match pre-detected units
1491
+ for _, preTarget in ipairs(preExplosionTargets) do
1492
+ local found = false
1493
+ local postHealth = 0
1494
+ local postPosition = nil
1495
+ for _, postTarget in ipairs(postExplosionTargets) do
1496
+ if preTarget.name == postTarget.name and getDistance(preTarget.position, postTarget.position) < 1 then
1497
+ found = true
1498
+ postHealth = postTarget.health
1499
+ postPosition = postTarget.position
1500
+ break
1501
+ end
1502
+ end
1503
+
1504
+ local healthPercent = preTarget.maxHealth > 0 and (postHealth / preTarget.maxHealth * 100) or 0
1505
+ local status = ""
1506
+
1507
+ if not found or postHealth <= 0 then
1508
+ status = "WAS FULLY DESTROYED"
1509
+ elseif healthPercent < splash_damage_options.cargo_damage_threshold then
1510
+ status = "WAS DAMAGED BELOW THRESHOLD"
1511
+ else
1512
+ status = "SURVIVED (Health: " .. postHealth .. ")"
1513
+ end
1514
+
1515
+ --Always include coords in status message
1516
+ local coords = found and postPosition or preTarget.position
1517
+ local statusMsg = status .. " AT " .. string.format("X: %.0f, Y: %.0f, Z: %.0f", coords.x, coords.y, coords.z) .. " (Pre: " .. preTarget.health .. ", Post: " .. postHealth .. ")"
1518
+ --Check if target is in cargoUnits and within blast radius
1519
+ local cargoData = cargoUnits[preTarget.name]
1520
+ if cargoData and preTarget.distance <= blastRadius and
1521
+ (not found or postHealth <= 0 or healthPercent < splash_damage_options.cargo_damage_threshold) then
1522
+
1523
+ if splash_damage_options.enable_cargo_effects then
1524
+ local cargoPower = cargoData.cargoExplosionPower or weaponPower --Use fixed power or fallback
1525
+ table.insert(cargoEffectsQueue, {
1526
+ name = preTarget.name,
1527
+ distance = preTarget.distance,
1528
+ coords = coords,
1529
+ power = cargoPower,
1530
+ explosion = cargoData.cargoExplosion,
1531
+ cookOff = cargoData.cargoCookOff,
1532
+ cookOffCount = cargoData.cookOffCount,
1533
+ cookOffPower = cargoData.cookOffPower,
1534
+ cookOffDuration = cargoData.cookOffDuration,
1535
+ cookOffRandomTiming = cargoData.cookOffRandomTiming,
1536
+ cookOffPowerRandom = cargoData.cookOffPowerRandom,
1537
+ isTanker = cargoData.isTanker,
1538
+ flameSize = cargoData.flameSize,
1539
+ flameDuration = cargoData.flameDuration
1540
+ })
1541
+ statusMsg = statusMsg .. " WITH CARGO EXPLOSION (Power: " .. cargoPower .. ")"
1542
+ if cargoData.cargoCookOff and cargoData.cookOffCount > 0 then
1543
+ statusMsg = statusMsg .. " WITH COOK-OFF (" .. cargoData.cookOffCount .. " blasts over " .. cargoData.cookOffDuration .. "s)"
1544
+ end
1545
+ end
1546
+ end
1547
+
1548
+ msg = msg .. "- " .. preTarget.name .. " " .. statusMsg .. "\n"
1549
+ end
1550
+ --Check for additional units
1551
+ for _, postTarget in ipairs(postExplosionTargets) do
1552
+ local isPreDetected = false
1553
+ for _, preTarget in ipairs(preExplosionTargets) do
1554
+ if preTarget.name == postTarget.name and getDistance(preTarget.position, postTarget.position) < 1 then
1555
+ isPreDetected = true
1556
+ break
1557
+ end
1558
+ end
1559
+ if not isPreDetected then
1560
+ local coords = postTarget.position
1561
+ local healthPercent = postTarget.maxHealth > 0 and (postTarget.health / postTarget.maxHealth * 100) or 0
1562
+ local status = postTarget.health <= 0 and "WAS FULLY DESTROYED" or
1563
+ (healthPercent < splash_damage_options.cargo_damage_threshold and "WAS DAMAGED BELOW THRESHOLD" or
1564
+ "SURVIVED (Health: " .. postTarget.health .. ")")
1565
+ local statusMsg = status .. " AT " .. string.format("X: %.0f, Y: %.0f, Z: %.0f", coords.x, coords.y, coords.z) .. " (Pre: Unknown, Post: " .. postTarget.health .. ")"
1566
+ local cargoData = cargoUnits[postTarget.name]
1567
+ if cargoData and (postTarget.health <= 0 or healthPercent < splash_damage_options.cargo_damage_threshold) then
1568
+ if splash_damage_options.enable_cargo_effects then
1569
+ local cargoPower = cargoData.cargoExplosionPower or weaponPower --Use fixed power or fallback
1570
+ local distance = getDistance(impactPoint, coords)
1571
+ table.insert(cargoEffectsQueue, {
1572
+ name = postTarget.name,
1573
+ distance = distance,
1574
+ coords = coords,
1575
+ power = cargoPower,
1576
+ explosion = cargoData.cargoExplosion,
1577
+ cookOff = cargoData.cargoCookOff,
1578
+ cookOffCount = cargoData.cookOffCount,
1579
+ cookOffPower = cargoData.cookOffPower,
1580
+ cookOffDuration = cargoData.cookOffDuration,
1581
+ cookOffRandomTiming = cargoData.cookOffRandomTiming,
1582
+ cookOffPowerRandom = cargoData.cookOffPowerRandom,
1583
+ isTanker = cargoData.isTanker,
1584
+ flameSize = cargoData.flameSize,
1585
+ flameDuration = cargoData.flameDuration
1586
+ })
1587
+ statusMsg = statusMsg .. " WITH CARGO EXPLOSION (Power: " .. cargoPower .. ")"
1588
+ if cargoData.cargoCookOff and cargoData.cookOffCount > 0 then
1589
+ statusMsg = statusMsg .. " WITH COOK-OFF (" .. cargoData.cookOffCount .. " blasts over " .. cargoData.cookOffDuration .. "s)"
1590
+ end
1591
+ end
1592
+ end
1593
+ msg = msg .. "- " .. postTarget.name .. " " .. statusMsg .. "\n"
1594
+ end
1595
+ end
1596
+
1597
+ --Schedule all queued cargo effects
1598
+ if #cargoEffectsQueue > 0 then
1599
+ local effectIndex = 0
1600
+ local processedCargoUnits = {} --Track processed units
1601
+ local flamePositions = {} --Track flame coords with 3m radius
1602
+ for _, effect in ipairs(cargoEffectsQueue) do
1603
+ local unitKey = effect.name .. "_" .. effect.coords.x .. "_" .. effect.coords.z
1604
+ if not processedUnitsGlobal[unitKey] and not processedCargoUnits[unitKey] then
1605
+ if effect.explosion then
1606
+ debugMsg("Triggering cargo explosion for " .. effect.name .. " at " .. string.format("%.1f", effect.distance) .. "m with power " .. effect.power .. " scheduled at " .. effectIndex .. "s")
1607
+ timer.scheduleFunction(function(params)
1608
+ 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])
1609
+ trigger.action.explosion(params[1], params[2])
1610
+ end, {effect.coords, effect.power}, timer.getTime() + effectIndex + 0.1) --Slight delay for visibility
1611
+ if effect.isTanker then
1612
+ local flameSize = effect.flameSize or 3
1613
+ local flameDuration = effect.flameDuration --Use cargoUnits value directly, no default
1614
+ local flameDensity = 1.0 --Max density for visibility
1615
+ local effectId = effectSmokeId
1616
+ effectSmokeId = effectSmokeId + 1
1617
+ --Check for nearby flames within 3m
1618
+ local isDuplicate = false
1619
+ for _, pos in pairs(flamePositions) do
1620
+ if getDistance3D(effect.coords, pos) < 3 then
1621
+ isDuplicate = true
1622
+ 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))
1623
+ break
1624
+ end
1625
+ end
1626
+ if not isDuplicate then
1627
+ 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")
1628
+ timer.scheduleFunction(function(params)
1629
+ --Adjust Y-coordinate to terrain height + offset
1630
+ local terrainHeight = land.getHeight({x = params[1].x, y = params[1].z})
1631
+ local adjustedCoords = {x = params[1].x, y = terrainHeight + 2, z = params[1].z}
1632
+ debugMsg("Spawning flame effect at X: " .. string.format("%.0f", adjustedCoords.x) .. ", Y: " .. string.format("%.0f", adjustedCoords.y) .. ", Z: " .. string.format("%.0f", adjustedCoords.z))
1633
+ trigger.action.explosion(adjustedCoords, 10) --Small explosion to force visibility
1634
+ trigger.action.effectSmokeBig(adjustedCoords, params[2], params[3], params[4])
1635
+ end, {effect.coords, flameSize, flameDensity, effectId}, timer.getTime() + effectIndex + 0.2) --Slight delay
1636
+ timer.scheduleFunction(function(id)
1637
+ debugMsg("Stopping flame effect for " .. effect.name .. " (ID: " .. id .. ")")
1638
+ trigger.action.effectSmokeStop(id)
1639
+ end, effectId, timer.getTime() + effectIndex + flameDuration + 0.2)
1640
+ table.insert(flamePositions, effect.coords)
1641
+ end
1642
+ end
1643
+ end
1644
+ debugMsg("Checking cook-off for " .. effect.name .. ": cookOff=" .. tostring(effect.cookOff) .. ", count=" .. tostring(effect.cookOffCount))
1645
+ if effect.cookOff and effect.cookOffCount > 0 then
1646
+ 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")
1647
+ for i = 1, effect.cookOffCount do
1648
+ local delay = effect.cookOffRandomTiming and math.random() * effect.cookOffDuration or (i - 1) * (effect.cookOffDuration / effect.cookOffCount)
1649
+ local basePower = effect.cookOffPower
1650
+ local powerVariation = effect.cookOffPowerRandom / 100
1651
+ local cookOffPower = effect.cookOffPowerRandom == 0 and basePower or basePower * (1 + powerVariation * (math.random() * 2 - 1))
1652
+ 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))
1653
+ timer.scheduleFunction(function(params)
1654
+ local pos = params[1]
1655
+ local power = params[2]
1656
+ debugMsg("Executing cook-off at " .. string.format("X: %.0f, Y: %.0f, Z: %.0f", pos.x, pos.y, pos.z) .. " with power " .. power)
1657
+ trigger.action.explosion(pos, power)
1658
+ end, {effect.coords, cookOffPower}, timer.getTime() + effectIndex + delay)
1659
+ end
1660
+ --Debris burst only if cook-off is true and enabled
1661
+ if splash_damage_options.debris_effects then
1662
+ local debrisCount = math.random(splash_damage_options.debris_count_min, splash_damage_options.debris_count_max)
1663
+ for j = 1, debrisCount do
1664
+ --Random spherical offset
1665
+ local theta = math.random() * 2 * math.pi --Horizontal angle
1666
+ local phi = math.acos(math.random() * 2 - 1) --Vertical angle for sphere
1667
+ local minDist = splash_damage_options.debris_max_distance * 0.1 --10% of max
1668
+ local maxDist = splash_damage_options.debris_max_distance
1669
+ local r = math.random() * (maxDist - minDist) + minDist --10% to full max distance
1670
+ local debrisX = effect.coords.x + r * math.sin(phi) * math.cos(theta)
1671
+ local debrisZ = effect.coords.z + r * math.sin(phi) * math.sin(theta)
1672
+ local terrainY = land.getHeight({x = debrisX, y = debrisZ})
1673
+ local debrisY = terrainY + math.random() * maxDist --0 to max_distance above ground
1674
+ local debrisPos = {x = debrisX, y = debrisY, z = debrisZ}
1675
+ local debrisPower = splash_damage_options.debris_power
1676
+ local debrisDelay = (j - 1) * (effect.cookOffDuration / debrisCount) --Spread over cook-off duration
1677
+ timer.scheduleFunction(function(debrisArgs)
1678
+ local dPos = debrisArgs[1]
1679
+ local dPower = debrisArgs[2]
1680
+ 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)
1681
+ trigger.action.explosion(dPos, dPower)
1682
+ end, {debrisPos, debrisPower}, timer.getTime() + effectIndex + debrisDelay)
1683
+ end
1684
+ end
1685
+ end
1686
+
1687
+
1688
+ processedCargoUnits[unitKey] = true
1689
+ processedUnitsGlobal[unitKey] = true
1690
+ effectIndex = effectIndex + 3 --3 secs spacing if not random
1691
+ end
1692
+ end
1693
+ --Clear the queue after scheduling
1694
+ cargoEffectsQueue = {}
1695
+ end
1696
+
1697
+ debugMsg(msg)
1698
+ env.info("SplashDamage Post-Explosion: " .. msg)
1699
+ end, {finalPos, blastRadius, chosenTargets, weaponName, explosionPower}, timer.getTime() + 1)
1700
+ end
1701
+ end)
1702
+ if not status then
1703
+ debugMsg("Impact handling error for '" .. weaponName .. "': " .. err)
1704
+ end
1705
+ end, {explosionPoint, explosionPoint, explosionPower, isShapedCharge, blastRadius, chosenTargets, wpnData.name, wpnData}, timer.getTime() + 0.1)
1706
+ else
1707
+ debugMsg("Explosion skipped due to ordnance protection for '" .. wpnData.name .. "'")
1708
+ if splash_damage_options.larger_explosions then
1709
+ table.insert(recentExplosions, { pos = explosionPoint, time = timer.getTime(), radius = blastRadius })
1710
+ debugMsg("Skipped explosion logged for snap check for '" .. wpnData.name .. "': X: " .. explosionPoint.x .. ", Y: " .. explosionPoint.y .. ", Z: " .. explosionPoint.z .. ", Time: " .. timer.getTime())
1711
+ end
1712
+ end
1713
+ table.insert(weaponsToRemove, wpn_id_)
1714
+ end
1715
+ end)
1716
+ if not status then
1717
+ debugMsg("Error in track_wpns for '" .. (wpnData.name or "unknown weapon") .. "': " .. err)
1718
+ end
1719
+ end
1720
+ --Perform all removals after iteration
1721
+ for _, id in ipairs(weaponsToRemove) do
1722
+ tracked_weapons[id] = nil
1723
+ end
1724
+ return timer.getTime() + refreshRate
1725
+ end
1726
+ function onWpnEvent(event)
1727
+ if event.id == world.event.S_EVENT_SHOT then
1728
+ if event.weapon then
1729
+ local ordnance = event.weapon
1730
+ local typeName = trim(ordnance:getTypeName())
1731
+ if splash_damage_options.debug then
1732
+ env.info("Weapon fired: [" .. typeName .. "]")
1733
+ debugMsg("Weapon fired: [" .. typeName .. "]")
1734
+ end
1735
+ if string.find(typeName, "weapons.shells") then
1736
+ if splash_damage_options.debug then
1737
+ debugMsg("Event shot, but not tracking: " .. typeName)
1738
+ env.info("SplashDamage: event shot, but not tracking: " .. typeName .. " (" .. event.initiator:getTypeName() .. ")")
1739
+ end
1740
+ return
1741
+ end
1742
+
1743
+ --Check if weapon is in explTable before tracking
1744
+ if not explTable[typeName] then
1745
+ env.info("SplashDamage: " .. typeName .. " missing from script (" .. event.initiator:getTypeName() .. ")")
1746
+ if splash_damage_options.weapon_missing_message == true then
1747
+ trigger.action.outText("SplashDamage: " .. typeName .. " missing from script (" .. (event.initiator and event.initiator:isExist() and event.initiator:getTypeName() or "no initiator") .. ")", 3)
1748
+ -- if mist and mist.utils and mist.utils.tableShow then --Only if MiST is present
1749
+ -- local success, desc = pcall(mist.utils.tableShow, ordnance:getDesc())
1750
+ -- if success then
1751
+ -- debugMsg("desc for [" .. typeName .. "]: " .. desc)
1752
+ -- else
1753
+ -- debugMsg("Could not retrieve description for [" .. typeName .. "]. Object may no longer exist.")
1754
+ -- end
1755
+ -- end
1756
+ env.info("Current keys in explTable:")
1757
+ for k, v in pairs(explTable) do
1758
+ env.info("Key: [" .. k .. "]")
1759
+ end
1760
+ end
1761
+ return --Skip tracking this weapon since its not in the table
1762
+ end
1763
+
1764
+ if (ordnance:getDesc().category ~= 0) and event.initiator then
1765
+ if ordnance:getDesc().category == 1 then
1766
+ if (ordnance:getDesc().MissileCategory ~= 1 and ordnance:getDesc().MissileCategory ~= 2) then
1767
+ 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() }
1768
+ end
1769
+ else
1770
+ 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() }
1771
+ end
1772
+ end
1773
+ end
1774
+ end
1775
+ end
1776
+
1777
+ local function protectedCall(...)
1778
+ local status, retval = pcall(...)
1779
+ if not status then
1780
+ env.warning("Splash damage script error... gracefully caught! " .. retval, true)
1781
+ end
1782
+ end
1783
+
1784
+ function WpnHandler:onEvent(event)
1785
+ protectedCall(onWpnEvent, event)
1786
+ end
1787
+
1788
+ function explodeObject(args)
1789
+ local point = args[1]
1790
+ local distance = args[2]
1791
+ local power = args[3]
1792
+ trigger.action.explosion(point, power)
1793
+ end
1794
+
1795
+ function blastWave(_point, _radius, weapon, power, isShapedCharge)
1796
+ if isShapedCharge then
1797
+ _radius = _radius * splash_damage_options.shaped_charge_multiplier
1798
+ end
1799
+ if splash_damage_options.use_dynamic_blast_radius then
1800
+ local dynamicRadius = math.pow(power, 1/3) * 5 * splash_damage_options.dynamic_blast_radius_modifier
1801
+ if isShapedCharge then
1802
+ _radius = dynamicRadius * splash_damage_options.shaped_charge_multiplier
1803
+ else
1804
+ _radius = dynamicRadius
1805
+ end
1806
+ end
1807
+
1808
+ local foundUnits = {}
1809
+ local volS = {
1810
+ id = world.VolumeType.SPHERE,
1811
+ params = {
1812
+ point = _point,
1813
+ radius = _radius
1814
+ }
1815
+ }
1816
+
1817
+ local ifFound = function(foundObject, val)
1818
+ if foundObject:getDesc().category == Unit.Category.GROUND_UNIT and foundObject:getCategory() == Object.Category.UNIT then
1819
+ foundUnits[#foundUnits + 1] = foundObject
1820
+ end
1821
+ if foundObject:getDesc().category == Unit.Category.GROUND_UNIT then
1822
+ if splash_damage_options.blast_stun == true then
1823
+ --suppressUnit(foundObject, 2, weapon)
1824
+ end
1825
+ end
1826
+ if splash_damage_options.wave_explosions then
1827
+ local obj = foundObject
1828
+ local obj_location = obj:getPoint()
1829
+ local dist = getDistance(_point, obj_location)
1830
+ local timing = dist / 500
1831
+ if obj:isExist() and tableHasKey(obj:getDesc(), "box") then
1832
+ local length = (obj:getDesc().box.max.x + math.abs(obj:getDesc().box.min.x))
1833
+ local height = (obj:getDesc().box.max.y + math.abs(obj:getDesc().box.min.y))
1834
+ local depth = (obj:getDesc().box.max.z + math.abs(obj:getDesc().box.min.z))
1835
+ local _length = length
1836
+ local _depth = depth
1837
+ if depth > length then
1838
+ _length = depth
1839
+ _depth = length
1840
+ end
1841
+ local surface_distance = dist - _depth / 2
1842
+ local scaled_power_factor = 0.006 * power + 1
1843
+ local intensity = (power * scaled_power_factor) / (4 * math.pi * surface_distance^2)
1844
+ local surface_area = _length * height
1845
+ local damage_for_surface = intensity * surface_area
1846
+ if damage_for_surface > splash_damage_options.cascade_damage_threshold then
1847
+ local explosion_size = damage_for_surface
1848
+ if obj:getDesc().category == Unit.Category.STRUCTURE then
1849
+ explosion_size = intensity * splash_damage_options.static_damage_boost
1850
+ end
1851
+ if explosion_size > power then explosion_size = power end
1852
+ local triggerExplosion = false
1853
+ if splash_damage_options.always_cascade_explode then
1854
+ triggerExplosion = true
1855
+ else
1856
+ if obj:getDesc().life then
1857
+ local healthPercent = (obj:getLife() / obj:getDesc().life) * 100
1858
+ if healthPercent <= splash_damage_options.cascade_explode_threshold then
1859
+ triggerExplosion = true
1860
+ end
1861
+ --Queue cargo effects for units below
1862
+ local cargoData = cargoUnits[obj:getTypeName()]
1863
+ if cargoData and healthPercent <= splash_damage_options.cargo_damage_threshold and splash_damage_options.enable_cargo_effects then
1864
+ local cargoPower = power * cargoData.cargoExplosionMult
1865
+ table.insert(cargoEffectsQueue, {
1866
+ name = obj:getTypeName(),
1867
+ distance = dist,
1868
+ coords = obj_location,
1869
+ power = cargoPower,
1870
+ explosion = cargoData.cargoExplosion,
1871
+ cookOff = cargoData.cargoCookOff,
1872
+ cookOffCount = cargoData.cookOffCount,
1873
+ cookOffPower = cargoData.cookOffPower,
1874
+ cookOffDuration = cargoData.cookOffDuration,
1875
+ cookOffRandomTiming = cargoData.cookOffRandomTiming,
1876
+ cookOffPowerRandom = cargoData.cookOffPowerRandom,
1877
+ isTanker = cargoData.isTanker,
1878
+ flameSize = cargoData.flameSize,
1879
+ flameDuration = cargoData.flameDuration
1880
+ })
1881
+ end
1882
+ else
1883
+ triggerExplosion = true
1884
+ end
1885
+ if not triggerExplosion and obj:getDesc().category == Unit.Category.GROUND_UNIT then
1886
+ local health = obj:getLife() or 0
1887
+ if health <= 0 then
1888
+ triggerExplosion = true
1889
+ end
1890
+ end
1891
+ end
1892
+ if triggerExplosion then
1893
+ timer.scheduleFunction(explodeObject, {obj_location, dist, explosion_size * splash_damage_options.cascade_scaling}, timer.getTime() + timing)
1894
+ end
1895
+ end
1896
+ end
1897
+ end
1898
+ return true
1899
+ end
1900
+
1901
+ world.searchObjects(Object.Category.UNIT, volS, ifFound)
1902
+ world.searchObjects(Object.Category.STATIC, volS, ifFound)
1903
+ world.searchObjects(Object.Category.SCENERY, volS, ifFound)
1904
+ world.searchObjects(Object.Category.CARGO, volS, ifFound)
1905
+ if splash_damage_options.damage_model then
1906
+ timer.scheduleFunction(modelUnitDamage, foundUnits, timer.getTime() + 1.5)
1907
+ end
1908
+ end
1909
+
1910
+ function modelUnitDamage(units)
1911
+ for i, unit in ipairs(units) do
1912
+ if unit:isExist() then
1913
+ local health = (unit:getLife() / unit:getDesc().life) * 100
1914
+ if unit:hasAttribute("Infantry") and health > 0 then
1915
+ if health <= splash_damage_options.infantry_cant_fire_health then
1916
+ unit:getController():setOption(AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD)
1917
+ end
1918
+ end
1919
+ if unit:getDesc().category == Unit.Category.GROUND_UNIT and (not unit:hasAttribute("Infantry")) and health > 0 then
1920
+ if health <= splash_damage_options.unit_cant_fire_health then
1921
+ unit:getController():setOption(AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD)
1922
+ gameMsg(unit:getTypeName() .. " weapons disabled")
1923
+ end
1924
+ if health <= splash_damage_options.unit_disabled_health and health > 0 then
1925
+ unit:getController():setTask({id = 'Hold', params = {}})
1926
+ unit:getController():setOnOff(false)
1927
+ gameMsg(unit:getTypeName() .. " disabled")
1928
+ end
1929
+ end
1930
+ end
1931
+ end
1932
+ end
1933
+
1934
+ function updateSplashDamageSetting(setting, increment)
1935
+ if not splash_damage_options[setting] then
1936
+ env.info("Error: Setting " .. setting .. " does not exist.")
1937
+ return
1938
+ end
1939
+
1940
+ local newValue = math.max(0, splash_damage_options[setting] + increment)
1941
+ env.info("Updating " .. setting .. " from " .. tostring(splash_damage_options[setting]) .. " to " .. tostring(newValue))
1942
+ splash_damage_options[setting] = newValue
1943
+ trigger.action.outText("Updated " .. setting .. " to: " .. tostring(splash_damage_options[setting]), 5)
1944
+ end
1945
+
1946
+ function toggleSplashDamageSetting(setting)
1947
+ splash_damage_options[setting] = not splash_damage_options[setting]
1948
+ trigger.action.outText("Toggled " .. setting .. " to: " .. tostring(splash_damage_options[setting]), 5)
1949
+
1950
+ if setting == "enable_radio_menu" then
1951
+ if splash_damage_options.enable_radio_menu then
1952
+ addSplashDamageMenu()
1953
+ else
1954
+ missionCommands.removeItem(splash_damage_menu)
1955
+ splash_damage_menu = nil
1956
+ end
1957
+ end
1958
+ end
1959
+
1960
+ function addValueAdjustmentCommands(menu, setting)
1961
+ missionCommands.addCommand("+0.1", menu, updateSplashDamageSetting, setting, 0.1)
1962
+ missionCommands.addCommand("+1", menu, updateSplashDamageSetting, setting, 1)
1963
+ missionCommands.addCommand("+10", menu, updateSplashDamageSetting, setting, 10)
1964
+ missionCommands.addCommand("+100", menu, updateSplashDamageSetting, setting, 100)
1965
+
1966
+ missionCommands.addCommand("-0.1", menu, updateSplashDamageSetting, setting, -0.1)
1967
+ missionCommands.addCommand("-1", menu, updateSplashDamageSetting, setting, -1)
1968
+ missionCommands.addCommand("-10", menu, updateSplashDamageSetting, setting, -10)
1969
+ missionCommands.addCommand("-100", menu, updateSplashDamageSetting, setting, -100)
1970
+ end
1971
+
1972
+ function exitSplashDamageMenu()
1973
+ if splash_damage_menu then
1974
+ missionCommands.removeItem(splash_damage_menu)
1975
+ splash_damage_menu = nil
1976
+ end
1977
+ end
1978
+
1979
+ function addSplashDamageMenu()
1980
+ if not splash_damage_options.enable_radio_menu then return end
1981
+
1982
+ if splash_damage_menu then
1983
+ missionCommands.removeItem(splash_damage_menu)
1984
+ end
1985
+
1986
+ splash_damage_menu = missionCommands.addSubMenu("Splash Damage Settings")
1987
+
1988
+ --Page 1: Debug & General Settings
1989
+ local debugGeneralMenu = missionCommands.addSubMenu("Debug & General Settings", splash_damage_menu)
1990
+ missionCommands.addCommand("Toggle Game Messages", debugGeneralMenu, toggleSplashDamageSetting, "game_messages")
1991
+ missionCommands.addCommand("Toggle Debug Messages", debugGeneralMenu, toggleSplashDamageSetting, "debug")
1992
+ missionCommands.addCommand("Toggle Weapon Missing Messages", debugGeneralMenu, toggleSplashDamageSetting, "weapon_missing_message")
1993
+ missionCommands.addCommand("Toggle Pre-Explosion Debug", debugGeneralMenu, toggleSplashDamageSetting, "track_pre_explosion_debug")
1994
+ missionCommands.addCommand("Toggle Damage Model", debugGeneralMenu, toggleSplashDamageSetting, "damage_model")
1995
+ missionCommands.addCommand("Toggle Blast Stun", debugGeneralMenu, toggleSplashDamageSetting, "blast_stun")
1996
+ local unitDisabledMenu = missionCommands.addSubMenu("Unit Disabled Health", debugGeneralMenu)
1997
+ addValueAdjustmentCommands(unitDisabledMenu, "unit_disabled_health")
1998
+ local unitCantFireMenu = missionCommands.addSubMenu("Unit Cant Fire Health", debugGeneralMenu)
1999
+ addValueAdjustmentCommands(unitCantFireMenu, "unit_cant_fire_health")
2000
+ local infantryCantFireMenu = missionCommands.addSubMenu("Infantry Cant Fire Health", debugGeneralMenu)
2001
+ addValueAdjustmentCommands(infantryCantFireMenu, "infantry_cant_fire_health")
2002
+ local rocketMultiplierMenu = missionCommands.addSubMenu("Rocket Multiplier", debugGeneralMenu)
2003
+ addValueAdjustmentCommands(rocketMultiplierMenu, "rocket_multiplier")
2004
+ --Page 2/3: Explosions
2005
+ local explosionCargoMenu = missionCommands.addSubMenu("Explosion Settings", splash_damage_menu)
2006
+ local staticDamageMenu = missionCommands.addSubMenu("Static Damage Boost", explosionCargoMenu)
2007
+ addValueAdjustmentCommands(staticDamageMenu, "static_damage_boost")
2008
+ missionCommands.addCommand("Toggle Wave Explosions", explosionCargoMenu, toggleSplashDamageSetting, "wave_explosions")
2009
+ missionCommands.addCommand("Toggle Larger Explosions", explosionCargoMenu, toggleSplashDamageSetting, "larger_explosions")
2010
+ local blastRadiusMenu = missionCommands.addSubMenu("Blast Search Radius", explosionCargoMenu)
2011
+ addValueAdjustmentCommands(blastRadiusMenu, "blast_search_radius")
2012
+
2013
+ local overallScalingMenu = missionCommands.addSubMenu("Overall Scaling", explosionCargoMenu)
2014
+ addValueAdjustmentCommands(overallScalingMenu, "overall_scaling")
2015
+ missionCommands.addCommand("Toggle Shaped Charge Effects", explosionCargoMenu, toggleSplashDamageSetting, "apply_shaped_charge_effects")
2016
+ local shapedChargeMenu = missionCommands.addSubMenu("Shaped Charge Multiplier", explosionCargoMenu)
2017
+ addValueAdjustmentCommands(shapedChargeMenu, "shaped_charge_multiplier")
2018
+ missionCommands.addCommand("Toggle Dynamic Blast Radius", explosionCargoMenu, toggleSplashDamageSetting, "use_dynamic_blast_radius")
2019
+ local dynamicBlastMenu = missionCommands.addSubMenu("Dynamic Blast Radius Modifier", explosionCargoMenu)
2020
+ addValueAdjustmentCommands(dynamicBlastMenu, "dynamic_blast_radius_modifier")
2021
+
2022
+ local explosionCargoMenu = missionCommands.addSubMenu("Cascade Settings", splash_damage_menu)
2023
+ local cascadeScalingMenu = missionCommands.addSubMenu("Cascade Scaling", explosionCargoMenu)
2024
+ addValueAdjustmentCommands(cascadeScalingMenu, "cascade_scaling")
2025
+ local cascadeExplodeThresholdMenu = missionCommands.addSubMenu("Cascade Explode Threshold", explosionCargoMenu)
2026
+ addValueAdjustmentCommands(cascadeExplodeThresholdMenu, "cascade_explode_threshold")
2027
+ local cascadeThresholdMenu = missionCommands.addSubMenu("Cascade Damage Threshold", explosionCargoMenu)
2028
+ addValueAdjustmentCommands(cascadeThresholdMenu, "cascade_damage_threshold")
2029
+
2030
+ --Page 4: Cargo and Ordnance Protection
2031
+ local explosionCargoMenu = missionCommands.addSubMenu("Cargo and Ordnance", splash_damage_menu)
2032
+ missionCommands.addCommand("Toggle Always Cascade Explode", explosionCargoMenu, toggleSplashDamageSetting, "always_cascade_explode")
2033
+ missionCommands.addCommand("Toggle Tracking & Cargo Effects", explosionCargoMenu, toggleSplashDamageSetting, "track_pre_explosion")
2034
+ local cargoThresholdMenu = missionCommands.addSubMenu("Cargo Damage Threshold", explosionCargoMenu)
2035
+ addValueAdjustmentCommands(cargoThresholdMenu, "cargo_damage_threshold")
2036
+ missionCommands.addCommand("Toggle Ordnance Protection", explosionCargoMenu, toggleSplashDamageSetting, "ordnance_protection")
2037
+ local ordnanceRadiusMenu = missionCommands.addSubMenu("Ordnance Protection Radius", explosionCargoMenu)
2038
+ addValueAdjustmentCommands(ordnanceRadiusMenu, "ordnance_protection_radius")
2039
+ missionCommands.addCommand("Toggle Snap To Ground If Destroyed By LE", explosionCargoMenu, toggleSplashDamageSetting, "snap_to_ground_if_destroyed_by_large_explosion")
2040
+ local ordnanceRadiusMenu = missionCommands.addSubMenu("Ordnance Protection Radius", explosionCargoMenu)
2041
+ local cargoThresholdMenu = missionCommands.addSubMenu("Max Snap Height", explosionCargoMenu)
2042
+ addValueAdjustmentCommands(cargoThresholdMenu, "max_snapped_height")
2043
+ missionCommands.addCommand("Toggle Recent Expl Track Snap", explosionCargoMenu, toggleSplashDamageSetting, "recent_large_explosion_snap")
2044
+
2045
+
2046
+ --Page 5: Debris Settings
2047
+ local debrisMenu = missionCommands.addSubMenu("Debris Settings", splash_damage_menu)
2048
+ missionCommands.addCommand("Toggle Debris Effects", debrisMenu, toggleSplashDamageSetting, "debris_effects")
2049
+ local debrisCountMinMenu = missionCommands.addSubMenu("Min Debris Count", debrisMenu)
2050
+ addValueAdjustmentCommands(debrisCountMinMenu, "debris_count_min")
2051
+ local debrisCountMaxMenu = missionCommands.addSubMenu("Max Debris Count", debrisMenu)
2052
+ addValueAdjustmentCommands(debrisCountMaxMenu, "debris_count_max")
2053
+ local debrisDistanceMenu = missionCommands.addSubMenu("Max Debris Distance", debrisMenu)
2054
+ addValueAdjustmentCommands(debrisDistanceMenu, "debris_max_distance")
2055
+ local debrisPowerMenu = missionCommands.addSubMenu("Debris Power", debrisMenu)
2056
+ addValueAdjustmentCommands(debrisPowerMenu, "debris_power")
2057
+
2058
+ --Page 6: Cluster Settings
2059
+ local clusterMenu = missionCommands.addSubMenu("Cluster Settings", splash_damage_menu)
2060
+ missionCommands.addCommand("Toggle Cluster Enabled", clusterMenu, toggleSplashDamageSetting, "cluster_enabled")
2061
+ local clusterBaseLengthMenu = missionCommands.addSubMenu("Cluster Base Length", clusterMenu)
2062
+ addValueAdjustmentCommands(clusterBaseLengthMenu, "cluster_base_length")
2063
+ local clusterBaseWidthMenu = missionCommands.addSubMenu("Cluster Base Width", clusterMenu)
2064
+ addValueAdjustmentCommands(clusterBaseWidthMenu, "cluster_base_width")
2065
+ local clusterMaxLengthMenu = missionCommands.addSubMenu("Cluster Max Length", clusterMenu)
2066
+ addValueAdjustmentCommands(clusterMaxLengthMenu, "cluster_max_length")
2067
+ local clusterMaxWidthMenu = missionCommands.addSubMenu("Cluster Max Width", clusterMenu)
2068
+ addValueAdjustmentCommands(clusterMaxWidthMenu, "cluster_max_width")
2069
+ local clusterMinLengthMenu = missionCommands.addSubMenu("Cluster Min Length", clusterMenu)
2070
+ addValueAdjustmentCommands(clusterMinLengthMenu, "cluster_min_length")
2071
+ local clusterMinWidthMenu = missionCommands.addSubMenu("Cluster Min Width", clusterMenu)
2072
+ addValueAdjustmentCommands(clusterMinWidthMenu, "cluster_min_width")
2073
+ missionCommands.addCommand("Toggle Bomblet Reduction Modifier", clusterMenu, toggleSplashDamageSetting, "cluster_bomblet_reductionmodifier")
2074
+ local clusterBombletDamageMenu = missionCommands.addSubMenu("Bomblet Damage Modifier", clusterMenu)
2075
+ addValueAdjustmentCommands(clusterBombletDamageMenu, "cluster_bomblet_damage_modifier")
2076
+
2077
+ --Page 7: Giant Explosion Settings
2078
+ local giantExplosionMenu = missionCommands.addSubMenu("Giant Explosion Settings", splash_damage_menu)
2079
+ missionCommands.addCommand("Toggle Giant Explosion", giantExplosionMenu, toggleSplashDamageSetting, "giant_explosion_enabled")
2080
+ missionCommands.addCommand("Toggle Static Target", giantExplosionMenu, toggleSplashDamageSetting, "giant_explosion_target_static")
2081
+ for name, target in pairs(giantExplosionTargets) do
2082
+ local displayName = name:gsub("GiantExplosionTarget", "GiantExplosionTarget")
2083
+ missionCommands.addCommand("Detonate " .. displayName, giantExplosionMenu, function()
2084
+ trigger.action.setUserFlag(displayName, 1)
2085
+ end)
2086
+ end
2087
+ missionCommands.addCommand("Detonate All Giant Targets", giantExplosionMenu, function()
2088
+ for name, target in pairs(giantExplosionTargets) do
2089
+ local flagName = name:gsub("GiantExplosionTarget", "GiantExplosionTarget")
2090
+ trigger.action.setUserFlag(flagName, 1)
2091
+ end
2092
+ end)
2093
+ local powerMenu = missionCommands.addSubMenu("Explosion Power", giantExplosionMenu)
2094
+ addValueAdjustmentCommands(powerMenu, "giant_explosion_power")
2095
+ local scaleMenu = missionCommands.addSubMenu("Size Scale", giantExplosionMenu)
2096
+ missionCommands.addCommand("+0.1", scaleMenu, updateSplashDamageSetting, "giant_explosion_scale", 0.1)
2097
+ missionCommands.addCommand("+0.5", scaleMenu, updateSplashDamageSetting, "giant_explosion_scale", 0.5)
2098
+ missionCommands.addCommand("-0.1", scaleMenu, updateSplashDamageSetting, "giant_explosion_scale", -0.1)
2099
+ missionCommands.addCommand("-0.5", scaleMenu, updateSplashDamageSetting, "giant_explosion_scale", -0.5)
2100
+ local durationMenu = missionCommands.addSubMenu("Duration", giantExplosionMenu)
2101
+ missionCommands.addCommand("+0.25s", durationMenu, updateSplashDamageSetting, "giant_explosion_duration", 0.25)
2102
+ missionCommands.addCommand("-0.25s", durationMenu, updateSplashDamageSetting, "giant_explosion_duration", -0.25)
2103
+ local countMenu = missionCommands.addSubMenu("Explosion Count", giantExplosionMenu)
2104
+ addValueAdjustmentCommands(countMenu, "giant_explosion_count")
2105
+
2106
+ end
2107
+
2108
+ if (script_enable == 1) then
2109
+ gameMsg("SPLASH DAMAGE 3.1 SCRIPT RUNNING")
2110
+ env.info("SPLASH DAMAGE 3.1 SCRIPT RUNNING")
2111
+
2112
+ timer.scheduleFunction(function()
2113
+ protectedCall(track_wpns)
2114
+ return timer.getTime() + refreshRate
2115
+ end, {}, timer.getTime() + refreshRate)
2116
+
2117
+ if splash_damage_options.giant_explosion_enabled then
2118
+ giantExplosionTargets = {} -- Ensure it’s fresh
2119
+ local targetCount = 0
2120
+ for coa = 0, 2 do
2121
+ local groups = coalition.getGroups(coa)
2122
+ if groups then
2123
+ for _, group in pairs(groups) do
2124
+ local units = group:getUnits()
2125
+ if units then
2126
+ for _, unit in pairs(units) do
2127
+ local name = unit:getName()
2128
+ if name:find("GiantExplosionTarget") then
2129
+ local pos = unit:getPosition().p
2130
+ giantExplosionTargets[name] = {obj = unit, pos = pos}
2131
+ if splash_damage_options.giant_explosion_target_static then
2132
+ giantExplosionTargets[name].pos = pos
2133
+ end
2134
+ debugMsg("Found GiantExplosionTarget: " .. name .. " at X:" .. pos.x .. " Y:" .. pos.y .. " Z:" .. pos.z)
2135
+ targetCount = targetCount + 1
2136
+ end
2137
+ end
2138
+ end
2139
+ end
2140
+ end
2141
+ end
2142
+ debugMsg("Total GiantExplosionTargets found: " .. targetCount)
2143
+ timer.scheduleFunction(checkGiantExplosionFlag, {}, timer.getTime() + splash_damage_options.giant_explosion_poll_rate)
2144
+ if not splash_damage_options.giant_explosion_target_static then
2145
+ timer.scheduleFunction(updateTargetPosition, {}, timer.getTime() + 1.0)
2146
+ end
2147
+ end
2148
+
2149
+ world.addEventHandler(WpnHandler)
2150
+ addSplashDamageMenu()
2151
+ end