@jtff/miztemplate-lib 3.8.2 → 3.8.3

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.
@@ -1,126 +1,74 @@
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
1
+ --[[-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-=
2
+ Latest Changes
3
+ -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-=-
57
4
 
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
5
+ x x 2025 - 3.4
76
6
 
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)
7
+ (Stevey666)
8
+
9
+ - Added in optional kill feed feature, this will try to display kills from DCS engine and kills from the additional explosions by checking pre/post scans of the explosion area
10
+ --SPLASH KILL FEED WORKS IN MP ONLY (you can host your local SP mission as MP for now)
11
+ - Added in Lekas Foothold Integration to allow splash kills to count towards the points, killfeed is required to be enabled for this
12
+ - Added AGM_45B to expl table
13
+
14
+ -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-=-
15
+ Full Changelog at the bottom of the script
16
+ -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-=-
81
17
 
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
18
 
88
- ----[[ ##### SCRIPT CONFIGURATION ##### ]]----
19
+ -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-=-
20
+ ##### SCRIPT CONFIGURATION #####
21
+ -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-]]
89
22
  splash_damage_options = {
90
- --debug options
23
+ ---------------------------------------------------------------------- Debug and Messages ----------------------------------------------------------------
91
24
  ["game_messages"] = false, --enable some messages on screen
92
25
  ["debug"] = false, --enable debugging messages
93
26
  ["weapon_missing_message"] = false, --false disables messages alerting you to weapons missing from the explTable
94
27
  ["track_pre_explosion_debug"] = false, --Toggle to enable/disable pre-explosion tracking debugging
28
+ ["track_groundunitordnance_debug"] = false, --Enable detailed debug messages for ground unit ordnance tracking
29
+ ["napalm_unitdamage_debug"] = false, --Enable detailed debug messages for napalm unit damage tracking
30
+ ["damage_model_game_messages"] = false, --ground unit movement and weapons disabled notification
31
+ ["killfeed_debug"] = false, --Enable detailed debug messages for killfeed
32
+ ["events_debug"] = false, --enable debugging for event handling
95
33
 
96
- ["enable_radio_menu"] = true, --enables the in-game radio menu for modifying settings
34
+ ---------------------------------------------------------------------- Radio -----------------------------------------------------------------------------
35
+ ["enable_radio_menu"] = false, --enables the in-game radio menu for modifying settings
97
36
 
37
+
38
+ ---------------------------------------------------------------------- Basic Splash Settings -------------------------------------------------------------
98
39
  ["static_damage_boost"] = 2000, --apply extra damage to Unit.Category.STRUCTUREs with wave explosions
99
40
  ["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
41
  ["larger_explosions"] = true, --secondary explosions on top of weapon impact points, dictated by the values in the explTable
101
42
  ["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.
43
+ ["blast_search_radius"] = 90, --this is the max size of any blast wave radius, since we will only find objects within this zone. Only used if dynamic is not enabled
44
+ ["use_dynamic_blast_radius"] = true, --if true, blast radius is calculated from explosion power; if false, blast_search_radius (90) is used
45
+ ["dynamic_blast_radius_modifier"] = 2, --multiplier for the blast radius
104
46
  ["blast_stun"] = false, --not implemented
47
+ ["overall_scaling"] = 1, --overall scaling for explosive power
48
+
49
+ ---------------------------------------------------------------------- Units -----------------------------------------------------------------------------
105
50
  ["unit_disabled_health"] = 30, --if health is below this value after our explosions, disable its movement
106
51
  ["unit_cant_fire_health"] = 40, --if health is below this value after our explosions, set ROE to HOLD to simulate damage weapon systems
107
52
  ["infantry_cant_fire_health"] = 60, --if health is below this value after our explosions, set ROE to HOLD to simulate severe injury
108
53
 
54
+
55
+ ---------------------------------------------------------------------- Rockets ---------------------------------------------------------------------------
109
56
  ["rocket_multiplier"] = 1.3, --multiplied by the explTable value for rockets
110
- ["overall_scaling"] = 1, --overall scaling for explosive power
111
-
57
+
58
+ ---------------------------------------------------------------------- Shaped Charge ---------------------------------------------------------------------
112
59
  ["apply_shaped_charge_effects"] = true, --apply reduction in blastwave etc for shaped charge munitions
113
60
  ["shaped_charge_multiplier"] = 0.2, --multiplier that reduces blast radius and explosion power for shaped charge munitions.
114
61
 
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
-
62
+
63
+ ---------------------------------------------------------------------- Cascading -------------------------------------------------------------------------
118
64
  ["cascade_scaling"] = 2, --multiplier for secondary (cascade) blast damage, 1 damage fades out too soon, 2 or 3 damage seems a good balance
65
+ ["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.
119
66
  ["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
67
  ["always_cascade_explode"] = false, --switch if you want everything to explode like with the original script
121
68
 
122
-
123
- --track_pre_explosion/enable_cargo_effects should both be the same value
69
+ ---------------------------------------------------------------------- Cargo Cook Off/Fuel Explosion ----------------------------------------------------
70
+ --track_pre_explosion/enable_cargo_effects should both be the same value--
71
+
124
72
  ["track_pre_explosion"] = true, --Toggle to enable/disable pre-explosion tracking
125
73
  ["enable_cargo_effects"] = true, --Toggle for enabling/disabling cargo explosions and cook-offs
126
74
  ["cargo_damage_threshold"] = 60, --Health % below which cargo explodes (0 = destroyed only)
@@ -130,6 +78,13 @@ splash_damage_options = {
130
78
  ["debris_count_max"] = 12, --Maximum debris pieces per cook-off
131
79
  ["debris_max_distance"] = 10, --Max distance debris can travel (meters), the min distance from the vehicle will be 10% of this
132
80
 
81
+ ["cookoff_flares_enabled"] = false, --Enable/disable flare effects for cook-offs
82
+ ["cookoff_flare_color"] = 2,
83
+ ["cookoff_flare_count_modifier"] = 1, --Multiplier for flare count (e.g., 1x, 2x cookOffCount from the vehicle table)
84
+ ["cookoff_flare_offset"] = 1, --Max offset distance for flares in meters (horizontal)
85
+
86
+
87
+ ---------------------------------------------------------------------- Ordnance Protection --------------------------------------------------------------
133
88
  ["ordnance_protection"] = true, --Toggle ordinance protection features
134
89
  ["ordnance_protection_radius"] = 10, --Distance in meters to protect nearby bombs
135
90
  ["detect_ordnance_destruction"] = true, --Toggle detection of ordnance destroyed by large explosions
@@ -139,7 +94,8 @@ splash_damage_options = {
139
94
  ["recent_large_explosion_range"] = 100, --range its looking for in meters for a recent large_explosion generated by the script
140
95
  ["recent_large_explosion_time"] = 4, --in seconds how long ago there was a recent large_explosion generated by the script
141
96
 
142
- --Cluster bomb settings
97
+
98
+ ---------------------------------------------------------------------- Cluster Bombs ---------------------------------------------------------------------
143
99
  ["cluster_enabled"] = false,
144
100
  ["cluster_base_length"] = 150, --Base forward spread (meters)
145
101
  ["cluster_base_width"] = 200, --Base lateral spread (meters)
@@ -150,14 +106,73 @@ splash_damage_options = {
150
106
  ["cluster_bomblet_reductionmodifier"] = true, --Use equation to reduce number of bomblets (to make it look better)
151
107
  ["cluster_bomblet_damage_modifier"] = 1, --Adjustable global modifier for bomblet explosive power
152
108
 
153
- --Giant Explosion Options - Remember, any target you want to blow up needs to be named "GiantExplosionTarget(X)" (X) being any value/name etc
109
+
110
+ ---------------------------------------------------------------------- Giant Explosions ------------------------------------------------------------------
111
+ --Remember, any target you want to blow up needs to be named "GiantExplosionTarget(X)" (X) being any value/name etc
154
112
  ["giant_explosion_enabled"] = true, --Toggle to enable/disable Giant Explosion
155
113
  ["giant_explosion_power"] = 6000, --Power in kg of TNT (default 8 tons)
156
114
  ["giant_explosion_scale"] = 1, --Size scale factor (default 1)
157
115
  ["giant_explosion_duration"] = 3.0, --Total duration in seconds (default 3s)
158
- ["giant_explosion_count"] = 250, --Number of explosions (default 300)
116
+ ["giant_explosion_count"] = 250, --Number of explosions (default 250)
159
117
  ["giant_explosion_target_static"] = true, --Toggle to true for static targets (store position once), false for dynamic (update every second)
160
118
  ["giant_explosion_poll_rate"] = 1, --Polling rate in seconds for flag checks (default 1s)
119
+ ["giantexplosion_ondamage"] = true, --Trigger explosion when unit is damaged
120
+ ["giantexplosion_ondeath"] = true, --Trigger explosion when unit is destroyed
121
+ ["giantexplosion_testmode"] = true, --Enable test mode with separate array for radio commands
122
+
123
+
124
+ ---------------------------------------------------------------------- Ground/Ship Ordnance -------------------------------------------------------------
125
+ ["track_groundunitordnance"] = false, --Enable tracking of ground unit ordnance (shells)
126
+ ["groundunitordnance_damage_modifier"] = 1.0, --Multiplier for ground unit ordnance explosive power
127
+ ["groundunitordnance_blastwave_modifier"] = 4.0, --Additional multiplier for blast wave intensity of ground unit ordnance
128
+ ["groundunitordnance_maxtrackedcount"] = 100, --Maximum number of ground ordnance shells tracked at once to prevent overload
129
+ ["scan_50m_for_groundordnance"] = true, --If true, uses a 50m scan radius for ground ordnance instead of dynamic blast radius
130
+
131
+
132
+ ---------------------------------------------------------------------- Smoke and Cookoff For All Vehicles -----------------------------------------------
133
+ ["smokeandcookoffeffectallvehicles"] = false, --Enable effects for all ground vehicles not in cargoUnits vehicle table
134
+ ["allunits_enable_smoke"] = false, -- Enable /disable smoke effects
135
+ ["allunits_enable_cookoff"] = false, -- Enable /disable cookoffs
136
+ ["allunits_explode_power"] = 50, --Initial power of vehicle exploding
137
+ ["allunits_default_flame_size"] = 6, --Default smoke size (called flame here in the code, but it'll be smoke) 5 = small smoke, 6 = medium smoke, 7 = large smoke, 8 = huge smoke
138
+ ["allunits_default_flame_duration"] = 60, --Default smoke (called flame here in the code, but it's smoke) duration in seconds for non-cargoUnits vehicles
139
+ ["allunits_cookoff_count"] = 4, --number of cookoff explosions to schedule
140
+ ["allunits_cookoff_duration"] = 30, --max time window of cookoffs (will be scheduled randomly between 0 seconds and this figure)
141
+ ["allunits_cookoff_power"] = 10, --power of the cookoff explosions
142
+ ["allunits_cookoff_powerrandom"] = 50, --percentage higher or lower of the cookoff power figure
143
+
144
+
145
+ ---------------------------------------------------------------------- Napalm ---------------------------------------------------------------------------
146
+ ["napalm_mk77_enabled"] = true, --Enable napalm effects for MK77mod0-WPN and MK77mod1-WPN
147
+ ["napalmoverride_enabled"] = false, --If true, enables napalm effects for weapons in napalm_override_weapons
148
+ ["napalm_override_weapons"] = "Mk_82,SAMP125LD", --Comma-separated list of weapons to override as napalm when overrides enabled, i.e Mk_82,SAMP125LD. Do not pick CBUs
149
+
150
+ ["napalm_spread_points"] = 4, --Number of points of explosion per each bomb (aka spawns of dummy fuel tank), so 1 bomb can have 4 fireballs as such. The MK77 0 is bigger and will do a % more by default (i.e 5 instead of 4)
151
+ ["napalm_spread_spacing"] = 25, --Distance m between the points
152
+ ["napalm_phosphor_enabled"] = true, --If true, enables phosphor flare effects for napalm weapons
153
+ ["napalm_phosphor_multiplier"] = 0.5, --Multiplier for number of phosphor flares that shoot out, there is a level of randomisation in the code already
154
+ ["napalm_addflame"] = true, --Enable flame effects at napalm spawn points
155
+ ["napalm_addflame_size"] = 3, --Flame size (1-8, 4 = huge smoke and fire)
156
+ ["napalm_addflame_duration"] = 180, --Flame duration in seconds napalm_destroy_delay
157
+ ["napalm_flame_delay"] = 0.01, --Delay in seconds before flame effect
158
+ ["napalm_explode_delay"] = 0.01, --Delay in seconds before putting an exlode on the ground to blow up the spawned fuel tank, original script had this as 0.1
159
+ ["napalm_destroy_delay"] = 0.02, --Delay in seconds before it destroys the fuel tank object, original script had this as 0.12
160
+
161
+ ["napalm_doublewide_enabled"] = false, --Toggle for double-wide napalm (two points per spread point, ~28m width)
162
+ ["napalm_doublewide_spread"] = 15, --Meters either side of bomb vector either side to spawn a fuel tank
163
+
164
+ ["napalm_unitdamage_enable"] = true, --Enable/disable napalm unit damage
165
+ ["napalm_unitdamage_scandistance"] = 70, --Scan radius in meters
166
+ ["napalm_unitdamage_startdelay"] = 0.1, --Seconds between Napalm exploding and explosion occurring (can be 0 for no delay)
167
+ ["napalm_unitdamage_spreaddelay"] = 0, --If startdelay is greater than 0, explosions are ordered by distance with this gap between each unit
168
+
169
+ ---------------------------------------------------------------------- Kill Feed ------------------------------------------------------------------------
170
+ ["killfeed_enable"] = false, --Enable killfeed logging and messaging
171
+ ["killfeed_game_messages"] = false, --Show killfeed SPLASH KILL FEED WORKS IN MP ONLY (you can host your local SP mission as MP for now)
172
+ ["killfeed_game_message_duration"] = 15, --Duration in seconds for game messages (killfeed and SplashKillFeed) - note the message will be delayed to let DCS catch up as per next option
173
+ ["killfeed_splashdelay"] = 8, --Duration in seconds delay to allow dcs to see that units are dead before saying the splash damage got them instead of the the players weapon
174
+ ["killfeed_lekas_foothold_integration"] = false, --Enable Lekas Foothold integration
175
+ ["killfeed_lekas_contribution_delay"] = 240, -- Delay in seconds before processing splash kills into Lekas contributions (default 240 seconds/4mins)
161
176
  }
162
177
 
163
178
  local script_enable = 1
@@ -188,7 +203,7 @@ flamesize:
188
203
  ["r11_volvo_drivable"] = {
189
204
  cargoExplosion = true,
190
205
  cargoExplosionMult = 2.0,
191
- cargoExplosionPower = 200,
206
+ cargoExplosionPower = 50,
192
207
  cargoCookOff = false,
193
208
  cookOffCount = 0,
194
209
  cookOffPower = 0,
@@ -204,7 +219,7 @@ flamesize:
204
219
  ["ATMZ-5"] = {
205
220
  cargoExplosion = true,
206
221
  cargoExplosionMult = 2.0,
207
- cargoExplosionPower = 200,
222
+ cargoExplosionPower = 50,
208
223
  cargoCookOff = false,
209
224
  cookOffCount = 0,
210
225
  cookOffPower = 0,
@@ -220,7 +235,7 @@ flamesize:
220
235
  ["ATZ-10"] = {
221
236
  cargoExplosion = true,
222
237
  cargoExplosionMult = 2,
223
- cargoExplosionPower = 200,
238
+ cargoExplosionPower = 50,
224
239
  cargoCookOff = false,
225
240
  cookOffCount = 0,
226
241
  cookOffPower = 0,
@@ -236,7 +251,7 @@ flamesize:
236
251
  ["ATZ-5"] = {
237
252
  cargoExplosion = true,
238
253
  cargoExplosionMult = 1.8,
239
- cargoExplosionPower = 200,
254
+ cargoExplosionPower = 50,
240
255
  cargoCookOff = false,
241
256
  cookOffCount = 0,
242
257
  cookOffPower = 0,
@@ -252,7 +267,7 @@ flamesize:
252
267
  ["M978 HEMTT Tanker"] = {
253
268
  cargoExplosion = true,
254
269
  cargoExplosionMult = 2.0,
255
- cargoExplosionPower = 200,
270
+ cargoExplosionPower = 50,
256
271
  cargoCookOff = false,
257
272
  cookOffCount = 0,
258
273
  cookOffPower = 0,
@@ -268,7 +283,7 @@ flamesize:
268
283
  ["GAZ-66"] = {
269
284
  cargoExplosion = true,
270
285
  cargoExplosionMult = 1,
271
- cargoExplosionPower = 200,
286
+ cargoExplosionPower = 50,
272
287
  cargoCookOff = true,
273
288
  cookOffCount = 4,
274
289
  cookOffPower = 1,
@@ -280,10 +295,11 @@ flamesize:
280
295
  flameDuration = 30,
281
296
  },
282
297
  --#Technically this is both ammo and fuel looking at the model
283
- ["Ural-4320"] = {
298
+ --#Called Ural-4320 in game, but in code its Ural-375
299
+ ["Ural-375"] = {
284
300
  cargoExplosion = true,
285
301
  cargoExplosionMult = 1,
286
- cargoExplosionPower = 200,
302
+ cargoExplosionPower = 50,
287
303
  cargoCookOff = true,
288
304
  cookOffCount = 4,
289
305
  cookOffPower = 1,
@@ -298,7 +314,7 @@ flamesize:
298
314
  ["ZIL-135"] = {
299
315
  cargoExplosion = true,
300
316
  cargoExplosionMult = 1,
301
- cargoExplosionPower = 200,
317
+ cargoExplosionPower = 50,
302
318
  cargoCookOff = true,
303
319
  cookOffCount = 5,
304
320
  cookOffPower = 1,
@@ -309,9 +325,118 @@ flamesize:
309
325
  flameSize = 1,
310
326
  flameDuration = 30,
311
327
  },
328
+
329
+ --#Ammo Boxes etc
330
+
331
+ --#Long ammo box
332
+
333
+ ["Cargo06"] = {
334
+ cargoExplosion = true,
335
+ cargoExplosionMult = 1,
336
+ cargoExplosionPower = 50,
337
+ cargoCookOff = true,
338
+ cookOffCount = 5,
339
+ cookOffPower = 1,
340
+ cookOffDuration = 10,
341
+ cookOffRandomTiming = true,
342
+ cookOffPowerRandom = 50,
343
+ isTanker = false,
344
+ flameSize = 1,
345
+ flameDuration = 30,
346
+ },
347
+
348
+ --#ammo boxes
349
+
350
+ ["Cargo03"] = {
351
+ cargoExplosion = true,
352
+ cargoExplosionMult = 1,
353
+ cargoExplosionPower = 10,
354
+ cargoCookOff = true,
355
+ cookOffCount = 10,
356
+ cookOffPower = 1,
357
+ cookOffDuration = 20,
358
+ cookOffRandomTiming = true,
359
+ cookOffPowerRandom = 0,
360
+ isTanker = false,
361
+ flameSize = 1,
362
+ flameDuration = 30,
363
+ },
364
+
365
+ --FuelBarrels
366
+
367
+ ["Cargo05"] = {
368
+ cargoExplosion = true,
369
+ cargoExplosionMult = 1,
370
+ cargoExplosionPower = 50,
371
+ cargoCookOff = false,
372
+ cookOffCount = 5,
373
+ cookOffPower = 1,
374
+ cookOffDuration = 10,
375
+ cookOffRandomTiming = true,
376
+ cookOffPowerRandom = 50,
377
+ isTanker = true,
378
+ flameSize = 2,
379
+ flameDuration = 30,
380
+ },
381
+
382
+ --APFC fuel
383
+
384
+ ["APFC fuel"] = {
385
+ cargoExplosion = true,
386
+ cargoExplosionMult = 1,
387
+ cargoExplosionPower = 50,
388
+ cargoCookOff = false,
389
+ cookOffCount = 5,
390
+ cookOffPower = 1,
391
+ cookOffDuration = 10,
392
+ cookOffRandomTiming = true,
393
+ cookOffPowerRandom = 50,
394
+ isTanker = true,
395
+ flameSize = 2,
396
+ flameDuration = 30,
397
+ },
398
+
399
+ --Oil Barrel
400
+
401
+ ["Oil Barrel"] = {
402
+ cargoExplosion = true,
403
+ cargoExplosionMult = 1,
404
+ cargoExplosionPower = 50,
405
+ cargoCookOff = false,
406
+ cookOffCount = 5,
407
+ cookOffPower = 1,
408
+ cookOffDuration = 10,
409
+ cookOffRandomTiming = true,
410
+ cookOffPowerRandom = 50,
411
+ isTanker = true,
412
+ flameSize = 1,
413
+ flameDuration = 20,
414
+ },
415
+
416
+
417
+ --FARP Ammo Dump Coating
418
+
419
+ ["FARP Ammo Dump Coating"] = {
420
+ cargoExplosion = true,
421
+ cargoExplosionMult = 1,
422
+ cargoExplosionPower = 50,
423
+ cargoCookOff = true,
424
+ cookOffCount = 5,
425
+ cookOffPower = 1,
426
+ cookOffDuration = 20,
427
+ cookOffRandomTiming = true,
428
+ cookOffPowerRandom = 50,
429
+ isTanker = false,
430
+ flameSize = 1,
431
+ flameDuration = 20,
432
+ },
312
433
  }
434
+ --[[
435
+
313
436
 
314
- --Weapon Explosive Table
437
+ -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-=-
438
+ Weapon Explosive Table
439
+ -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-]]
315
440
  explTable = {
316
441
  --*** WWII BOMBS ***
317
442
  ["British_GP_250LB_Bomb_Mk1"] = { explosive = 100, shaped_charge = false },
@@ -409,11 +534,11 @@ explTable = {
409
534
 
410
535
  --*** CLUSTER BOMBS (CBU) ***
411
536
  --I don't have most of these so can't test them with debug on
537
+ ["MK77mod0-WPN"] = { explosive = 0, shaped_charge = false, cluster = false, submunition_count = 132, submunition_explosive = 0.1, submunition_name = "BLU_1B" }, --napalm skyhawk, have set to cluster (false) for napalm purposes
538
+ ["MK77mod1-WPN"] = { explosive = 0, shaped_charge = false, cluster = false, submunition_count = 132, submunition_explosive = 0.1, submunition_name = "BLU_1B" }, --napalm skyhawk, have set to cluster (false) for napalm purposes
412
539
  ["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
540
  ["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
541
  ["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
542
  ["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
543
  ["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
544
  ["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
@@ -427,8 +552,8 @@ explTable = {
427
552
  ["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
553
  ["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
554
  ["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" },
555
+ ["RBK_500_255_PTO_1M"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 126, submunition_explosive = 0.5, submunition_name = "PTO_1M" },
556
+ ["RBK_500_255_ShO"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 565, submunition_explosive = 0.1, submunition_name = "ShO" },
432
557
  --*** INS/GPS BOMBS (JDAM) ***
433
558
  ["GBU_31"] = { explosive = 582, shaped_charge = false },
434
559
  ["GBU_31_V_3B"] = { explosive = 582, shaped_charge = false },
@@ -452,6 +577,7 @@ explTable = {
452
577
  ["GB-6"] = { explosive = 0, shaped_charge = false },
453
578
  ["GB-6-HE"] = { explosive = 0, shaped_charge = false },
454
579
  ["GB-6-SFW"] = { explosive = 0, shaped_charge = false },
580
+ ["X_65"] = { explosive = 100, shaped_charge = false },
455
581
 
456
582
  --*** AIR GROUND MISSILE (AGM) ***
457
583
  ["AGM_62"] = { explosive = 400, shaped_charge = false },
@@ -493,26 +619,34 @@ explTable = {
493
619
  ["X_29L"] = { explosive = 320, shaped_charge = false },
494
620
  ["X_29T"] = { explosive = 320, shaped_charge = false },
495
621
  ["X_29TE"] = { explosive = 320, shaped_charge = false },
496
-
622
+
623
+ ["AKD-10"] = { explosive = 10, shaped_charge = false }, --drone
624
+
497
625
  --*** 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 },
626
+ ["AGM_88C"] = { explosive = 69, shaped_charge = false },
627
+ ["AGM_88"] = { explosive = 69, shaped_charge = false },
628
+ ["AGM_122"] = { explosive = 12, shaped_charge = false },
629
+ ["LD-10"] = { explosive = 75, shaped_charge = false },
630
+ ["AGM_45A"] = { explosive = 66, shaped_charge = false },
631
+ ["AGM_45B"] = { explosive = 66, shaped_charge = false },
632
+ ["X_58"] = { explosive = 149, shaped_charge = false },
633
+ ["X_25MP"] = { explosive = 90, shaped_charge = false },
634
+ ["X_31P"] = { explosive = 90, shaped_charge = false },
505
635
 
506
636
  --*** ANTI-SHIP MISSILE (ASh) ***
507
637
  ["AGM_84D"] = { explosive = 488, shaped_charge = false },
508
638
  ["Rb 15F"] = { explosive = 500, shaped_charge = false },
509
639
  ["C-802AK"] = { explosive = 500, shaped_charge = false },
510
-
640
+ ["X_31A"] = { explosive = 89, shaped_charge = false }, --KH-31A ASh
641
+ ["X_22"] = { explosive = 1200, shaped_charge = false }, --Ash 1ton RDX = 1600KG TNT
642
+ ["X_35"] = { explosive = 145, shaped_charge = true }, --ASh 145KG
643
+
511
644
  --*** 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 },
645
+ ["CM-802AKG"] = { explosive = 240, shaped_charge = false },
646
+ ["AGM_84E"] = { explosive = 360, shaped_charge = false },
647
+ ["AGM_84H"] = { explosive = 380, shaped_charge = false },
648
+ ["X_59M"] = { explosive = 340, shaped_charge = false },
649
+ ["X_65"] = { explosive = 545, shaped_charge = false },
516
650
 
517
651
  --*** ROCKETS ***
518
652
  ["HYDRA_70M15"] = { explosive = 5, shaped_charge = false },
@@ -559,11 +693,11 @@ explTable = {
559
693
  ["S-25O"] = { explosive = 150, shaped_charge = false },
560
694
  ["S-25-O"] = { explosive = 150, shaped_charge = false },
561
695
  ["S_25L"] = { explosive = 190, shaped_charge = false },
562
- ["S-5M"] = { explosive = 1, shaped_charge = false },
696
+ ["S-5M"] = { explosive = 3, shaped_charge = false },
563
697
  ["C_5"] = { explosive = 8, shaped_charge = false },
564
698
  ["C5"] = { explosive = 5, shaped_charge = false },
565
- ["C_8"] = { explosive = 4, shaped_charge = false },
566
- ["C_8OFP2"] = { explosive = 3, shaped_charge = false },
699
+ ["C_8"] = { explosive = 5, shaped_charge = false },
700
+ ["C_8OFP2"] = { explosive = 5, shaped_charge = false },
567
701
  ["C_13"] = { explosive = 21, shaped_charge = false },
568
702
  ["C_24"] = { explosive = 123, shaped_charge = false },
569
703
  ["C_25"] = { explosive = 151, shaped_charge = false },
@@ -574,21 +708,107 @@ explTable = {
574
708
  ["AGR_20_M282"] = { explosive = 8, shaped_charge = false },
575
709
  ["Hydra_70_M282_MPP"] = { explosive = 5, shaped_charge = true },
576
710
  ["BRM-1_90MM"] = { explosive = 8, shaped_charge = false },
577
- }
578
-
579
-
580
-
581
711
 
712
+ --*** JF17 weapons changes as per Kurdes ***
713
+ ["C_701T"] = { explosive = 38, shaped_charge = false },
714
+ ["C_701IR"] = { explosive = 38, shaped_charge = false },
715
+ ["LS_6_100"] = { explosive = 45, shaped_charge = false },
716
+ ["LS_6"] = { explosive = 100, shaped_charge = false },
717
+ ["LS_6_500"] = { explosive = 274, shaped_charge = false },
718
+
719
+ --==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--
720
+ --*** Vehicle/Ship based ***--
721
+ --==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--
722
+
723
+ --*** Rocketry ***
724
+ ["9M22U"] = { explosive = 25, shaped_charge = false, groundordnance = true }, --122mm HE rocket, BM-21 Grad (~20-30 kg TNT equiv)
725
+ ["GRAD_9M22U"] = { explosive = 25, shaped_charge = false, groundordnance = true }, --122mm HE rocket, BM-21 Grad (~20-30 kg TNT equiv)
726
+ -- ["M26"] = { explosive = 0, shaped_charge = false, groundordnance = true}, --227mm cluster rocket, M270 MLRS (adjusted for cluster)
727
+ ["M26"] = { explosive = 0, shaped_charge = false, cluster = true, submunition_count = 644, submunition_explosive = 0.1, submunition_name = "M77", groundordnance = true }, --227mm cluster rocket, M270 MLRS (adjusted for cluster)
728
+ ["SCUD_RAKETA"] = { explosive = 985, shaped_charge = false, groundordnance = true },
729
+ ["SMERCH_9M55F"] = { explosive = 46, shaped_charge = false, groundordnance = true }, --220mm HE rocket, (~25-45 kg TNT equiv)
730
+
731
+ ["TOW2"] = { explosive = 6.5, shaped_charge = true, groundordnance = true }, --ATGM
732
+
733
+ --*** Shells ***
734
+ ["weapons.shells.M_105mm_HE"] = { explosive = 12, shaped_charge = false, groundordnance = true }, --105mm HE shell, M119/M102 (~10-15 kg TNT equiv)
735
+ ["weapons.shells.M_155mm_HE"] = { explosive = 60, shaped_charge = false, groundordnance = true }, --155mm HE shell, M777/M109 (~50-70 kg TNT equiv)
736
+ ["weapons.shells.2A60_120"] = { explosive = 18, shaped_charge = false, groundordnance = true }, --120mm HE shell, 2B11 mortar (~15-20 kg TNT equiv)
737
+ ["weapons.shells.2A18_122"] = { explosive = 22, shaped_charge = false, groundordnance = true }, --122mm HE shell, D-30 (~20-25 kg TNT equiv)
738
+ ["weapons.shells.2A33_152"] = { explosive = 50, shaped_charge = false, groundordnance = true }, --152mm HE shell, SAU Akatsia (~40-60 kg TNT equiv)
739
+ ["weapons.shells.PLZ_155_HE"] = { explosive = 60, shaped_charge = false, groundordnance = true }, --155mm HE shell, PLZ05 (~50-70 kg TNT equiv)
740
+ ["weapons.shells.M185_155"] = { explosive = 60, shaped_charge = false, groundordnance = true }, --155mm HE shell, M109 (~50-70 kg TNT equiv)
741
+ ["weapons.shells.2A64_152"] = { explosive = 50, shaped_charge = false, groundordnance = true }, --152mm HE shell, SAU Msta (~40-60 kg TNT equiv)
742
+
743
+ ["weapons.shells.2A46M_125_HE"] = { explosive = 5, shaped_charge = false, groundordnance = true }, --125mm HE shell, T-90 (~5-6 kg TNT equiv)
744
+ ["weapons.shells.HESH_105"] = { explosive = 6, shaped_charge = false, groundordnance = true }, --105mm HESH shell, M1128 Stryker (~4-6 kg TNT equiv)
745
+
746
+ ---*** Naval ***
747
+ ["weapons.missiles.AGM_84S"] = { explosive = 225, shaped_charge = false, groundordnance = true }, --Harpoon missile, Ticonderoga (~200-250 kg TNT equiv)
748
+ ["weapons.missiles.P_500"] = { explosive = 500, shaped_charge = false, groundordnance = true }, --P-500 Bazalt missile, Moscow (~450-550 kg TNT equiv)
749
+
750
+ ["weapons.shells.AK176_76"] = { explosive = 1, shaped_charge = false, groundordnance = true }, --76mm HE shell, AK-176 (~0.7-1 kg TNT equiv)
751
+ ["weapons.shells.A222_130"] = { explosive = 5, shaped_charge = false, groundordnance = true }, --130mm HE shell, A-222 Bereg (~4-5 kg TNT equiv)
752
+ ["weapons.shells.53-UBR-281U"] = { explosive = 5, shaped_charge = false, groundordnance = true }, --130mm HE shell, SM-2-1 (~4-5 kg TNT equiv)
753
+ ["weapons.shells.PJ87_100_PFHE"] = { explosive = 3, shaped_charge = false, groundordnance = true }, --100mm HE-PF shell, Type 052B (~2.4-3.4 kg TNT equiv)
754
+ ["weapons.shells.AK100_100"] = { explosive = 3, shaped_charge = false, groundordnance = true }, --100mm HE shell, AK-100 (~2.5-3.5 kg TNT equiv) AK-100 100mm (e.g., on Project 1135 Krivak-class)
755
+ ["weapons.shells.AK130_130"] = { explosive = 5, shaped_charge = false, groundordnance = true }, --130mm HE shell, AK-130 (~4-5 kg TNT equiv) AK-130 130mm (e.g., on Project 956 Sovremenny-class)
756
+ ["weapons.shells.2A70_100"] = { explosive = 3, shaped_charge = false, groundordnance = true }, --100mm HE shell, 2A70 (~3-3.5 kg TNT equiv) 2A70 100mm (e.g., on Project 775 Ropucha-class)
757
+ ["weapons.shells.OTO_76"] = { explosive = 1, shaped_charge = false, groundordnance = true }, --76mm HE shell, OTO Melara (~0.8-1.1 kg TNT equiv) OTO Melara 76mm (e.g., on NATO frigates like Oliver Hazard Perry-class)
758
+ ["weapons.shells.MK45_127"] = { explosive = 5, shaped_charge = false, groundordnance = true }, --127mm HE shell, Mark 45 (~4.8-5.6 kg TNT equiv) Mark 45 127mm (e.g., on Arleigh Burke-class destroyers)
759
+ ["weapons.shells.PJ26_76_PFHE"] = { explosive = 1, shaped_charge = false, groundordnance = true }, --76mm HE-PF shell, PJ-26 (~0.8-1.1 kg TNT equiv)
760
+ ["weapons.shells.53-UOR-281U"] = { explosive = 5, shaped_charge = false, groundordnance = true }, --130mm HE shell, SM-2-1 (~4-5 kg TNT equiv)
761
+ ["weapons.shells.MK75_76"] = { explosive = 1, shaped_charge = false, groundordnance = true }, --76mm HE shell, Mk 75 (~0.8-1.1 kg TNT equiv)
762
+
763
+ --*** Bismark Mod Weapon ***
764
+ ["weapons.shells.Breda_37_HE"] = { explosive = 70, shaped_charge = false, groundordnance = true }, --380mm HE shell, 38 cm SK C/34 (~60-75 kg TNT equiv)
765
+ --*** Bismark Mod Weapons ***
766
+ ["weapons.shells.380mm_HE"] = { explosive = 70, shaped_charge = false, groundordnance = true }, --380mm HE shell, 38 cm SK C/34 (~60-75 kg TNT equiv)
767
+ ["weapons.shells.SK_C_33_105_HE"] = { explosive = 15, shaped_charge = false, groundordnance = true }, --105mm HE shell, SK C/33 (~12-16 kg TNT equiv)
768
+
769
+
770
+
771
+
772
+ }
582
773
 
774
+ napalm_unitcat_tabl = {
775
+ ["Infantry"] = { maxDamageDistance = 50, explosionPower = 0.5 },
776
+ ["Tank"] = { maxDamageDistance = 30, explosionPower = 5 },
777
+ ["Artillery"] = { maxDamageDistance = 40, explosionPower = 5 },
778
+ ["Armored Vehicle"] = { maxDamageDistance = 35, explosionPower = 5 },
779
+ ["Anti-Air"] = { maxDamageDistance = 35, explosionPower = 5 },
780
+ ["Helicopter"] = { maxDamageDistance = 45, explosionPower = 5 },
781
+ ["Airplane"] = { maxDamageDistance = 40, explosionPower = 5 },
782
+ ["Structure"] = { maxDamageDistance = 60, explosionPower = 60 }
783
+ }
583
784
 
584
785
  local effectSmokeId = 1
585
786
 
586
787
  ----[[ ##### HELPER/UTILITY FUNCTIONS ##### ]]----
587
788
 
588
- local function tableHasKey(table, key)
589
- return table[key] ~= nil
789
+ --Global tables
790
+ local processedUnitIds = {}
791
+ local killfeedTable = {}
792
+ local splashKillfeedTable = {}
793
+ local splashKillfeedTemp = {}
794
+ local LogEventProcessedUnitTable = {}
795
+
796
+ --Function to safely get data with pcall
797
+ local function safeGet(func, default)
798
+ local success, result = pcall(func)
799
+ return success and result or default
590
800
  end
591
-
801
+
802
+ --Function to clear processed unit IDs after a delay
803
+ function clearProcessedUnitIds(unitId)
804
+ if processedUnitIds[unitId] then
805
+ processedUnitIds[unitId] = nil
806
+ if splash_damage_options.napalm_unitdamage_debug then
807
+ env.info("scanUnitsForNapalm: Cleared unit ID " .. unitId .. " from processedUnitIds")
808
+ end
809
+ end
810
+ end
811
+
592
812
  local function debugMsg(str)
593
813
  if splash_damage_options.debug == true then
594
814
  debugCounter = (debugCounter or 0) + 1
@@ -597,29 +817,303 @@ local function debugMsg(str)
597
817
  env.info("DEBUG: " .. uniqueStr)
598
818
  end
599
819
  end
600
-
601
- local function gameMsg(str)
602
- if splash_damage_options.game_messages == true then
603
- trigger.action.outText(str, 5)
820
+
821
+ function napalm_phosphor(vec3)
822
+ local baseFlareCount = math.random(0, 8) -- Wider range for variation
823
+ local randomFactor = math.random(0.1, 1) -- Random scaling per call
824
+ local scaledFlareCount = math.max(1, math.floor(baseFlareCount * splash_damage_options.napalm_phosphor_multiplier * randomFactor))
825
+ for i = 1, scaledFlareCount do
826
+ local randomAzimuth = math.random(0, 359) -- Random angle for scatter
827
+ local offsetX = math.random(-15, 15) -- Position offset (meters)
828
+ local offsetZ = math.random(-15, 15)
829
+ local flarePos = { x = vec3.x + offsetX, y = vec3.y, z = vec3.z + offsetZ }
830
+ trigger.action.signalFlare(flarePos, 2, randomAzimuth)
831
+ end
832
+ if splash_damage_options.debug then
833
+ debugMsg("Triggered " .. scaledFlareCount .. " napalm phosphor flares at X: " .. string.format("%.0f", vec3.x) .. ", Z: " .. string.format("%.0f", vec3.z))
604
834
  end
605
835
  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
836
+
837
+ --getSpreadPoints function
838
+ local function getSpreadPoints(impactPoint, velocity, numPoints, spacing)
839
+ local points = {}
840
+ local mag = math.sqrt(velocity.x^2 + velocity.z^2)
841
+ if mag == 0 then
842
+ table.insert(points, {x = impactPoint.x, y = land.getHeight({x = impactPoint.x, y = impactPoint.z}), z = impactPoint.z})
843
+ return points
844
+ end
845
+ local dir = {x = velocity.x / mag, z = velocity.z / mag}
846
+ local perpDir = {x = -dir.z, z = dir.x} --Perpendicular to velocity direction
847
+ local prevHeight = land.getHeight({x = impactPoint.x, y = impactPoint.z})
848
+ for i = 1, numPoints do
849
+ local offset = (i - 1) * spacing
850
+ if splash_damage_options.napalm_doublewide_enabled then
851
+ --Double-wide: two points with ±15m lateral offset
852
+ local point1 = {
853
+ x = impactPoint.x + dir.x * offset + perpDir.x * splash_damage_options.napalm_doublewide_spread,
854
+ z = impactPoint.z + dir.z * offset + perpDir.z * splash_damage_options.napalm_doublewide_spread
855
+ }
856
+ local terrainHeight1 = land.getHeight({x = point1.x, y = point1.z})
857
+ local heightDiff1 = terrainHeight1 - prevHeight
858
+ point1.y = prevHeight + math.max(math.min(heightDiff1, 30), -30)
859
+ table.insert(points, point1)
860
+ local point2 = {
861
+ x = impactPoint.x + dir.x * offset - perpDir.x * splash_damage_options.napalm_doublewide_spread,
862
+ z = impactPoint.z + dir.z * offset - perpDir.z * splash_damage_options.napalm_doublewide_spread
863
+ }
864
+ local terrainHeight2 = land.getHeight({x = point2.x, y = point2.z})
865
+ local heightDiff2 = terrainHeight2 - prevHeight
866
+ point2.y = prevHeight + math.max(math.min(heightDiff2, 30), -30)
867
+ table.insert(points, point2)
868
+ prevHeight = (terrainHeight1 + terrainHeight2) / 2
869
+ else
870
+ --Single point, linear spread
871
+ local point = {
872
+ x = impactPoint.x + dir.x * offset,
873
+ z = impactPoint.z + dir.z * offset
874
+ }
875
+ local terrainHeight = land.getHeight({x = point.x, y = point.z})
876
+ local heightDiff = terrainHeight - prevHeight
877
+ point.y = prevHeight + math.max(math.min(heightDiff, 30), -30)
878
+ table.insert(points, point)
879
+ prevHeight = terrainHeight
880
+ end
881
+ end
882
+ return points
883
+ end
884
+
885
+
886
+ function V3Mag(speedVec)
887
+ local mag = speedVec.x*speedVec.x + speedVec.y*speedVec.y+speedVec.z*speedVec.z
888
+ mag = math.sqrt(mag)
889
+ return mag
618
890
  end
619
891
 
620
- local function getDistance3D(point1, point2)
621
- local x1 = point1.x
622
- local y1 = point1.y
892
+ function Vhead(speedVec)
893
+ local speed = V3Mag(speedVec)
894
+ local dist = speed * refreshRate * 1.5
895
+ return dist
896
+ end
897
+
898
+ function explodeNapalm(vec3)
899
+ local explosionPos = {
900
+ x = vec3.x,
901
+ y = vec3.y + 1.6, --Add 1.6m to the ground height
902
+ z = vec3.z
903
+ }
904
+ trigger.action.explosion(explosionPos, 10)
905
+ end
906
+
907
+ -- Helper function to calculate 2D distance
908
+ local function getDistance(point1, point2)
909
+ local dX = math.abs(point1.x - point2.x)
910
+ local dZ = math.abs(point1.z - point2.z)
911
+ return math.sqrt(dX * dX + dZ * dZ)
912
+ end
913
+
914
+ --Scan for units around the napalm explosions and apply damage if required
915
+ function scanUnitsForNapalm(posX, posY, posZ)
916
+ if not splash_damage_options.napalm_unitdamage_enable then
917
+ if splash_damage_options.napalm_unitdamage_debug then
918
+ env.info("scanUnitsForNapalm: Napalm unit damage disabled, skipping scan")
919
+ end
920
+ return
921
+ end
922
+
923
+ if splash_damage_options.napalm_unitdamage_debug then
924
+ env.info("scanUnitsForNapalm: Starting scan at (X: " .. posX .. ", Y: " .. posY .. ", Z: " .. posZ .. ") with radius " .. splash_damage_options.napalm_unitdamage_scandistance)
925
+ end
926
+
927
+ local volS = {
928
+ id = world.VolumeType.SPHERE,
929
+ params = {
930
+ point = {x = posX, y = posY, z = posZ},
931
+ radius = splash_damage_options.napalm_unitdamage_scandistance
932
+ }
933
+ }
934
+
935
+ local foundUnits = {}
936
+ local status, err = pcall(function()
937
+ -- Scan for units
938
+ world.searchObjects(Object.Category.UNIT, volS, function(foundObject)
939
+ local success, result = pcall(function()
940
+ if foundObject:isExist() and foundObject:getCategory() == Object.Category.UNIT then
941
+ local unitType = foundObject:getTypeName() or "Unknown"
942
+ -- Exclude Fuel tank
943
+ if unitType ~= "Fuel tank" then
944
+ local unitPos = foundObject:getPoint()
945
+ local distance = getDistance({x = posX, y = posY, z = posZ}, unitPos)
946
+ if distance <= splash_damage_options.napalm_unitdamage_scandistance then
947
+ local category = "Unknown"
948
+ local desc = foundObject:getDesc()
949
+ if desc and foundObject:hasAttribute("Infantry") then
950
+ category = "Infantry"
951
+ elseif desc and foundObject:hasAttribute("Tanks") then
952
+ category = "Tank"
953
+ elseif desc and foundObject:hasAttribute("Artillery") then
954
+ category = "Artillery"
955
+ elseif desc and foundObject:hasAttribute("Armored vehicles") then
956
+ category = "Armored Vehicle"
957
+ elseif desc and foundObject:hasAttribute("AA") then
958
+ category = "Anti-Air"
959
+ elseif desc and foundObject:hasAttribute("Helicopters") then
960
+ category = "Helicopter"
961
+ elseif desc and foundObject:hasAttribute("Planes") then
962
+ category = "Airplane"
963
+ end
964
+ table.insert(foundUnits, {
965
+ unit = foundObject,
966
+ id = foundObject:getID(),
967
+ type = unitType,
968
+ distance = distance,
969
+ category = category,
970
+ position = unitPos
971
+ })
972
+ end
973
+ end
974
+ end
975
+ end)
976
+ if not success and splash_damage_options.napalm_unitdamage_debug then
977
+ env.info("scanUnitsForNapalm: Error processing unit ID " .. (foundObject:getID() or "unknown") .. ": " .. tostring(result))
978
+ end
979
+ return true
980
+ end)
981
+ -- Scan for static objects
982
+ world.searchObjects(Object.Category.STATIC, volS, function(foundObject)
983
+ local success, result = pcall(function()
984
+ if foundObject:isExist() and foundObject:getCategory() == Object.Category.STATIC then
985
+ local unitType = foundObject:getTypeName() or "Unknown"
986
+ -- Exclude Fuel tank
987
+ if unitType ~= "Fuel tank" then
988
+ local unitPos = foundObject:getPoint()
989
+ local distance = getDistance({x = posX, y = posY, z = posZ}, unitPos)
990
+ if distance <= splash_damage_options.napalm_unitdamage_scandistance then
991
+ table.insert(foundUnits, {
992
+ unit = foundObject,
993
+ id = foundObject:getID(),
994
+ type = unitType,
995
+ distance = distance,
996
+ category = "Structure",
997
+ position = unitPos
998
+ })
999
+ end
1000
+ end
1001
+ end
1002
+ end)
1003
+ if not success and splash_damage_options.napalm_unitdamage_debug then
1004
+ env.info("scanUnitsForNapalm: Error processing static object ID " .. (foundObject:getID() or "unknown") .. ": " .. tostring(result))
1005
+ end
1006
+ return true
1007
+ end)
1008
+ end)
1009
+
1010
+ if not status and splash_damage_options.napalm_unitdamage_debug then
1011
+ env.info("scanUnitsForNapalm: Error during scan: " .. tostring(err))
1012
+ return
1013
+ end
1014
+
1015
+ table.sort(foundUnits, function(a, b) return a.distance < b.distance end)
1016
+
1017
+ if splash_damage_options.napalm_unitdamage_debug then
1018
+ env.info("scanUnitsForNapalm: Scan completed, found " .. #foundUnits .. " objects within " .. splash_damage_options.napalm_unitdamage_scandistance .. " meters at position (X: " .. posX .. ", Y: " .. posY .. ", Z: " .. posZ .. ")")
1019
+ -- Log all found objects
1020
+ for _, unitData in ipairs(foundUnits) do
1021
+ env.info("scanUnitsForNapalm: Found object ID " .. tostring(unitData.id) .. " of type: " .. unitData.type .. ", Category: " .. unitData.category .. ", Distance: " .. string.format("%.2f", unitData.distance) .. " meters, Position: (X: " .. string.format("%.2f", unitData.position.x) .. ", Y: " .. string.format("%.2f", unitData.position.y) .. ", Z: " .. string.format("%.2f", unitData.position.z) .. ")")
1022
+ end
1023
+ end
1024
+
1025
+ if #foundUnits > 0 then
1026
+ local processedPositions = {} -- Track processed coordinates for this scan
1027
+ local explosionIndex = 0
1028
+ for _, unitData in ipairs(foundUnits) do
1029
+ if napalm_unitcat_tabl[unitData.category] and unitData.distance <= napalm_unitcat_tabl[unitData.category].maxDamageDistance then
1030
+ -- Check if unit ID has already been processed
1031
+ if not processedUnitIds[unitData.id] then
1032
+ -- Check for duplicate position (within 1 meter)
1033
+ local posKey = string.format("%.0f_%.0f_%.0f", unitData.position.x, unitData.position.y, unitData.position.z)
1034
+ if not processedPositions[posKey] then
1035
+ -- Check if unit is still alive (for units) or exists (for statics)
1036
+ local isAlive = unitData.unit:isExist() and (unitData.category == "Structure" or unitData.unit:getLife() > 0)
1037
+ if isAlive then
1038
+ processedPositions[posKey] = true
1039
+ processedUnitIds[unitData.id] = true
1040
+ local power = napalm_unitcat_tabl[unitData.category].explosionPower
1041
+ -- Calculate delay
1042
+ local delay = splash_damage_options.napalm_unitdamage_startdelay
1043
+ if splash_damage_options.napalm_unitdamage_startdelay > 0 then
1044
+ delay = delay + (explosionIndex * splash_damage_options.napalm_unitdamage_spreaddelay)
1045
+ explosionIndex = explosionIndex + 1
1046
+ end
1047
+ -- Adjust position for infantry to reduce ground interaction
1048
+ local explosionPos = unitData.position
1049
+ if unitData.category == "Infantry" then
1050
+ explosionPos = {
1051
+ x = unitData.position.x,
1052
+ y = land.getHeight({x = unitData.position.x, y = unitData.position.z}) + 1.6,
1053
+ z = unitData.position.z
1054
+ }
1055
+ end
1056
+ if splash_damage_options.napalm_unitdamage_debug then
1057
+ env.info("scanUnitsForNapalm: Scheduling explosion on unit ID " .. tostring(unitData.id) .. " (" .. unitData.type .. ") at (X: " .. string.format("%.2f", explosionPos.x) .. ", Z: " .. string.format("%.2f", explosionPos.z) .. ") with power " .. power .. " after " .. string.format("%.2f", delay) .. "s")
1058
+ end
1059
+ timer.scheduleFunction(function(params)
1060
+ trigger.action.explosion(params.position, params.power)
1061
+ end, {position = explosionPos, power = power}, timer.getTime() + delay)
1062
+ -- Schedule cleanup for this unit ID 20 seconds after its explosion
1063
+ timer.scheduleFunction(clearProcessedUnitIds, unitData.id, timer.getTime() + delay + 20)
1064
+ elseif splash_damage_options.napalm_unitdamage_debug then
1065
+ env.info("scanUnitsForNapalm: Skipped explosion for unit ID " .. tostring(unitData.id) .. " (" .. unitData.type .. ") at (X: " .. string.format("%.2f", unitData.position.x) .. ", Z: " .. string.format("%.2f", unitData.position.z) .. ") because unit is not alive (isExist: " .. tostring(unitData.unit:isExist()) .. ", life: " .. (unitData.category == "Structure" and "N/A" or tostring(unitData.unit:getLife())) .. ")")
1066
+ end
1067
+ elseif splash_damage_options.napalm_unitdamage_debug then
1068
+ env.info("scanUnitsForNapalm: Skipped explosion for unit ID " .. tostring(unitData.id) .. " (" .. unitData.type .. ") at (X: " .. string.format("%.2f", unitData.position.x) .. ", Z: " .. string.format("%.2f", unitData.position.z) .. ") due to duplicate position")
1069
+ end
1070
+ elseif splash_damage_options.napalm_unitdamage_debug then
1071
+ env.info("scanUnitsForNapalm: Skipped explosion for unit ID " .. tostring(unitData.id) .. " (" .. unitData.type .. ") at (X: " .. string.format("%.2f", unitData.position.x) .. ", Z: " .. string.format("%.2f", unitData.position.z) .. ") due to already processed unit ID")
1072
+ end
1073
+ end
1074
+ end
1075
+ else
1076
+ if splash_damage_options.napalm_unitdamage_debug then
1077
+ env.info("scanUnitsForNapalm: No objects found in scan area")
1078
+ end
1079
+ end
1080
+ end
1081
+
1082
+
1083
+
1084
+ function removeNapalm(staticName)
1085
+ StaticObject.getByName(staticName):destroy()
1086
+ end
1087
+
1088
+
1089
+
1090
+ local function tableHasKey(table, key)
1091
+ return table[key] ~= nil
1092
+ end
1093
+
1094
+
1095
+ local function gameMsg(str)
1096
+ if splash_damage_options.game_messages == true then
1097
+ trigger.action.outText(str, 5)
1098
+ end
1099
+ end
1100
+
1101
+ local function getDistance(point1, point2)
1102
+ local x1 = point1.x
1103
+ local y1 = point1.y
1104
+ local z1 = point1.z
1105
+ local x2 = point2.x
1106
+ local y2 = point2.y
1107
+ local z2 = point2.z
1108
+ local dX = math.abs(x1 - x2)
1109
+ local dZ = math.abs(z1 - z2)
1110
+ local distance = math.sqrt(dX * dX + dZ * dZ)
1111
+ return distance
1112
+ end
1113
+
1114
+ local function getDistance3D(point1, point2)
1115
+ local x1 = point1.x
1116
+ local y1 = point1.y
623
1117
  local z1 = point1.z
624
1118
  local x2 = point2.x
625
1119
  local y2 = point2.y
@@ -640,7 +1134,167 @@ local function lookahead(speedVec)
640
1134
  local dist = speed * refreshRate * 1.5
641
1135
  return dist
642
1136
  end
643
-
1137
+
1138
+ function napalmOnImpact(impactPoint, velocity, weaponName)
1139
+ if not (splash_damage_options.napalmoverride_enabled or (splash_damage_options.napalm_mk77_enabled and (weaponName == "MK77mod0-WPN" or weaponName == "MK77mod1-WPN"))) then return end
1140
+ --For MK77 cluster munitions, snap impact point to ground
1141
+ local finalImpactPoint = impactPoint
1142
+ if splash_damage_options.napalm_mk77_enabled and (weaponName == "MK77mod0-WPN" or weaponName == "MK77mod1-WPN") then
1143
+ local groundHeight = land.getHeight({x = impactPoint.x, y = impactPoint.z})
1144
+ finalImpactPoint = {
1145
+ x = impactPoint.x,
1146
+ y = groundHeight,
1147
+ z = impactPoint.z
1148
+ }
1149
+ if splash_damage_options.debug then
1150
+ debugMsg("Snapped MK77 " .. weaponName .. " impact to ground at X: " .. string.format("%.0f", finalImpactPoint.x) .. ", Z: " .. string.format("%.0f", finalImpactPoint.z))
1151
+ end
1152
+ else
1153
+ --For non-MK77, skip if more than 50m above ground
1154
+ local groundHeight = land.getHeight({x = impactPoint.x, y = impactPoint.z})
1155
+ if impactPoint.y - groundHeight > 50 then return end --Skip if more than 50m above ground
1156
+ end
1157
+
1158
+ --Adjust spread points for MK77mod0-WPN (30% more)
1159
+ local spreadPointsCount = splash_damage_options.napalm_spread_points
1160
+ if weaponName == "MK77mod0-WPN" then
1161
+ spreadPointsCount = math.floor(spreadPointsCount * 1.3 + 0.5) --30% more, rounded
1162
+ end
1163
+
1164
+ --Use horizontal velocity for MK77, full velocity for others
1165
+ local spreadVelocity = velocity
1166
+ if weaponName == "MK77mod0-WPN" or weaponName == "MK77mod1-WPN" then
1167
+ spreadVelocity = {x = velocity.x, z = velocity.z}
1168
+ end
1169
+ local spreadPoints = getSpreadPoints(finalImpactPoint, spreadVelocity, spreadPointsCount, splash_damage_options.napalm_spread_spacing)
1170
+ if splash_damage_options.debug then
1171
+ debugMsg("Generated " .. #spreadPoints .. " spread points for " .. weaponName .. " (expected " .. (splash_damage_options.napalm_doublewide_enabled and spreadPointsCount * 2 or spreadPointsCount) .. ")")
1172
+ for i, point in ipairs(spreadPoints) do
1173
+ debugMsg("Point " .. i .. ": X: " .. string.format("%.0f", point.x) .. ", Y: " .. string.format("%.0f", point.y) .. ", Z: " .. string.format("%.0f", point.z))
1174
+ end
1175
+ end
1176
+ local flamePositions = {} -- Track flame coordinates to avoid duplicates
1177
+ local function spawnAndExplode(pairIndex)
1178
+ if pairIndex > spreadPointsCount then return end
1179
+ local pointsToProcess = {}
1180
+ if splash_damage_options.napalm_doublewide_enabled then
1181
+ -- Process two points (pair) at indices 2*pairIndex-1 and 2*pairIndex
1182
+ local idx1 = 2 * pairIndex - 1
1183
+ local idx2 = 2 * pairIndex
1184
+ if idx1 <= #spreadPoints then
1185
+ table.insert(pointsToProcess, spreadPoints[idx1])
1186
+ end
1187
+ if idx2 <= #spreadPoints then
1188
+ table.insert(pointsToProcess, spreadPoints[idx2])
1189
+ end
1190
+ else
1191
+ -- Process single point at pairIndex
1192
+ if pairIndex <= #spreadPoints then
1193
+ table.insert(pointsToProcess, spreadPoints[pairIndex])
1194
+ end
1195
+ end
1196
+ for _, point in ipairs(pointsToProcess) do
1197
+ local napalmName = "napalmImpact" .. napalmCounter
1198
+ local currentCounter = napalmCounter
1199
+ napalmCounter = napalmCounter + 1
1200
+ local owngroupID = math.random(9999, 99999)
1201
+ local cvnunitID = math.random(9999, 99999)
1202
+ local _dataFuel = {
1203
+ ["groupId"] = owngroupID,
1204
+ ["category"] = "Fortifications",
1205
+ ["shape_name"] = "toplivo-bak",
1206
+ ["type"] = "Fuel tank",
1207
+ ["unitId"] = cvnunitID,
1208
+ ["rate"] = 100,
1209
+ ["y"] = point.z,
1210
+ ["x"] = point.x,
1211
+ ["name"] = napalmName,
1212
+ ["heading"] = 0,
1213
+ ["dead"] = false,
1214
+ ["hidden"] = true,
1215
+ }
1216
+ if splash_damage_options.debug then
1217
+ local staticCount = 0
1218
+ for _, coalitionId in pairs(coalition.side) do
1219
+ local statics = coalition.getStaticObjects(coalitionId)
1220
+ staticCount = staticCount + #statics
1221
+ end
1222
+ debugMsg("Spawning napalm object '" .. napalmName .. "' (Counter: " .. currentCounter .. ") at X: " .. string.format("%.0f", point.x) .. ", Y: " .. string.format("%.0f", point.y) .. ", Z: " .. string.format("%.0f", point.z) .. " (Active static objects: " .. staticCount .. ")")
1223
+ end
1224
+ local status, result = pcall(function()
1225
+ return coalition.addStaticObject(coalition.side.BLUE, _dataFuel)
1226
+ end)
1227
+ local spawnSuccess = status and result and StaticObject.getByName(napalmName) and StaticObject.getByName(napalmName):isExist()
1228
+ if not spawnSuccess then
1229
+ if splash_damage_options.debug then
1230
+ debugMsg("Failed to spawn napalm object '" .. napalmName .. "' at X: " .. string.format("%.0f", point.x) .. ", Y: " .. string.format("%.0f", point.y) .. ", Z: " .. string.format("%.0f", point.z) .. ": " .. (status and "Object not found or does not exist" or tostring(result)))
1231
+ end
1232
+ --Fallback: Trigger explosion without static object
1233
+ --timer.scheduleFunction(explodeNapalm, point, timer.getTime() + splash_damage_options.napalm_explode_delay)
1234
+ else
1235
+ timer.scheduleFunction(explodeNapalm, point, timer.getTime() + splash_damage_options.napalm_explode_delay)
1236
+ timer.scheduleFunction(function(name)
1237
+ if splash_damage_options.debug then
1238
+ debugMsg("Destroying napalm object '" .. name .. "' at X: " .. string.format("%.0f", point.x) .. ", Z: " .. string.format("%.0f", point.z))
1239
+ end
1240
+ removeNapalm(name)
1241
+ end, napalmName, timer.getTime() + splash_damage_options.napalm_destroy_delay)
1242
+ end
1243
+ if splash_damage_options.napalm_phosphor_enabled then
1244
+ timer.scheduleFunction(napalm_phosphor, point, timer.getTime() + splash_damage_options.napalm_explode_delay)
1245
+ local status, err = pcall(function()
1246
+ scanUnitsForNapalm(point.x, point.y, point.z)
1247
+ end)
1248
+ if not status then
1249
+ env.info("napalmOnImpact: Error during unit scan for point (X: " .. point.x .. ", Y: " .. point.y .. ", Z: " .. point.z .. "): " .. tostring(err))
1250
+ end
1251
+ end
1252
+ --Add flame effect if enabled
1253
+ if splash_damage_options.napalm_addflame then
1254
+ local flameSize = splash_damage_options.napalm_addflame_size
1255
+ local flameDuration = splash_damage_options.napalm_addflame_duration
1256
+ local flameDensity = 1.0
1257
+ local effectId = effectSmokeId
1258
+ effectSmokeId = effectSmokeId + 1
1259
+ local isDuplicate = false
1260
+ for _, pos in pairs(flamePositions) do
1261
+ if getDistance3D(point, pos) < 3 then
1262
+ isDuplicate = true
1263
+ if splash_damage_options.debug then
1264
+ debugMsg("Skipping duplicate flame for napalm object '" .. napalmName .. "' near X: " .. string.format("%.0f", pos.x) .. ", Z: " .. string.format("%.0f", pos.z))
1265
+ end
1266
+ break
1267
+ end
1268
+ end
1269
+ if not isDuplicate then
1270
+ if splash_damage_options.debug then
1271
+ debugMsg("Adding flame effect for napalm object '" .. napalmName .. "' at X: " .. string.format("%.0f", point.x) .. ", Z: " .. string.format("%.0f", point.z) .. " (Size: " .. flameSize .. ", Duration: " .. flameDuration .. "s, ID: " .. effectId .. ")")
1272
+ end
1273
+ timer.scheduleFunction(function(params)
1274
+ local terrainHeight = land.getHeight({x = params[1].x, y = params[1].z})
1275
+ local adjustedCoords = {x = params[1].x, y = terrainHeight + 2, z = params[1].z}
1276
+ trigger.action.effectSmokeBig(adjustedCoords, params[2], params[3], params[4])
1277
+ end, {point, flameSize, flameDensity, effectId}, timer.getTime() + splash_damage_options.napalm_flame_delay)
1278
+ timer.scheduleFunction(function(id)
1279
+ if splash_damage_options.debug then
1280
+ debugMsg("Stopping flame effect for napalm object (ID: " .. id .. ")")
1281
+ end
1282
+ trigger.action.effectSmokeStop(id)
1283
+ end, effectId, timer.getTime() + splash_damage_options.napalm_flame_delay + flameDuration)
1284
+ table.insert(flamePositions, point)
1285
+ end
1286
+ end
1287
+ end
1288
+ timer.scheduleFunction(spawnAndExplode, pairIndex + 1, timer.getTime() + 0.2)
1289
+ end
1290
+ spawnAndExplode(1)
1291
+ end
1292
+
1293
+
1294
+
1295
+
1296
+
1297
+
644
1298
  --Cluster-specific helper functions from Rockeye script
645
1299
  local function normalizeVector(vec)
646
1300
  local mag = math.sqrt(vec.x^2 + vec.z^2)
@@ -668,55 +1322,141 @@ local function calculate_dispersion(velocity, burst_altitude)
668
1322
  math.max(splash_damage_options.cluster_min_width, math.min(splash_damage_options.cluster_max_width, width_jitter))
669
1323
  end
670
1324
 
1325
+ local function protectedCall(...)
1326
+ local status, retval = pcall(...)
1327
+ if not status then
1328
+ env.warning("Splash damage script error... gracefully caught! " .. retval, true)
1329
+ end
1330
+ end
671
1331
 
672
-
673
-
674
-
675
-
676
-
677
-
678
-
679
- ----[[ ##### End of HELPER/UTILITY FUNCTIONS ##### ]]----
1332
+ --[[
1333
+ -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-=-
1334
+ ##### End of HELPER/UTILITY FUNCTIONS ##### ##### End of HELPER/UTILITY FUNCTIONS ##### ##### End of HELPER/UTILITY FUNCTIONS #####
1335
+ -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-]]
680
1336
  giantExplosionTargets = {}
1337
+ giantExplosionTestTargets = {}
681
1338
  cargoEffectsQueue = {}
682
1339
  WpnHandler = {}
683
1340
  tracked_target_position = nil --Store the last known position of TargetUnit for giant explosion
684
1341
  tracked_weapons = {}
685
1342
  local processedUnitsGlobal = {}
1343
+ napalmCounter = 1
1344
+ napalmCounter = 1
686
1345
 
687
1346
  function scanGiantExplosionTargets()
688
- giantExplosionTargets = {}
689
- local function findTargets(obj)
1347
+ local function processObject(obj)
690
1348
  if obj:isExist() then
691
1349
  local name = obj:getName()
692
1350
  if string.find(name, "GiantExplosionTarget") then
693
- local flagName = string.gsub(name, "Target", "")
694
- table.insert(giantExplosionTargets, {
1351
+ local pos = obj:getPoint()
1352
+ local targetData = {
695
1353
  name = name,
696
- flag = flagName,
697
1354
  obj = obj,
698
- pos = obj:getPoint(),
699
- static = splash_damage_options.giant_explosion_target_static
700
- })
1355
+ pos = pos,
1356
+ static = splash_damage_options.giant_explosion_target_static,
1357
+ initialHealth = obj:getLife() or 0
1358
+ }
1359
+ table.insert(giantExplosionTargets, targetData)
1360
+ if splash_damage_options.giantexplosion_testmode then
1361
+ table.insert(giantExplosionTestTargets, {name = name, pos = pos})
1362
+ end
1363
+ debugMsg("Found GiantExplosion unit: " .. name .. " at X:" .. pos.x .. " Y:" .. pos.y .. " Z:" .. pos.z)
1364
+ end
1365
+ end
1366
+ end
1367
+ --Iterate over all coalitions
1368
+ for coa = 0, 2 do
1369
+ --Process units
1370
+ local groups = coalition.getGroups(coa)
1371
+ if groups then
1372
+ for _, group in pairs(groups) do
1373
+ local units = group:getUnits()
1374
+ if units then
1375
+ for _, unit in pairs(units) do
1376
+ processObject(unit)
1377
+ end
1378
+ end
1379
+ end
1380
+ end
1381
+ --Process static objects
1382
+ local statics = coalition.getStaticObjects(coa)
1383
+ if statics then
1384
+ for _, static in pairs(statics) do
1385
+ processObject(static)
701
1386
  end
702
1387
  end
703
- return true
704
1388
  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)
1389
+ debugMsg("Total GiantExplosion units found: " .. #giantExplosionTargets)
1390
+ if #giantExplosionTargets > 0 then
1391
+ timer.scheduleFunction(checkGiantExplosionUnits, {}, timer.getTime() + splash_damage_options.giant_explosion_poll_rate)
708
1392
  end
709
1393
  end
710
1394
 
711
- function updateTargetPosition()
712
- for name, target in pairs(giantExplosionTargets) do
1395
+ function updateGiantExplosionPositions()
1396
+ for _, target in ipairs(giantExplosionTargets) do
713
1397
  if target.obj:isExist() then
714
- target.pos = target.obj:getPosition().p
1398
+ target.pos = target.obj:getPoint()
715
1399
  end
716
1400
  end
717
1401
  return timer.getTime() + 1.0
718
1402
  end
719
1403
 
1404
+ function checkGiantExplosionUnits()
1405
+ if not splash_damage_options.giant_explosion_enabled then
1406
+ debugMsg("Giant Explosion is disabled in options.")
1407
+ return
1408
+ end
1409
+
1410
+ local targetsToRemove = {}
1411
+ for i, target in ipairs(giantExplosionTargets) do
1412
+ local triggerExplosion = false
1413
+ local currentPos = target.pos
1414
+
1415
+ if target.obj:isExist() then
1416
+ if not target.static then
1417
+ currentPos = target.obj:getPoint()
1418
+ target.pos = currentPos
1419
+ end
1420
+ if splash_damage_options.giantexplosion_ondamage then
1421
+ local currentHealth = target.obj:getLife() or 0
1422
+ if currentHealth < target.initialHealth then
1423
+ triggerExplosion = true
1424
+ debugMsg("Triggering explosion for " .. target.name .. " due to damage (Health: " .. currentHealth .. "/" .. target.initialHealth .. ")")
1425
+ end
1426
+ end
1427
+ else
1428
+ if splash_damage_options.giantexplosion_ondeath then
1429
+ triggerExplosion = true
1430
+ debugMsg("Triggering explosion for " .. target.name .. " due to destruction")
1431
+ end
1432
+ end
1433
+
1434
+ if triggerExplosion then
1435
+ triggerGiantExplosion({
1436
+ pos = currentPos,
1437
+ power = splash_damage_options.giant_explosion_power,
1438
+ scale = splash_damage_options.giant_explosion_scale,
1439
+ duration = splash_damage_options.giant_explosion_duration,
1440
+ count = splash_damage_options.giant_explosion_count
1441
+ })
1442
+ table.insert(targetsToRemove, i)
1443
+ end
1444
+ end
1445
+
1446
+ --Remove triggered targets in reverse order to avoid index issues
1447
+ for i = #targetsToRemove, 1, -1 do
1448
+ table.remove(giantExplosionTargets, targetsToRemove[i])
1449
+ debugMsg("Removed " .. targetsToRemove[i] .. " from giantExplosionTargets. Remaining: " .. #giantExplosionTargets)
1450
+ end
1451
+
1452
+ --Continue scheduling checks if there are still targets
1453
+ if #giantExplosionTargets > 0 then
1454
+ return timer.getTime() + splash_damage_options.giant_explosion_poll_rate
1455
+ else
1456
+ debugMsg("No GiantExplosion units remaining. Disabling periodic checks.")
1457
+ end
1458
+ end
1459
+
720
1460
 
721
1461
  --Giant Explosion Function
722
1462
  function triggerGiantExplosion(params)
@@ -751,8 +1491,8 @@ function triggerGiantExplosion(params)
751
1491
  end, pos, timer.getTime() + delay)
752
1492
  end
753
1493
 
754
- -- Pre-explosion scan for cargo units
755
- local scanRadius = 1500 * sizeScale -- 1500m base radius, scaled by sizeScale
1494
+ --Pre-explosion scan for cargo units
1495
+ local scanRadius = 1500 * sizeScale --1500m base radius, scaled by sizeScale
756
1496
  local preExplosionTargets = {}
757
1497
  if splash_damage_options.enable_cargo_effects then
758
1498
  local volS = {
@@ -778,7 +1518,7 @@ function triggerGiantExplosion(params)
778
1518
  world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, volS, ifFound)
779
1519
  debugMsg("Pre-explosion scan for Giant Explosion: " .. #preExplosionTargets .. " targets found within " .. scanRadius .. "m")
780
1520
  end
781
- -- Trigger the explosion
1521
+ --Trigger the explosion
782
1522
  local maxRadius = 200 * sizeScale
783
1523
  local maxHeight = 500 * sizeScale
784
1524
  local adjustedExplosionCount = math.floor(explosionCount * (sizeScale ^ 2.5))
@@ -815,7 +1555,7 @@ function triggerGiantExplosion(params)
815
1555
 
816
1556
  gameMsg("Expanding giant fireball over " .. totalDuration .. "s (scale " .. sizeScale .. ")!")
817
1557
 
818
- -- Post-explosion scan and cargo cook-off queuing
1558
+ --Post-explosion scan and cargo cook-off queuing
819
1559
  if splash_damage_options.enable_cargo_effects then
820
1560
  timer.scheduleFunction(function(args)
821
1561
  local centerPos = args[1]
@@ -845,7 +1585,7 @@ function triggerGiantExplosion(params)
845
1585
  world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, volS, ifFound)
846
1586
  debugMsg("Post-explosion scan for Giant Explosion: " .. #postExplosionTargets .. " targets found within " .. radius .. "m")
847
1587
 
848
- -- Compare pre- and post-explosion targets
1588
+ --Compare pre- and post-explosion targets
849
1589
  for _, preTarget in ipairs(preTargets) do
850
1590
  local found = false
851
1591
  local postHealth = 0
@@ -883,16 +1623,16 @@ function triggerGiantExplosion(params)
883
1623
  end
884
1624
  end
885
1625
 
886
- -- Process queued cargo effects with prioritized flames
1626
+ --Process queued cargo effects with prioritized flames
887
1627
  if #cargoEffectsQueue > 0 then
888
- local flameIndex = 0 -- Separate index for flames
889
- local otherIndex = 0 -- Index for explosions, cook-offs, debris
1628
+ local flameIndex = 0 --Separate index for flames
1629
+ local otherIndex = 0 --Index for explosions, cook-offs, debris
890
1630
  local processedCargoUnits = {}
891
1631
  local flamePositions = {}
892
1632
  for _, effect in ipairs(cargoEffectsQueue) do
893
1633
  local unitKey = effect.name .. "_" .. effect.coords.x .. "_" .. effect.coords.z
894
1634
  if not processedUnitsGlobal[unitKey] and not processedCargoUnits[unitKey] then
895
- -- Handle tanker flames first with minimal delay
1635
+ --Handle tanker flames first with minimal delay
896
1636
  if effect.isTanker and effect.explosion then
897
1637
  debugMsg("Triggering cargo explosion for tanker " .. effect.name .. " at " .. string.format("%.1f", effect.distance) .. "m with power " .. effect.power .. " scheduled at " .. flameIndex .. "s")
898
1638
  timer.scheduleFunction(function(params)
@@ -900,37 +1640,37 @@ function triggerGiantExplosion(params)
900
1640
  trigger.action.explosion(params[1], params[2])
901
1641
  end, {effect.coords, effect.power}, timer.getTime() + flameIndex + 0.1)
902
1642
 
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
1643
+ local flameSize = effect.flameSize or 3
1644
+ local flameDuration = effect.flameDuration
1645
+ local flameDensity = 1.0
1646
+ local effectId = effectSmokeId
1647
+ effectSmokeId = effectSmokeId + 1
1648
+ local isDuplicate = false
1649
+ for _, pos in pairs(flamePositions) do
1650
+ if getDistance3D(effect.coords, pos) < 3 then
1651
+ isDuplicate = true
1652
+ 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))
1653
+ break
915
1654
  end
916
- if not isDuplicate then
1655
+ end
1656
+ if not isDuplicate then
917
1657
  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])
1658
+ timer.scheduleFunction(function(params)
1659
+ local terrainHeight = land.getHeight({x = params[1].x, y = params[1].z})
1660
+ local adjustedCoords = {x = params[1].x, y = terrainHeight + 2, z = params[1].z}
1661
+ debugMsg("Spawning flame effect at X: " .. string.format("%.0f", adjustedCoords.x) .. ", Y: " .. string.format("%.0f", adjustedCoords.y) .. ", Z: " .. string.format("%.0f", adjustedCoords.z))
1662
+ trigger.action.explosion(adjustedCoords, 10) --Small trigger explosion
1663
+ trigger.action.effectSmokeBig(adjustedCoords, params[2], params[3], params[4])
924
1664
  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)
1665
+ timer.scheduleFunction(function(id)
1666
+ debugMsg("Stopping flame effect for " .. effect.name .. " (ID: " .. id .. ")")
1667
+ trigger.action.effectSmokeStop(id)
928
1668
  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)
1669
+ table.insert(flamePositions, effect.coords)
932
1670
  end
933
- -- Handle non-tanker explosions, cook-offs, and debris
1671
+ flameIndex = flameIndex + 0.5 --Fast spacing for flames (0.5s)
1672
+ end
1673
+ --Handle non-tanker explosions, cook-offs, and debris
934
1674
  if not effect.isTanker or (effect.explosion and not effect.isTanker) then
935
1675
  if effect.explosion then
936
1676
  debugMsg("Triggering cargo explosion for " .. effect.name .. " at " .. string.format("%.1f", effect.distance) .. "m with power " .. effect.power .. " scheduled at " .. otherIndex .. "s")
@@ -938,76 +1678,56 @@ function triggerGiantExplosion(params)
938
1678
  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
1679
  trigger.action.explosion(params[1], params[2])
940
1680
  end, {effect.coords, effect.power}, timer.getTime() + otherIndex + 0.1)
941
- end
942
- if effect.cookOff and effect.cookOffCount > 0 then
1681
+ end
1682
+ if effect.cookOff and effect.cookOffCount > 0 then
943
1683
  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])
1684
+ for i = 1, effect.cookOffCount do
1685
+ local delay = effect.cookOffRandomTiming and math.random() * effect.cookOffDuration or (i - 1) * (effect.cookOffDuration / effect.cookOffCount)
1686
+ local basePower = effect.cookOffPower
1687
+ local powerVariation = effect.cookOffPowerRandom / 100
1688
+ local cookOffPower = effect.cookOffPowerRandom == 0 and basePower or basePower * (1 + powerVariation * (math.random() * 2 - 1))
1689
+ 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))
1690
+ timer.scheduleFunction(function(params)
1691
+ 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])
1692
+ trigger.action.explosion(params[1], params[2])
953
1693
  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])
1694
+ end
1695
+ if splash_damage_options.debris_effects then
1696
+ local debrisCount = math.random(splash_damage_options.debris_count_min, splash_damage_options.debris_count_max)
1697
+ for j = 1, debrisCount do
1698
+ local theta = math.random() * 2 * math.pi
1699
+ local phi = math.acos(math.random() * 2 - 1)
1700
+ local minDist = splash_damage_options.debris_max_distance * 0.1
1701
+ local maxDist = splash_damage_options.debris_max_distance
1702
+ local r = math.random() * (maxDist - minDist) + minDist
1703
+ local debrisX = effect.coords.x + r * math.sin(phi) * math.cos(theta)
1704
+ local debrisZ = effect.coords.z + r * math.sin(phi) * math.sin(theta)
1705
+ local terrainY = land.getHeight({x = debrisX, y = debrisZ})
1706
+ local debrisY = terrainY + math.random() * maxDist
1707
+ local debrisPos = {x = debrisX, y = debrisY, z = debrisZ}
1708
+ local debrisPower = splash_damage_options.debris_power
1709
+ local debrisDelay = (j - 1) * (effect.cookOffDuration / debrisCount)
1710
+ timer.scheduleFunction(function(debrisArgs)
1711
+ 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])
1712
+ trigger.action.explosion(debrisArgs[1], debrisArgs[2])
973
1713
  end, {debrisPos, debrisPower}, timer.getTime() + otherIndex + debrisDelay)
974
1714
  end
975
1715
  end
976
1716
  end
977
- otherIndex = otherIndex + 1 -- Slower spacing for non-flame effects (1s)
1717
+ otherIndex = otherIndex + 1 --Slower spacing for non-flame effects (1s)
978
1718
  end
979
1719
  processedCargoUnits[unitKey] = true
980
1720
  processedUnitsGlobal[unitKey] = true
981
1721
  end
982
1722
  end
983
- cargoEffectsQueue = {} -- Clear the queue after processing
1723
+ cargoEffectsQueue = {} --Clear the queue after processing
984
1724
  end
985
1725
  end, {initialPos, scanRadius, preExplosionTargets}, timer.getTime() + totalDuration + 1.0)
986
1726
  end
987
1727
  end
988
1728
 
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
-
1729
+
1730
+
1011
1731
  function getWeaponExplosive(name)
1012
1732
  local weaponData = explTable[name]
1013
1733
  if weaponData then
@@ -1088,6 +1808,30 @@ end
1088
1808
 
1089
1809
  ----[[ ##### Updated track_wpns() Function ##### ]]----
1090
1810
  local recentExplosions = {}
1811
+
1812
+
1813
+ --function to schedule flares for cook-offs
1814
+ function scheduleCookOffFlares(coords, cookOffCount, cookOffDuration, flareColor)
1815
+ local flareCount = math.floor(cookOffCount * splash_damage_options.cookoff_flare_count_modifier)
1816
+ if flareCount < 1 then return end --Skip if no flares
1817
+ debugMsg("Scheduling " .. flareCount .. " flares for cook-off at X: " .. string.format("%.0f", coords.x) .. ", Z: " .. string.format("%.0f", coords.z) .. " over " .. cookOffDuration .. "s")
1818
+ for i = 1, flareCount do
1819
+ local delay = math.random() * cookOffDuration --Random time within cook-off duration
1820
+ local terrainHeight = land.getHeight({x = coords.x, y = coords.z})
1821
+ local offset = {
1822
+ x = coords.x + math.random(-splash_damage_options.cookoff_flare_offset, splash_damage_options.cookoff_flare_offset),
1823
+ y = terrainHeight, --Start at ground level
1824
+ z = coords.z + math.random(-splash_damage_options.cookoff_flare_offset, splash_damage_options.cookoff_flare_offset)
1825
+ }
1826
+ local azimuth = math.random(1, 360) --Random direction
1827
+ timer.scheduleFunction(function(params)
1828
+ debugMsg("Spawning flare #" .. params[1] .. " at X: " .. string.format("%.0f", params[2].x) .. ", Y: " .. string.format("%.0f", params[2].y) .. ", Z: " .. string.format("%.0f", params[2].z) .. " with color " .. params[3])
1829
+ trigger.action.signalFlare(params[2], params[3], params[4])
1830
+ end, {i, offset, flareColor, azimuth}, timer.getTime() + delay)
1831
+ end
1832
+ end
1833
+
1834
+
1091
1835
  function track_wpns()
1092
1836
  local weaponsToRemove = {} --Delay removal to ensure all weapons are checked
1093
1837
  for wpn_id_, wpnData in pairs(tracked_weapons) do
@@ -1138,6 +1882,14 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1138
1882
  if splash_damage_options.rocket_multiplier and wpnData.cat == Weapon.Category.ROCKET then
1139
1883
  base_explosive = base_explosive * splash_damage_options.rocket_multiplier
1140
1884
  end
1885
+ if wpnData.isGroundUnitOrdnance and splash_damage_options.track_groundunitordnance then
1886
+ base_explosive = base_explosive * splash_damage_options.groundunitordnance_damage_modifier
1887
+ --Log modifier only once per weapon
1888
+ --if splash_damage_options.track_groundunitordnance_debug and not wpnData.debugLogged then
1889
+ --debugMsg("Applying ground unit ordnance damage modifier " .. splash_damage_options.groundunitordnance_damage_modifier .. " to " .. wpnData.name .. ", base explosive power: " .. base_explosive)
1890
+ --wpnData.debugLogged = true --Mark as logged
1891
+ --end
1892
+ end
1141
1893
 
1142
1894
  local explosionPower = base_explosive
1143
1895
  if splash_damage_options.apply_shaped_charge_effects and isShapedCharge then
@@ -1149,12 +1901,14 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1149
1901
  blastRadius = math.pow(explosionPower, 1/3) * 10 * splash_damage_options.dynamic_blast_radius_modifier
1150
1902
  end
1151
1903
 
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
1904
+ --Set tightRadius, use 50m for ground ordnance if enabled
1905
+ local tightRadius = blastRadius
1906
+ if wpnData.isGroundUnitOrdnance and splash_damage_options.scan_50m_for_groundordnance then
1907
+ tightRadius = 50 --Fixed 50m radius for ground ordnance
1908
+ if splash_damage_options.track_groundunitordnance_debug then
1909
+ debugMsg("Using 50m scan radius for ground ordnance " .. wpnData.name)
1910
+ end
1911
+ end
1158
1912
  local volS = {
1159
1913
  id = world.VolumeType.SPHERE,
1160
1914
  params = {
@@ -1174,7 +1928,9 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1174
1928
  health = foundObject:getLife() or 0,
1175
1929
  position = foundObject:getPoint(),
1176
1930
  maxHealth = (category == Object.Category.UNIT and foundObject:getDesc().life) or foundObject:getLife() or 0,
1177
- unit = foundObject
1931
+ unit = foundObject,
1932
+ id = foundObject:getID(),
1933
+ unitName = foundObject:getName() or "Unknown"
1178
1934
  })
1179
1935
  end
1180
1936
  end
@@ -1234,15 +1990,54 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1234
1990
  else
1235
1991
  --Weapon has impacted
1236
1992
  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.
1993
+ local ip = land.getIP(wpnData.pos, wpnData.dir, lookahead(wpnData.speed)) --terrain intersection point with weapon's nose
1238
1994
  local explosionPoint
1239
1995
  if not ip then --use last calculated IP
1240
1996
  explosionPoint = wpnData.pos
1241
1997
  else --use intersection point
1242
1998
  explosionPoint = ip
1999
+ end
2000
+ if wpnData.isGroundUnitOrdnance and splash_damage_options.track_groundunitordnance_debug then
2001
+ local base_explosive, isShapedCharge = getWeaponExplosive(wpnData.name)
2002
+ base_explosive = base_explosive * splash_damage_options.overall_scaling
2003
+ if splash_damage_options.rocket_multiplier and wpnData.cat == Weapon.Category.ROCKET then
2004
+ base_explosive = base_explosive * splash_damage_options.rocket_multiplier
2005
+ end
2006
+ if wpnData.isGroundUnitOrdnance and splash_damage_options.track_groundunitordnance then
2007
+ base_explosive = base_explosive * splash_damage_options.groundunitordnance_damage_modifier
2008
+ end
2009
+ local explosionPower = base_explosive
2010
+ if splash_damage_options.apply_shaped_charge_effects and isShapedCharge then
2011
+ explosionPower = explosionPower * splash_damage_options.shaped_charge_multiplier
2012
+ end
2013
+ debugMsg("Ground unit ordnance " .. wpnData.name .. " impacted at X: " .. string.format("%.0f", explosionPoint.x) .. ", Y: " .. string.format("%.0f", explosionPoint.y) .. ", Z: " .. string.format("%.0f", explosionPoint.z) .. " with power " .. explosionPower)
1243
2014
  end
1244
2015
  local chosenTargets = wpnData.tightTargets or {}
1245
2016
  local safeToBlast = true
2017
+ --Check if weapon is napalm
2018
+ local isNapalm = false
2019
+ --Check for napalm override weapons
2020
+ if splash_damage_options.napalmoverride_enabled then
2021
+ local napalmWeapons = {}
2022
+ for weapon in splash_damage_options.napalm_override_weapons:gmatch("[^,]+") do
2023
+ napalmWeapons[trim(weapon)] = true
2024
+ end
2025
+ if napalmWeapons[wpnData.name] then
2026
+ isNapalm = true
2027
+ debugMsg("Napalm override triggered for " .. wpnData.name .. " at X: " .. string.format("%.0f", explosionPoint.x) .. ", Z: " .. string.format("%.0f", explosionPoint.z))
2028
+ napalmOnImpact(explosionPoint, wpnData.speed, wpnData.name)
2029
+ table.insert(weaponsToRemove, wpn_id_)
2030
+ end
2031
+ end
2032
+
2033
+ --Check for MK77 weapons independently
2034
+ if splash_damage_options.napalm_mk77_enabled and (wpnData.name == "MK77mod0-WPN" or wpnData.name == "MK77mod1-WPN") then
2035
+ isNapalm = true
2036
+ debugMsg("MK77 napalm triggered for " .. wpnData.name .. " at X: " .. string.format("%.0f", explosionPoint.x) .. ", Z: " .. string.format("%.0f", explosionPoint.z))
2037
+ napalmOnImpact(explosionPoint, wpnData.speed, wpnData.name)
2038
+ table.insert(weaponsToRemove, wpn_id_)
2039
+ end
2040
+ if not isNapalm then
1246
2041
  if splash_damage_options.ordnance_protection then
1247
2042
  local checkVol = { id = world.VolumeType.SPHERE, params = { point = explosionPoint, radius = splash_damage_options.ordnance_protection_radius } }
1248
2043
  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")
@@ -1262,6 +2057,12 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1262
2057
  if splash_damage_options.rocket_multiplier and wpnData.cat == Weapon.Category.ROCKET then
1263
2058
  base_explosive = base_explosive * splash_damage_options.rocket_multiplier
1264
2059
  end
2060
+ if wpnData.isGroundUnitOrdnance and splash_damage_options.track_groundunitordnance then
2061
+ base_explosive = base_explosive * splash_damage_options.groundunitordnance_damage_modifier
2062
+ if splash_damage_options.track_groundunitordnance_debug then
2063
+ debugMsg("Applying ground unit ordnance damage modifier " .. splash_damage_options.groundunitordnance_damage_modifier .. " to " .. wpnData.name .. ", base explosive power: " .. base_explosive)
2064
+ end
2065
+ end
1265
2066
 
1266
2067
  local explosionPower = base_explosive
1267
2068
  if splash_damage_options.apply_shaped_charge_effects and isShapedCharge then
@@ -1297,7 +2098,7 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1297
2098
  local submunitionName = weaponData.submunition_name or "unknown"
1298
2099
  --Apply bomblet reduction logic if enabled
1299
2100
  if splash_damage_options.cluster_bomblet_reductionmodifier then
1300
- if submunitionCount > 35 then
2101
+ if submunitionCount > 35 then
1301
2102
  local reductionFactor = (60 - 35) / (247 - 35)
1302
2103
  submunitionCount = 35 + math.floor((submunitionCount - 35) * reductionFactor)
1303
2104
  if submunitionCount > 60 then submunitionCount = 60 end --Cap at 60
@@ -1312,8 +2113,16 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1312
2113
  trigger.action.explosion(explosionPoint, explosionPower)
1313
2114
  table.insert(recentExplosions, { pos = explosionPoint, time = timer.getTime(), radius = blastRadius })
1314
2115
  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)
2116
+ --Check for units destroyed by initial explosion
2117
+ local playerName = wpnData.init or "unknown"
2118
+ for _, target in ipairs(chosenTargets) do
2119
+ if target.unit:isExist() and target.health > 0 and target.unit:getLife() <= 0 then
2120
+ debugMsg("Unit " .. target.name .. " destroyed by initial explosion, credited to player: " .. playerName)
2121
+ end
2122
+ end
2123
+ end
2124
+ blastWave(explosionPoint, splash_damage_options.blast_search_radius, wpnData.name, explosionPower, isShapedCharge)
2125
+
1317
2126
  end
1318
2127
  --detect_ordnance_destruction comes before recent_large_explosion_snap in original
1319
2128
  if splash_damage_options.ordnance_protection and splash_damage_options.detect_ordnance_destruction and splash_damage_options.larger_explosions then
@@ -1429,6 +2238,8 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1429
2238
  end
1430
2239
  local status, err = pcall(function()
1431
2240
  --Log pre-explosion targets
2241
+ --Sort pre-explosion targets by distance
2242
+ table.sort(chosenTargets, function(a, b) return a.distance < b.distance end)
1432
2243
  if splash_damage_options.track_pre_explosion then
1433
2244
  if #chosenTargets > 0 then
1434
2245
  local msg = "Targets in blast zone for " .. weaponName .. " BEFORE explosion (last frame, using finalPos):\n"
@@ -1443,7 +2254,7 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1443
2254
  end
1444
2255
  end
1445
2256
 
1446
- blastWave(explosionPoint, splash_damage_options.blast_search_radius, wpnData.ordnance, explosionPower, isShapedCharge)
2257
+ blastWave(explosionPoint, splash_damage_options.blast_search_radius, wpnData.name, explosionPower, isShapedCharge)
1447
2258
 
1448
2259
  --Post-explosion analysis and queue cargo effects
1449
2260
  if splash_damage_options.track_pre_explosion then
@@ -1453,6 +2264,7 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1453
2264
  local preExplosionTargets = innerArgs[3] or {}
1454
2265
  local weaponName = innerArgs[4]
1455
2266
  local weaponPower = innerArgs[5]
2267
+ local playerName = innerArgs[6]
1456
2268
  if splash_damage_options.debug == true then
1457
2269
  debugMsg("Starting post-explosion analysis for " .. weaponName .. " at " .. timer.getTime() .. "s")
1458
2270
  end
@@ -1472,11 +2284,15 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1472
2284
  local category = foundObject:getCategory()
1473
2285
  if (category == Object.Category.UNIT and (foundObject:getDesc().category == Unit.Category.GROUND_UNIT or foundObject:getDesc().category == Unit.Category.AIRPLANE)) or
1474
2286
  category == Object.Category.STATIC then
2287
+ local distance = getDistance(impactPoint, foundObject:getPoint())
1475
2288
  table.insert(postExplosionTargets, {
1476
2289
  name = foundObject:getTypeName(),
1477
2290
  health = foundObject:getLife() or 0,
1478
2291
  position = foundObject:getPoint(),
1479
- maxHealth = (category == Object.Category.UNIT and foundObject:getDesc().life) or foundObject:getLife() or 0
2292
+ maxHealth = (category == Object.Category.UNIT and foundObject:getDesc().life) or foundObject:getLife() or 0,
2293
+ distance = distance,
2294
+ id = foundObject:getID(),
2295
+ unitName = foundObject:getName() or "Unknown"
1480
2296
  })
1481
2297
  end
1482
2298
  end
@@ -1484,7 +2300,8 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1484
2300
  end
1485
2301
 
1486
2302
  world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, volS, ifFound)
1487
-
2303
+ --Sort post-explosion targets by distance
2304
+ table.sort(postExplosionTargets, function(a, b) return a.distance < b.distance end)
1488
2305
  local msg = "Post-explosion analysis for " .. weaponName .. ":\n"
1489
2306
 
1490
2307
  --Match pre-detected units
@@ -1492,11 +2309,13 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1492
2309
  local found = false
1493
2310
  local postHealth = 0
1494
2311
  local postPosition = nil
2312
+ local postDistance = 0
1495
2313
  for _, postTarget in ipairs(postExplosionTargets) do
1496
2314
  if preTarget.name == postTarget.name and getDistance(preTarget.position, postTarget.position) < 1 then
1497
2315
  found = true
1498
2316
  postHealth = postTarget.health
1499
2317
  postPosition = postTarget.position
2318
+ postDistance = postTarget.distance
1500
2319
  break
1501
2320
  end
1502
2321
  end
@@ -1506,6 +2325,31 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1506
2325
 
1507
2326
  if not found or postHealth <= 0 then
1508
2327
  status = "WAS FULLY DESTROYED"
2328
+ --killfeed make sure its not an unknown weapon or an ai
2329
+ --if splash_damage_options.killfeed_enable and explTable[weaponName] then
2330
+ if splash_damage_options.killfeed_enable and explTable[weaponName] and playerName ~= "unknown" then
2331
+ local status, isPlayer = pcall(function()
2332
+ local playerList = net.get_player_list() or {}
2333
+ for _, pid in ipairs(playerList) do
2334
+ local pinfo = net.get_player_info(pid)
2335
+ if pinfo and pinfo.name == playerName then
2336
+ return true
2337
+ end
2338
+ end
2339
+ return false
2340
+ end)
2341
+ if status and isPlayer then
2342
+ table.insert(splashKillfeedTemp, {
2343
+ playerName = playerName,
2344
+ weaponName = weaponName,
2345
+ unitName = preTarget.unitName,
2346
+ unitType = preTarget.name,
2347
+ unitId = preTarget.id,
2348
+ time = timer.getTime(),
2349
+ position = coords
2350
+ })
2351
+ end
2352
+ end
1509
2353
  elseif healthPercent < splash_damage_options.cargo_damage_threshold then
1510
2354
  status = "WAS DAMAGED BELOW THRESHOLD"
1511
2355
  else
@@ -1514,14 +2358,14 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1514
2358
 
1515
2359
  --Always include coords in status message
1516
2360
  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 .. ")"
2361
+ local statusMsg = status .. " AT " .. string.format("X: %.0f, Y: %.0f, Z: %.0f", coords.x, coords.y, coords.z) .. " (Dist: " .. string.format("%.1f", postDistance) .. "m, Pre: " .. preTarget.health .. ", Post: " .. postHealth .. ")"
1518
2362
  --Check if target is in cargoUnits and within blast radius
1519
2363
  local cargoData = cargoUnits[preTarget.name]
1520
2364
  if cargoData and preTarget.distance <= blastRadius and
1521
2365
  (not found or postHealth <= 0 or healthPercent < splash_damage_options.cargo_damage_threshold) then
1522
2366
 
1523
2367
  if splash_damage_options.enable_cargo_effects then
1524
- local cargoPower = cargoData.cargoExplosionPower or weaponPower --Use fixed power or fallback
2368
+ local cargoPower = cargoData.cargoExplosionPower or weaponPower
1525
2369
  table.insert(cargoEffectsQueue, {
1526
2370
  name = preTarget.name,
1527
2371
  distance = preTarget.distance,
@@ -1543,10 +2387,33 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1543
2387
  statusMsg = statusMsg .. " WITH COOK-OFF (" .. cargoData.cookOffCount .. " blasts over " .. cargoData.cookOffDuration .. "s)"
1544
2388
  end
1545
2389
  end
1546
- end
1547
-
1548
- msg = msg .. "- " .. preTarget.name .. " " .. statusMsg .. "\n"
1549
- end
2390
+ elseif splash_damage_options.smokeandcookoffeffectallvehicles and preTarget.distance <= blastRadius and
2391
+ (not found or postHealth <= 0 or healthPercent < splash_damage_options.cargo_damage_threshold) then
2392
+ if splash_damage_options.enable_cargo_effects then
2393
+ table.insert(cargoEffectsQueue, {
2394
+ name = preTarget.name,
2395
+ distance = preTarget.distance,
2396
+ coords = coords,
2397
+ power = splash_damage_options.allunits_explode_power, --No explosion
2398
+ explosion = true,
2399
+ cookOff = splash_damage_options.allunits_enable_cookoff,
2400
+ cookOffCount = splash_damage_options.allunits_cookoff_count,
2401
+ cookOffPower = splash_damage_options.allunits_cookoff_power,
2402
+ cookOffDuration = splash_damage_options.allunits_cookoff_duration,
2403
+ cookOffRandomTiming = true,
2404
+ cookOffPowerRandom = splash_damage_options.allunits_cookoff_powerrandom,
2405
+ isTanker = splash_damage_options.allunits_enable_smoke, --Enable smoke
2406
+ flameSize = splash_damage_options.allunits_default_flame_size,
2407
+ flameDuration = splash_damage_options.allunits_default_flame_duration,
2408
+ cargoExplosionMult = 1
2409
+ })
2410
+ statusMsg = statusMsg .. " WITH DEFAULT SMOKE (Size: " .. splash_damage_options.allunits_default_flame_size .. ", Duration: " .. splash_damage_options.allunits_default_flame_duration .. "s)"
2411
+ debugMsg("Queued default smoke effect for " .. preTarget.name .. " at " .. string.format("%.1f", preTarget.distance) .. "m")
2412
+ end
2413
+ end
2414
+
2415
+ msg = msg .. "- " .. preTarget.name .. " " .. statusMsg .. "\n"
2416
+ end
1550
2417
  --Check for additional units
1551
2418
  for _, postTarget in ipairs(postExplosionTargets) do
1552
2419
  local isPreDetected = false
@@ -1562,7 +2429,7 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1562
2429
  local status = postTarget.health <= 0 and "WAS FULLY DESTROYED" or
1563
2430
  (healthPercent < splash_damage_options.cargo_damage_threshold and "WAS DAMAGED BELOW THRESHOLD" or
1564
2431
  "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 .. ")"
2432
+ local statusMsg = status .. " AT " .. string.format("X: %.0f, Y: %.0f, Z: %.0f", coords.x, coords.y, coords.z) .. " (Dist: " .. string.format("%.1f", postTarget.distance) .. "m, Pre: Unknown, Post: " .. postTarget.health .. ")"
1566
2433
  local cargoData = cargoUnits[postTarget.name]
1567
2434
  if cargoData and (postTarget.health <= 0 or healthPercent < splash_damage_options.cargo_damage_threshold) then
1568
2435
  if splash_damage_options.enable_cargo_effects then
@@ -1657,6 +2524,9 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1657
2524
  trigger.action.explosion(pos, power)
1658
2525
  end, {effect.coords, cookOffPower}, timer.getTime() + effectIndex + delay)
1659
2526
  end
2527
+ if splash_damage_options.cookoff_flares_enabled then
2528
+ scheduleCookOffFlares(effect.coords, effect.cookOffCount, effect.cookOffDuration, splash_damage_options.cookoff_flare_color)
2529
+ end
1660
2530
  --Debris burst only if cook-off is true and enabled
1661
2531
  if splash_damage_options.debris_effects then
1662
2532
  local debrisCount = math.random(splash_damage_options.debris_count_min, splash_damage_options.debris_count_max)
@@ -1696,7 +2566,11 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1696
2566
 
1697
2567
  debugMsg(msg)
1698
2568
  env.info("SplashDamage Post-Explosion: " .. msg)
1699
- end, {finalPos, blastRadius, chosenTargets, weaponName, explosionPower}, timer.getTime() + 1)
2569
+ -- Schedule splashKillFeed if there are entries
2570
+ if #splashKillfeedTemp > 0 and splash_damage_options.killfeed_enable then
2571
+ timer.scheduleFunction(splashKillFeed, {}, timer.getTime() + splash_damage_options.killfeed_splashdelay)
2572
+ end
2573
+ end, {finalPos, blastRadius, chosenTargets, weaponName, explosionPower, wpnData.init}, timer.getTime() + 1)
1700
2574
  end
1701
2575
  end)
1702
2576
  if not status then
@@ -1712,6 +2586,7 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1712
2586
  end
1713
2587
  table.insert(weaponsToRemove, wpn_id_)
1714
2588
  end
2589
+ end
1715
2590
  end)
1716
2591
  if not status then
1717
2592
  debugMsg("Error in track_wpns for '" .. (wpnData.name or "unknown weapon") .. "': " .. err)
@@ -1723,251 +2598,1198 @@ world.searchObjects({Object.Category.UNIT, Object.Category.STATIC}, tickVol, fun
1723
2598
  end
1724
2599
  return timer.getTime() + refreshRate
1725
2600
  end
2601
+
1726
2602
  function onWpnEvent(event)
1727
- if event.id == world.event.S_EVENT_SHOT then
2603
+ if event.id == world.event.S_EVENT_SHOT then
1728
2604
  if event.weapon then
1729
- local ordnance = event.weapon
1730
- local typeName = trim(ordnance:getTypeName())
2605
+ local ordnance = event.weapon
2606
+ --verify isExist and getDesc
2607
+ local isValid = false
2608
+ local status, desc = pcall(function() return ordnance:isExist() and ordnance:getDesc() end)
2609
+ if status and desc then
2610
+ isValid = true
2611
+ end
2612
+ if not isValid then
2613
+ if splash_damage_options.debug then
2614
+ env.info("SplashDamage: Invalid weapon object in S_EVENT_SHOT")
2615
+ debugMsg("Invalid weapon object in S_EVENT_SHOT")
2616
+ end
2617
+ return
2618
+ end
2619
+ --Safely get typeName with pcall
2620
+ local status, typeName = pcall(function() return trim(ordnance:getTypeName()) end)
2621
+ if not status or not typeName then
2622
+ if splash_damage_options.debug then
2623
+ env.info("SplashDamage: Failed to get weapon typeName: " .. tostring(typeName))
2624
+ debugMsg("Failed to get weapon typeName: " .. tostring(typeName))
2625
+ end
2626
+ return
2627
+ end
2628
+
2629
+
2630
+ local playerName = "Unknown"
2631
+ if event.initiator then
2632
+ local status, playerNameResult = pcall(function() return event.initiator:getPlayerName() end)
2633
+ if status and playerNameResult then
2634
+ playerName = playerNameResult
2635
+ else
2636
+ local status, unitId = pcall(function() return event.initiator:getID() end)
2637
+ if status and unitId then
2638
+ local playerList = net.get_player_list() or {}
2639
+ for _, pid in ipairs(playerList) do
2640
+ local pinfo = net.get_player_info(pid)
2641
+ if pinfo and pinfo.ucid and (tonumber(pinfo.slot) == unitId or pinfo.slot == event.initiator:getName()) then
2642
+ playerName = pinfo.name or "Unknown"
2643
+ break
2644
+ end
2645
+ end
2646
+ end
2647
+ end
2648
+ end
2649
+ if splash_damage_options.debug then
2650
+ env.info("Weapon [" .. typeName .. "] fired by player " .. playerName)
2651
+ debugMsg("Weapon [" .. typeName .. "] fired by player " .. playerName)
2652
+ end
2653
+ if splash_damage_options.napalmoverride_enabled then
2654
+ local napalmWeapons = {}
2655
+ for weapon in splash_damage_options.napalm_override_weapons:gmatch("[^,]+") do
2656
+ napalmWeapons[trim(weapon)] = true
2657
+ end
2658
+ if napalmWeapons[typeName] then
2659
+ isNapalm = true
2660
+ if splash_damage_options.debug then
2661
+ debugMsg("Tracking napalm override weapon: [" .. typeName .. "]")
2662
+ end
2663
+ end
2664
+ end
2665
+ if splash_damage_options.napalm_mk77_enabled and (typeName == "MK77mod0-WPN" or typeName == "MK77mod1-WPN") then
2666
+ isNapalm = true
2667
+ if splash_damage_options.debug then
2668
+ debugMsg("Tracking MK77 napalm weapon: [" .. typeName .. "]")
2669
+ end
2670
+ end
2671
+ if isNapalm then
2672
+ tracked_weapons[event.weapon.id_] = {
2673
+ wpn = ordnance,
2674
+ init = playerName,
2675
+ pos = ordnance:getPoint(),
2676
+ dir = ordnance:getPosition().x,
2677
+ name = typeName,
2678
+ speed = ordnance:getVelocity(),
2679
+ cat = ordnance:getCategory()
2680
+ }
2681
+ return
2682
+ end
2683
+ --Debug the exact typeName and explTable lookup
1731
2684
  if splash_damage_options.debug then
1732
- env.info("Weapon fired: [" .. typeName .. "]")
1733
- debugMsg("Weapon fired: [" .. typeName .. "]")
2685
+ debugMsg("Checking explTable for typeName: [" .. typeName .. "]")
2686
+ end
2687
+ local weaponData = explTable[typeName]
2688
+ if splash_damage_options.debug then
2689
+ if weaponData then
2690
+ debugMsg("Found in explTable: explosive=" .. weaponData.explosive .. ", groundordnance=" .. tostring(weaponData.groundordnance))
2691
+ else
2692
+ debugMsg("Not found in explTable: [" .. typeName .. "]")
2693
+ end
2694
+ end
2695
+ --Handle ground ordnance explicitly
2696
+ if weaponData and weaponData.groundordnance then
2697
+ if splash_damage_options.track_groundunitordnance then
2698
+ --Count tracked ground ordnance
2699
+ local groundOrdnanceCount = 0
2700
+ for _, wpnData in pairs(tracked_weapons) do
2701
+ if wpnData.isGroundUnitOrdnance then
2702
+ groundOrdnanceCount = groundOrdnanceCount + 1
2703
+ end
2704
+ end
2705
+ if groundOrdnanceCount >= splash_damage_options.groundunitordnance_maxtrackedcount then
2706
+ if splash_damage_options.debug then
2707
+ debugMsg("Skipping tracking for " .. typeName .. ": ground ordnance limit reached (" .. groundOrdnanceCount .. "/" .. splash_damage_options.groundunitordnance_maxtrackedcount .. ")")
2708
+ env.info("SplashDamage: Skipping tracking for " .. typeName .. ": ground ordnance limit reached (" .. groundOrdnanceCount .. "/" .. splash_damage_options.groundunitordnance_maxtrackedcount .. ")")
2709
+ end
2710
+ return
2711
+ end
2712
+ if splash_damage_options.track_groundunitordnance_debug then
2713
+ debugMsg("Tracking ground unit ordnance: " .. typeName .. " fired by " .. (event.initiator and event.initiator:getTypeName() or "unknown"))
2714
+ env.info("SplashDamage: Tracking ground unit ordnance: " .. typeName .. " (" .. (event.initiator and event.initiator:getTypeName() or "no initiator") .. ")")
2715
+ end
2716
+ tracked_weapons[event.weapon.id_] = {
2717
+ wpn = ordnance,
2718
+ init = playerName,
2719
+ pos = ordnance:getPoint(),
2720
+ dir = ordnance:getPosition().x,
2721
+ name = typeName,
2722
+ speed = ordnance:getVelocity(),
2723
+ cat = ordnance:getCategory(),
2724
+ isGroundUnitOrdnance = true --Flag for ground ordnance
2725
+ }
2726
+ elseif splash_damage_options.track_groundunitordnance_debug then
2727
+ debugMsg("Event shot, but not tracking ground unit ordnance: " .. typeName)
2728
+ env.info("SplashDamage: event shot, but not tracking ground unit ordnance: " .. typeName .. " (" .. (event.initiator and event.initiator:getTypeName() or "no initiator") .. ")")
2729
+ end
2730
+ return
2731
+ end
2732
+ --Handle other tracked weapons in explTable
2733
+ if weaponData then
2734
+ if (ordnance:getDesc().category ~= 0) and event.initiator then
2735
+ if ordnance:getDesc().category == 1 then --Missiles
2736
+ if (ordnance:getDesc().MissileCategory ~= 1 and ordnance:getDesc().MissileCategory ~= 2) then --Exclude AAM and SAM
2737
+ tracked_weapons[event.weapon.id_] = {
2738
+ wpn = ordnance,
2739
+ init = playerName,
2740
+ pos = ordnance:getPoint(),
2741
+ dir = ordnance:getPosition().x,
2742
+ name = typeName,
2743
+ speed = ordnance:getVelocity(),
2744
+ cat = ordnance:getCategory()
2745
+ }
2746
+ end
2747
+ else --Rockets, bombs, etc.
2748
+ tracked_weapons[event.weapon.id_] = {
2749
+ wpn = ordnance,
2750
+ init = playerName,
2751
+ pos = ordnance:getPoint(),
2752
+ dir = ordnance:getPosition().x,
2753
+ name = typeName,
2754
+ speed = ordnance:getVelocity(),
2755
+ cat = ordnance:getCategory()
2756
+ }
2757
+ end
2758
+ end
2759
+ return --Exit after handling known weapons
1734
2760
  end
2761
+ --Handle unknown weapons or non-tracked shells
1735
2762
  if string.find(typeName, "weapons.shells") then
1736
2763
  if splash_damage_options.debug then
1737
2764
  debugMsg("Event shot, but not tracking: " .. typeName)
1738
- env.info("SplashDamage: event shot, but not tracking: " .. typeName .. " (" .. event.initiator:getTypeName() .. ")")
2765
+ env.info("SplashDamage: event shot, but not tracking: " .. typeName .. " (" .. (event.initiator and event.initiator:getTypeName() or "no initiator") .. ")")
1739
2766
  end
1740
2767
  return
1741
2768
  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
2769
+
2770
+ --Log missing weapons
2771
+ env.info("SplashDamage: " .. typeName .. " missing from script (" .. (event.initiator and event.initiator:getTypeName() or "no initiator") .. ")")
2772
+ if splash_damage_options.weapon_missing_message then
1747
2773
  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
2774
  env.info("Current keys in explTable:")
1757
2775
  for k, v in pairs(explTable) do
1758
2776
  env.info("Key: [" .. k .. "]")
2777
+ end
2778
+
2779
+ end
2780
+ end
2781
+ end
2782
+ end
2783
+
2784
+ function splashKillFeed()
2785
+ if not splash_damage_options.killfeed_enable then return end
2786
+
2787
+ local status, err = pcall(function()
2788
+ local tempTable = splashKillfeedTemp
2789
+ splashKillfeedTemp = {}
2790
+ local processedUnitIds = {} -- Track unit IDs processed in this batch
2791
+
2792
+ for _, entry in ipairs(tempTable) do
2793
+ local unitId = entry.unitId
2794
+ local unitName = entry.unitName
2795
+ local unitType = entry.unitType
2796
+ local playerName = entry.playerName
2797
+ local weaponName = entry.weaponName
2798
+ local position = entry.position
2799
+
2800
+ -- Skip if unitType is "Unknown"
2801
+ if unitType == "Unknown" then
2802
+ if splash_damage_options.killfeed_debug then
2803
+ env.info(string.format("SplashKillFeed: Skipped unit ID %s with unknown type at %.2f", unitId, timer.getTime()))
2804
+ end
2805
+ return
2806
+ end
2807
+
2808
+ --Check if unit ID was already processed in this batch
2809
+ if processedUnitIds[unitId] then
2810
+ if splash_damage_options.killfeed_debug then
2811
+ env.info(string.format("SplashKillFeed: Skipped duplicate splash kill in batch for unit ID %s (%s) by %s with %s at %.2f",
2812
+ unitId, unitType, playerName, weaponName, timer.getTime()))
2813
+ end
2814
+ return --Skip to next iteration
2815
+ end
2816
+
2817
+ local unitExists = false
2818
+ local status, exists = pcall(function()
2819
+ local obj = Unit.getByName(unitName) or StaticObject.getByName(unitName)
2820
+ return obj and obj:isExist()
2821
+ end)
2822
+ if status and not exists then
2823
+ unitExists = false
2824
+ elseif status then
2825
+ unitExists = true
2826
+ else
2827
+ if splash_damage_options.killfeed_debug then
2828
+ env.info("SplashKillFeed: Error checking existence of unit ID " .. tostring(unitId) .. ": " .. tostring(exists))
2829
+ end
2830
+ end
2831
+
2832
+ if not unitExists then
2833
+ local isDuplicate = false
2834
+ for _, killEntry in ipairs(killfeedTable) do
2835
+ if killEntry.unitID == unitId then
2836
+ isDuplicate = true
2837
+ if splash_damage_options.killfeed_debug then
2838
+ env.info(string.format("SplashKillFeed: Skipped duplicate splash kill for unit ID %s (%s) by %s with %s at %.2f",
2839
+ unitId, unitType, playerName, weaponName, timer.getTime()))
1759
2840
  end
2841
+ break
1760
2842
  end
1761
- return --Skip tracking this weapon since its not in the table
1762
2843
  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() }
2844
+
2845
+ if not isDuplicate then
2846
+ local msg = string.format("%s destroyed by %s's %s Splash Damage", unitType, playerName, weaponName)
2847
+ if splash_damage_options.killfeed_game_messages then
2848
+ local status, err = pcall(function()
2849
+ trigger.action.outTextForCoalition(2, msg, splash_damage_options.killfeed_game_message_duration)
2850
+ end)
2851
+ if not status then
2852
+ trigger.action.outText(msg, splash_damage_options.killfeed_game_message_duration)
2853
+ if splash_damage_options.killfeed_debug then
2854
+ env.info("SplashKillFeed: Failed coalition message: " .. tostring(err))
2855
+ end
2856
+ end
1768
2857
  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() }
2858
+
2859
+ table.insert(splashKillfeedTable, {
2860
+ unitName = unitName,
2861
+ unitType = unitType,
2862
+ unitId = unitId,
2863
+ playerName = playerName,
2864
+ weaponName = weaponName,
2865
+ time = timer.getTime(),
2866
+ position = position
2867
+ })
2868
+
2869
+ if splash_damage_options.killfeed_debug then
2870
+ env.info(string.format("SplashKillFeed: %s destroyed by %s's %s Splash Damage [ID: %s] at %.2f",
2871
+ unitType, playerName, weaponName, unitId, timer.getTime()))
2872
+ end
2873
+ processedUnitIds[unitId] = true --Mark unit ID as processed
1771
2874
  end
2875
+ elseif splash_damage_options.killfeed_debug then
2876
+ env.info(string.format("SplashKillFeed: Unit ID %s (%s) still exists, skipping splash kill at %.2f",
2877
+ unitId, unitType, timer.getTime()))
1772
2878
  end
1773
2879
  end
2880
+ end)
2881
+
2882
+ if not status and splash_damage_options.killfeed_debug then
2883
+ env.info("SplashKillFeed: Error: " .. tostring(err))
1774
2884
  end
1775
2885
  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
2886
+
2887
+
2888
+ local function processSplashKillfeed()
2889
+ if not splash_damage_options.killfeed_enable or not splash_damage_options.killfeed_lekas_foothold_integration then
2890
+ if splash_damage_options.killfeed_debug then
2891
+ env.info("SplashDamage: processSplashKillfeed skipped")
1805
2892
  end
2893
+ return timer.getTime() + 60
1806
2894
  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
2895
+
2896
+ if not bc or type(bc) ~= "table" or not bc.addTempStat then
2897
+ if splash_damage_options.killfeed_debug then
2898
+ env.info("SplashDamage: bc is not accessible or missing addTempStat")
1820
2899
  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)
2900
+ return timer.getTime() + 60
2901
+ end
2902
+
2903
+ local currentTime = timer.getTime()
2904
+ local entriesToRemove = {}
2905
+ local processedCount = 0
2906
+
2907
+ -- Log bc table state before processing
2908
+ if splash_damage_options.killfeed_debug then
2909
+ env.info("SplashDamage: processSplashKillfeed started at " .. string.format("%.2f", currentTime))
2910
+ env.info("SplashDamage: bc table state: " .. (bc and "exists" or "nil"))
2911
+ env.info("SplashDamage: bc.addTempStat: " .. (bc.addTempStat and "exists" or "nil"))
2912
+ env.info("SplashDamage: bc.context: " .. (bc.context and "exists" or "nil"))
2913
+ if bc.context then
2914
+ env.info("SplashDamage: bc.context.playerContributions: " .. (bc.context.playerContributions and "exists" or "nil"))
2915
+ if bc.context.playerContributions then
2916
+ env.info("SplashDamage: bc.context.playerContributions[2]: " .. (bc.context.playerContributions[2] and "exists" or "nil"))
1824
2917
  end
1825
2918
  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
2919
+ end
2920
+
2921
+ for i, entry in ipairs(splashKillfeedTable) do
2922
+ if currentTime - entry.time >= splash_damage_options.killfeed_lekas_contribution_delay then
2923
+ local playerName = entry.playerName
2924
+ local unitType = entry.unitType
2925
+ local unitId = entry.unitId
2926
+
2927
+ -- Log entry details
2928
+ if splash_damage_options.killfeed_debug then
2929
+ env.info(string.format("SplashDamage: Processing splash kill entry %d: unitId=%s, unitType=%s, player=%s, time=%.2f",
2930
+ i, unitId, unitType, playerName, entry.time))
2931
+ end
2932
+
2933
+ local status, result = pcall(function()
2934
+ local statName = "Ground Units"
2935
+ local points = 10
2936
+ if unitType:find("Plane") then
2937
+ statName = "Air"
2938
+ points = 30
2939
+ elseif unitType:find("Helicopter") then
2940
+ statName = "Helo"
2941
+ points = 30
2942
+ elseif unitType:find("SAM") then
2943
+ statName = "SAM"
2944
+ points = 30
2945
+ elseif unitType:find("Infantry") then
2946
+ statName = "Infantry"
2947
+ points = 10
2948
+ elseif unitType:find("Ship") then
2949
+ statName = "Ship"
2950
+ points = 250
2951
+ elseif unitType:find("Building") then
2952
+ statName = "Structure"
2953
+ points = 30
1840
2954
  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
2955
+ bc:addTempStat(playerName, statName, 1)
2956
+ if splash_damage_options.killfeed_debug then
2957
+ env.info(string.format("SplashDamage: Added temp stat for %s: stat=%s, count=1", playerName, statName))
2958
+ end
2959
+ if bc.context and type(bc.context) == "table" and bc.context.playerContributions and type(bc.context.playerContributions) == "table" then
2960
+ bc.context.playerContributions[2] = bc.context.playerContributions[2] or {}
2961
+ local oldPoints = bc.context.playerContributions[2][playerName] or 0
2962
+ bc.context.playerContributions[2][playerName] = oldPoints + points
2963
+ if splash_damage_options.killfeed_debug then
2964
+ env.info(string.format("SplashDamage: Updated contributions for %s: old=%d, new=%d, added=%d",
2965
+ playerName, oldPoints, bc.context.playerContributions[2][playerName], points))
1891
2966
  end
1892
- if triggerExplosion then
1893
- timer.scheduleFunction(explodeObject, {obj_location, dist, explosion_size * splash_damage_options.cascade_scaling}, timer.getTime() + timing)
2967
+ else
2968
+ if splash_damage_options.killfeed_debug then
2969
+ env.info("SplashDamage: Skipped contribution update for " .. playerName .. ": bc.context or bc.context.playerContributions is nil")
1894
2970
  end
1895
2971
  end
2972
+ processedCount = processedCount + 1
2973
+ if splash_damage_options.killfeed_debug then
2974
+ env.info(string.format("SplashDamage: Processed splash kill for %s by %s: stat=%s, points=%d, unitId=%s",
2975
+ unitType, playerName, statName, points, unitId))
2976
+ end
2977
+ end)
2978
+ if not status and splash_damage_options.killfeed_debug then
2979
+ env.info("SplashDamage: Error processing splash kill for unitId=" .. tostring(unitId) .. ": " .. tostring(result))
1896
2980
  end
2981
+ table.insert(entriesToRemove, i)
1897
2982
  end
1898
- return true
1899
2983
  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)
2984
+
2985
+ for i = #entriesToRemove, 1, -1 do
2986
+ table.remove(splashKillfeedTable, entriesToRemove[i])
1907
2987
  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)
2988
+
2989
+ if splash_damage_options.killfeed_debug then
2990
+ if bc.tempStats and type(bc.tempStats) == "table" then
2991
+ env.info("SplashDamage: tempStats contents:")
2992
+ for playerName, stats in pairs(bc.tempStats) do
2993
+ local statStr = ""
2994
+ for statKey, value in pairs(stats) do
2995
+ statStr = statStr .. statKey .. "=" .. tostring(value) .. ", "
1917
2996
  end
2997
+ env.info("SplashDamage: " .. playerName .. ": " .. (statStr ~= "" and statStr or "empty"))
1918
2998
  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
2999
+ if not next(bc.tempStats) then
3000
+ env.info("SplashDamage: tempStats is empty")
1929
3001
  end
3002
+ else
3003
+ env.info("SplashDamage: bc.tempStats is nil or not a table")
1930
3004
  end
1931
3005
  end
1932
- end
1933
3006
 
1934
- function updateSplashDamageSetting(setting, increment)
1935
- if not splash_damage_options[setting] then
1936
- env.info("Error: Setting " .. setting .. " does not exist.")
1937
- return
3007
+ if splash_damage_options.killfeed_debug and processedCount > 0 then
3008
+ env.info("SplashDamage: Processed " .. processedCount .. " splash kills, remaining: " .. #splashKillfeedTable)
1938
3009
  end
1939
3010
 
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)
3011
+ return timer.getTime() + 60
1944
3012
  end
1945
3013
 
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
3014
 
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
3015
 
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
3016
 
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
3017
+
3018
+ --Function to log and process unit data from event
3019
+ function logEvent(eventName, eventData)
3020
+ local logStr = "\n---EVENT: " .. eventName .. " ---\n"
3021
+
3022
+ --Variables to hold unit data
3023
+ local unitID, unitName, unitType, unitPosition, unitLife
3024
+
3025
+ --Handle DEAD event (use initiator)
3026
+ if eventName == "DEAD" and eventData.initiator then
3027
+ unitID = safeGet(function() return eventData.initiator:getID() end, "unavailable")
3028
+ unitName = safeGet(function() return eventData.initiator:getName() end, "unknown")
3029
+ unitType = safeGet(function() return eventData.initiator:getTypeName() end, "unknown")
3030
+ unitPosition = safeGet(function()
3031
+ local pos = eventData.initiator:getPosition().p
3032
+ return string.format("x=%.0f, y=%.0f, z=%.0f", pos.x, pos.y, pos.z)
3033
+ end, "unavailable")
3034
+ unitLife = safeGet(function() return eventData.initiator:getLife() end, 0)
3035
+
3036
+ --Delay DEAD event processing by 0.1 seconds
3037
+ timer.scheduleFunction(function(params)
3038
+ local logStr = "\n---EVENT: " .. params.eventName .. " ---\n"
3039
+
3040
+ --Check if unitID is already processed
3041
+ if LogEventProcessedUnitTable[params.unitID] then
3042
+ logStr = logStr .. "Unit ID " .. params.unitID .. " already processed in LogEventProcessedUnitTable\n"
3043
+ if splash_damage_options.events_debug then
3044
+ env.info(logStr)
3045
+ end
3046
+ return
3047
+ end
3048
+
3049
+ --Log and store unit data if valid
3050
+ if params.unitID ~= "unavailable" and params.unitName ~= "unavailable" and params.unitType ~= "unavailable" and params.unitPosition ~= "unavailable" and params.unitLife ~= "unavailable" then
3051
+ logStr = logStr .. "Stored Unit Data: ID=" .. params.unitID .. ", Name=" .. params.unitName .. ", Type=" .. params.unitType .. ", Position=" .. params.unitPosition .. ", Life=" .. params.unitLife .. "\n"
3052
+ logStr = logStr .. "Processing DEAD event for unit " .. params.unitName .. " (ID: " .. params.unitID .. ") at position " .. params.unitPosition .. "\n"
3053
+
3054
+ --Store in LogEventProcessedUnitTable
3055
+ LogEventProcessedUnitTable[params.unitID] = {
3056
+ id = params.unitID,
3057
+ name = params.unitName,
3058
+ type = params.unitType,
3059
+ position = params.unitPosition,
3060
+ life = params.unitLife,
3061
+ event = params.eventName,
3062
+ time = timer.getTime()
3063
+ }
3064
+ else
3065
+ --logStr = logStr .. "Unit Data Not Available: One or more fields unavailable\n"
3066
+ end
3067
+
3068
+ if splash_damage_options.events_debug then
3069
+ env.info(logStr)
3070
+ end
3071
+ end, {
3072
+ eventName = eventName,
3073
+ unitID = unitID,
3074
+ unitName = unitName,
3075
+ unitType = unitType,
3076
+ unitPosition = unitPosition,
3077
+ unitLife = unitLife
3078
+ }, timer.getTime() + 0.1)
3079
+ return --Exit early to queue the event
3080
+ end
3081
+
3082
+ --Handle HIT and KILL events (use target or object)
3083
+ if (eventName == "HIT" or eventName == "KILL") and (eventData.target or eventData.object) then
3084
+ local tgt = eventData.target or eventData.object
3085
+ unitID = safeGet(function() return tgt:getID() end, "unavailable")
3086
+ unitName = safeGet(function() return tgt:getName() end, "unknown")
3087
+ unitType = safeGet(function() return tgt:getTypeName() end, "unknown")
3088
+ unitPosition = safeGet(function()
3089
+ local pos = tgt:getPosition().p
3090
+ return string.format("x=%.0f, y=%.0f, z=%.0f", pos.x, pos.y, pos.z)
3091
+ end, "unavailable")
3092
+ unitLife = safeGet(function() return tgt:getLife() end, "Alive")
3093
+ end
3094
+
3095
+ --Handle HIT event with delayed processing to capture last hit
3096
+ if eventName == "HIT" and unitID ~= "unavailable" then
3097
+ --Skip if life is 0 or below
3098
+ if type(unitLife) == "number" and unitLife <= 0 then
3099
+ logStr = logStr .. "Unit ID " .. unitID .. " has life <= 0, skipping HIT event\n"
3100
+ if splash_damage_options.events_debug then
3101
+ env.info(logStr)
3102
+ end
3103
+ return
3104
+ end
3105
+
3106
+ --Store event data temporarily
3107
+ local hitData = {
3108
+ unitID = unitID,
3109
+ unitName = unitName,
3110
+ unitType = unitType,
3111
+ unitPosition = unitPosition,
3112
+ unitLife = unitLife,
3113
+ eventName = eventName,
3114
+ timestamp = timer.getTime()
3115
+ }
3116
+
3117
+ --Schedule or reschedule processing after x seconds
3118
+ if not LogEventProcessedUnitTable[unitID] or LogEventProcessedUnitTable[unitID].event ~= "HIT" then
3119
+ LogEventProcessedUnitTable[unitID] = { event = "HIT", timerID = nil } --Initialize entry
3120
+ end
3121
+
3122
+ --Cancel previous timer if exists
3123
+ if LogEventProcessedUnitTable[unitID].timerID then
3124
+ timer.removeFunction(LogEventProcessedUnitTable[unitID].timerID)
3125
+ end
3126
+
3127
+ --Schedule new timer
3128
+ local timerID = timer.scheduleFunction(function(params)
3129
+ local logStr = "\n---EVENT: HIT ---\n"
3130
+
3131
+ --Check if unitID is already processed by another event
3132
+ if LogEventProcessedUnitTable[params.unitID] and LogEventProcessedUnitTable[params.unitID].event ~= "HIT" then
3133
+ logStr = logStr .. "Unit ID " .. params.unitID .. " already processed by " .. LogEventProcessedUnitTable[params.unitID].event .. " event\n"
3134
+ if splash_damage_options.events_debug then
3135
+ env.info(logStr)
3136
+ end
3137
+ return
3138
+ end
3139
+
3140
+ --Log and store unit data if valid
3141
+ if params.unitID ~= "unavailable" and params.unitName ~= "unavailable" and params.unitType ~= "unavailable" and params.unitPosition ~= "unavailable" and params.unitLife ~= "unavailable" then
3142
+ logStr = logStr .. "Stored Unit Data: ID=" .. params.unitID .. ", Name=" .. params.unitName .. ", Type=" .. params.unitType .. ", Position=" .. params.unitPosition .. ", Life=" .. params.unitLife .. "\n"
3143
+ logStr = logStr .. "Processing HIT event for unit " .. params.unitName .. " (ID: " .. params.unitID .. ") with life " .. params.unitLife .. "\n"
3144
+
3145
+ --Store in LogEventProcessedUnitTable
3146
+ LogEventProcessedUnitTable[params.unitID] = {
3147
+ id = params.unitID,
3148
+ name = params.unitName,
3149
+ type = params.unitType,
3150
+ position = params.unitPosition,
3151
+ life = params.unitLife,
3152
+ event = params.eventName,
3153
+ time = params.timestamp
3154
+ }
3155
+ else
3156
+ --logStr = logStr .. "Unit Data Not Available: One or more fields unavailable\n"
3157
+ end
3158
+
3159
+ if splash_damage_options.events_debug then
3160
+ env.info(logStr)
3161
+ end
3162
+ end, hitData, timer.getTime() + 0.15)
3163
+
3164
+ LogEventProcessedUnitTable[unitID].timerID = timerID
3165
+ return --Exit early to queue the hit
3166
+ elseif eventName == "HIT" then
3167
+ --logStr = logStr .. "Unit Data Not Available: One or more fields unavailable\n"
3168
+ if splash_damage_options.events_debug then
3169
+ env.info(logStr)
3170
+ end
3171
+ return
3172
+ end
3173
+
3174
+ --Check if unitID is already processed for KILL
3175
+ if unitID and LogEventProcessedUnitTable[unitID] and eventName == "KILL" then
3176
+ logStr = logStr .. "Unit ID " .. unitID .. " already processed in LogEventProcessedUnitTable\n"
3177
+ if splash_damage_options.events_debug then
3178
+ env.info(logStr)
3179
+ end
3180
+ return
3181
+ end
3182
+
3183
+ --Log and store unit data for KILL if valid
3184
+ if eventName == "KILL" and unitID ~= "unavailable" and unitName ~= "unavailable" and unitType ~= "unavailable" and unitPosition ~= "unavailable" and unitLife ~= "unavailable" then
3185
+ logStr = logStr .. "Stored Unit Data: ID=" .. unitID .. ", Name=" .. unitName .. ", Type=" .. unitType .. ", Position=" .. unitPosition .. ", Life=" .. unitLife .. "\n"
3186
+ logStr = logStr .. "Processing KILL event for unit " .. unitName .. " (ID: " .. unitID .. ") of type " .. unitType .. "\n"
3187
+
3188
+ --Store in LogEventProcessedUnitTable
3189
+ LogEventProcessedUnitTable[unitID] = {
3190
+ id = unitID,
3191
+ name = unitName,
3192
+ type = unitType,
3193
+ position = unitPosition,
3194
+ life = unitLife,
3195
+ event = eventName,
3196
+ time = timer.getTime()
3197
+ }
3198
+ elseif eventName == "KILL" then
3199
+ --logStr = logStr .. "Unit Data Not Available: One or more fields unavailable\n"
3200
+ end
3201
+
3202
+ if splash_damage_options.events_debug then
3203
+ env.info(logStr)
3204
+ end
3205
+ end
3206
+
3207
+
3208
+
3209
+ function WpnHandler:onEvent(event)
3210
+ protectedCall(onWpnEvent, event)
3211
+ if event.id == world.event.S_EVENT_HIT then
3212
+ logEvent("HIT", event)
3213
+ elseif event.id == world.event.S_EVENT_KILL then
3214
+ logEvent("KILL", event)
3215
+ protectedCall(onKillEvent, event)
3216
+ elseif event.id == world.event.S_EVENT_DEAD then
3217
+ logEvent("DEAD", event)
3218
+ end
3219
+ end
3220
+
3221
+ --kill feed event function
3222
+ function onKillEvent(event)
3223
+ if not splash_damage_options.killfeed_enable or event.id ~= world.event.S_EVENT_KILL then return end
3224
+
3225
+ local status, err = pcall(function()
3226
+ local killedUnit = event.target
3227
+ local killer = event.initiator
3228
+
3229
+ if not killedUnit then
3230
+ if splash_damage_options.killfeed_debug then
3231
+ env.info(string.format("KillFeed: Skipped, no target at %.2f", timer.getTime()))
3232
+ end
3233
+ return
3234
+ end
3235
+
3236
+ local unitName = safeGet(function() return killedUnit:getName() end, "unknown")
3237
+ local unitType = safeGet(function() return killedUnit:getTypeName() end, "unknown")
3238
+ local unitID = safeGet(function() return killedUnit:getID() end, "unavailable")
3239
+ local position = safeGet(function()
3240
+ local pos = killedUnit:getPoint()
3241
+ return {x = pos.x, y = pos.y, z = pos.z}
3242
+ end, {x=0, y=0, z=0})
3243
+
3244
+ if unitName == "unknown" or unitType == "unknown" or unitID == "unavailable" or unitID == 0 then
3245
+ if splash_damage_options.killfeed_debug then
3246
+ --env.info(string.format("KillFeed: Skipped unit ID %s with name %s and type %s at %.2f", tostring(unitID), unitName, unitType, timer.getTime()))
3247
+ end
3248
+ return
3249
+ end
3250
+
3251
+ -- Check if unitID is already in killfeedTable
3252
+ for _, entry in ipairs(killfeedTable) do
3253
+ if entry.unitID == unitID then
3254
+ if splash_damage_options.killfeed_debug then
3255
+ env.info(string.format("KillFeed: Skipped unit ID %s (%s) already in killfeedTable at %.2f", unitID, unitType, timer.getTime()))
3256
+ end
3257
+ return
3258
+ end
3259
+ end
3260
+
3261
+ local killerName = "Unknown"
3262
+ local killerUnitName = "Unknown"
3263
+ if killer then
3264
+ local status, unitNameResult = pcall(function() return killer:getName() end)
3265
+ if status and unitNameResult then
3266
+ killerUnitName = unitNameResult
3267
+ end
3268
+ local status, playerNameResult = pcall(function() return killer:getPlayerName() end)
3269
+ if status and playerNameResult then
3270
+ killerName = playerNameResult
3271
+ else
3272
+ local status, unitId = pcall(function() return killer:getID() end)
3273
+ if status and unitId then
3274
+ local playerList = net.get_player_list() or {}
3275
+ for _, pid in ipairs(playerList) do
3276
+ local pinfo = net.get_player_info(pid)
3277
+ if pinfo and pinfo.ucid then
3278
+ local slotUnitId = tonumber(pinfo.slot) or pinfo.slot
3279
+ if slotUnitId == unitId or pinfo.slot == killerUnitName then
3280
+ killerName = pinfo.name or killerUnitName
3281
+ break
3282
+ end
3283
+ end
3284
+ end
3285
+ end
3286
+ end
3287
+ if splash_damage_options.killfeed_debug then
3288
+ env.info(string.format("KillFeed: Killer UnitName: %s, PlayerName: %s, UnitID: %s, Type: %s, Slot: %s",
3289
+ killerUnitName, killerName, unitID, unitType, killer.getID and killer:getID() or "unknown"))
3290
+ end
3291
+ elseif splash_damage_options.killfeed_debug then
3292
+ env.info(string.format("KillFeed: Unit ID %s (%s) killed with no initiator at %.2f",
3293
+ unitID, unitType, timer.getTime()))
3294
+ end
3295
+
3296
+ --Log bc table state for direct kill only if Lekas integration is enabled
3297
+ if splash_damage_options.killfeed_debug and splash_damage_options.killfeed_lekas_foothold_integration then
3298
+ env.info("KillFeed: bc table state for direct kill: " .. (bc and "exists" or "nil"))
3299
+ env.info("KillFeed: bc.addTempStat: " .. (bc and bc.addTempStat and "exists" or "nil"))
3300
+ env.info("KillFeed: bc.context: " .. (bc and bc.context and "exists" or "nil"))
3301
+ if bc and bc.context then
3302
+ env.info("KillFeed: bc.context.playerContributions: " .. (bc.context.playerContributions and "exists" or "nil"))
3303
+ if bc.context.playerContributions then
3304
+ env.info("KillFeed: bc.context.playerContributions[2]: " .. (bc.context.playerContributions[2] and "exists" or "nil"))
3305
+ end
3306
+ end
3307
+ end
3308
+
3309
+ --Check if unitID is in splashKillfeedTable
3310
+ local splashIndex = nil
3311
+ for i, entry in ipairs(splashKillfeedTable) do
3312
+ if entry.unitId == unitID then
3313
+ splashIndex = i
3314
+ break
3315
+ end
3316
+ end
3317
+ if splashIndex then
3318
+ local dupeMsg = string.format("Duplicate kill: %s (%s) [ID: %s]", unitName, unitType, unitID)
3319
+ if splash_damage_options.killfeed_game_messages then
3320
+ local status, err = pcall(function()
3321
+ trigger.action.outTextForCoalition(2, dupeMsg, splash_damage_options.killfeed_game_message_duration)
3322
+ end)
3323
+ if not status then
3324
+ trigger.action.outText(dupeMsg, splash_damage_options.killfeed_game_message_duration)
3325
+ if splash_damage_options.killfeed_debug then
3326
+ env.info("KillFeed: Failed coalition message for duplicate: " .. tostring(err))
3327
+ end
3328
+ end
3329
+ end
3330
+ if splash_damage_options.killfeed_debug then
3331
+ env.info(string.format("KillFeed: %s at %.2f", dupeMsg, timer.getTime()))
3332
+ end
3333
+ table.remove(splashKillfeedTable, splashIndex)
3334
+ if splash_damage_options.killfeed_debug then
3335
+ env.info(string.format("SplashKillFeed: Removed duplicate entry for unit ID %s (%s) from splashKillfeedTable at %.2f",
3336
+ unitID, unitType, timer.getTime()))
3337
+ end
3338
+ else
3339
+ --Process direct kill contribution
3340
+ if killerName ~= "Unknown" and splash_damage_options.killfeed_lekas_foothold_integration then
3341
+ local status, result = pcall(function()
3342
+ local statName = "Ground Units"
3343
+ local points = 10
3344
+ if unitType:find("Plane") then
3345
+ statName = "Air"
3346
+ points = 30
3347
+ elseif unitType:find("Helicopter") then
3348
+ statName = "Helo"
3349
+ points = 30
3350
+ elseif unitType:find("SAM") then
3351
+ statName = "SAM"
3352
+ points = 30
3353
+ elseif unitType:find("Infantry") then
3354
+ statName = "Infantry"
3355
+ points = 10
3356
+ elseif unitType:find("Ship") then
3357
+ statName = "Ship"
3358
+ points = 250
3359
+ elseif unitType:find("Building") then
3360
+ statName = "Structure"
3361
+ points = 30
3362
+ end
3363
+ bc:addTempStat(killerName, statName, 1)
3364
+ if splash_damage_options.killfeed_debug then
3365
+ env.info(string.format("KillFeed: Added temp stat for %s: stat=%s, count=1", killerName, statName))
3366
+ end
3367
+ if bc.context and type(bc.context) == "table" and bc.context.playerContributions and type(bc.context.playerContributions) == "table" then
3368
+ bc.context.playerContributions[2] = bc.context.playerContributions[2] or {}
3369
+ local oldPoints = bc.context.playerContributions[2][killerName] or 0
3370
+ bc.context.playerContributions[2][killerName] = oldPoints + points
3371
+ if splash_damage_options.killfeed_debug then
3372
+ env.info(string.format("KillFeed: Updated contributions for %s: old=%d, new=%d, added=%d",
3373
+ killerName, oldPoints, bc.context.playerContributions[2][killerName], points))
3374
+ end
3375
+ else
3376
+ if splash_damage_options.killfeed_debug then
3377
+ env.info("KillFeed: Skipped contribution update for " .. killerName .. ": bc.context or bc.context.playerContributions is nil")
3378
+ end
3379
+ end
3380
+ end)
3381
+ if not status and splash_damage_options.killfeed_debug then
3382
+ env.info("KillFeed: Error processing direct kill for unitId=" .. tostring(unitID) .. ": " .. tostring(result))
3383
+ end
3384
+ end
3385
+ end
3386
+
3387
+ if unitType ~= "Unknown" then
3388
+ table.insert(killfeedTable, {
3389
+ unitName = unitName,
3390
+ unitType = unitType,
3391
+ unitID = unitID,
3392
+ killer = killerName,
3393
+ time = timer.getTime(),
3394
+ position = position
3395
+ })
3396
+
3397
+ if splash_damage_options.killfeed_game_messages and not splashIndex then
3398
+ local msg = string.format("%s destroyed by %s", unitType, killerName)
3399
+ local status, err = pcall(function()
3400
+ trigger.action.outTextForCoalition(2, msg, splash_damage_options.killfeed_game_message_duration)
3401
+ end)
3402
+
3403
+ if not status then
3404
+ trigger.action.outText(msg, splash_damage_options.killfeed_game_message_duration)
3405
+ if splash_damage_options.killfeed_debug then
3406
+ env.info("KillFeed: Failed coalition message: " .. tostring(err))
3407
+ end
3408
+ end
3409
+ end
3410
+
3411
+ if splash_damage_options.killfeed_debug then
3412
+ env.info(string.format("KillFeed: Recorded %s destroyed by %s [ID: %s] at %.2f",
3413
+ unitType, killerName, unitID, timer.getTime()))
3414
+ end
3415
+ end
3416
+ end)
3417
+
3418
+ if not status and splash_damage_options.killfeed_debug then
3419
+ env.info("KillFeed: Error: " .. tostring(err))
3420
+ end
3421
+ end
3422
+
3423
+
3424
+ --kill feed event function
3425
+ --kill feed event function
3426
+ function onDeadEvent(event)
3427
+ if not splash_damage_options.killfeed_enable or event.id ~= world.event.S_EVENT_DEAD then return end
3428
+
3429
+ local status, err = pcall(function()
3430
+ local deadUnit = event.initiator
3431
+
3432
+ if not deadUnit then
3433
+ if splash_damage_options.killfeed_debug then
3434
+ env.info(string.format("DeadFeed: Skipped, no initiator at %.2f", timer.getTime()))
3435
+ end
3436
+ return
3437
+ end
3438
+
3439
+ --Extract unit data using safeGet, matching logEvent defaults
3440
+ local unitID = safeGet(function() return deadUnit:getID() end, "unavailable")
3441
+ local unitName = safeGet(function() return deadUnit:getName() end, "unknown")
3442
+ local unitType = safeGet(function() return deadUnit:getTypeName() end, "unknown")
3443
+ local position = safeGet(function()
3444
+ local pos = deadUnit:getPoint()
3445
+ return {x = pos.x, y = pos.y, z = pos.z}
3446
+ end, {x=0, y=0, z=0})
3447
+
3448
+ --Skip invalid units (unknown type, unavailable ID, or scenery with ID 0)
3449
+ if unitName == "unknown" or unitType == "unknown" or unitID == "unavailable" or unitID == 0 then
3450
+ if splash_damage_options.killfeed_debug then
3451
+ env.info(string.format("DeadFeed: Skipped unit ID %s with name %s and type %s at %.2f", tostring(unitID), unitName, unitType, timer.getTime()))
3452
+ end
3453
+ return
3454
+ end
3455
+
3456
+ -- Check if unitID is already in killfeedTable before scheduling
3457
+ for _, entry in ipairs(killfeedTable) do
3458
+ if entry.unitID == unitID then
3459
+ if splash_damage_options.killfeed_debug then
3460
+ env.info(string.format("DeadFeed: Skipped unit ID %s (%s) already in killfeedTable at %.2f", unitID, unitType, timer.getTime()))
3461
+ end
3462
+ return
3463
+ end
3464
+ end
3465
+
3466
+ --Delay processing by 2 seconds to allow S_EVENT_KILL to take precedence
3467
+ timer.scheduleFunction(function(params)
3468
+ local unitID = params.unitID
3469
+ local unitName = params.unitName
3470
+ local unitType = params.unitType
3471
+ local position = params.position
3472
+ local currentTime = timer.getTime()
3473
+
3474
+ -- Re-check killfeedTable after delay to ensure no race condition
3475
+ for _, entry in ipairs(killfeedTable) do
3476
+ if entry.unitID == unitID then
3477
+ if splash_damage_options.killfeed_debug then
3478
+ env.info(string.format("DeadFeed: Skipped unit ID %s (%s) already in killfeedTable at %.2f", unitID, unitType, currentTime))
3479
+ end
3480
+ return
3481
+ end
3482
+ end
3483
+
3484
+ --Remove from splashKillfeedTable if present
3485
+ local splashIndex = nil
3486
+ for i, entry in ipairs(splashKillfeedTable) do
3487
+ if entry.unitId == unitID then
3488
+ splashIndex = i
3489
+ break
3490
+ end
3491
+ end
3492
+ if splashIndex then
3493
+ table.remove(splashKillfeedTable, splashIndex)
3494
+ if splash_damage_options.killfeed_debug then
3495
+ env.info(string.format("DeadFeed: Removed unit ID %s (%s) from splashKillfeedTable at %.2f", unitID, unitType, currentTime))
3496
+ end
3497
+ end
3498
+
3499
+ --Remove from splashKillfeedTemp if present
3500
+ local tempIndex = nil
3501
+ for i, entry in ipairs(splashKillfeedTemp) do
3502
+ if entry.unitId == unitID then
3503
+ tempIndex = i
3504
+ break
3505
+ end
3506
+ end
3507
+ if tempIndex then
3508
+ table.remove(splashKillfeedTemp, tempIndex)
3509
+ if splash_damage_options.killfeed_debug then
3510
+ env.info(string.format("DeadFeed: Removed unit ID %s (%s) from splashKillfeedTemp at %.2f", unitID, unitType, currentTime))
3511
+ end
3512
+ end
3513
+
3514
+ --Add to killfeedTable
3515
+ table.insert(killfeedTable, {
3516
+ unitName = unitName,
3517
+ unitType = unitType,
3518
+ unitID = unitID,
3519
+ killer = "unknown",
3520
+ time = currentTime,
3521
+ position = position
3522
+ })
3523
+
3524
+ --Display in-game message
3525
+ if splash_damage_options.killfeed_game_messages then
3526
+ local msg = string.format("%s destroyed", unitType)
3527
+ local status, err = pcall(function()
3528
+ trigger.action.outTextForCoalition(2, msg, splash_damage_options.killfeed_game_message_duration)
3529
+ end)
3530
+ if not status then
3531
+ trigger.action.outText(msg, splash_damage_options.killfeed_game_message_duration)
3532
+ if splash_damage_options.killfeed_debug then
3533
+ env.info("DeadFeed: Failed coalition message: " .. tostring(err))
3534
+ end
3535
+ end
3536
+ end
3537
+
3538
+ if splash_damage_options.killfeed_debug then
3539
+ env.info(string.format("DeadFeed: Recorded %s destroyed [ID: %s] at %.2f", unitType, unitID, currentTime))
3540
+ end
3541
+ end, {
3542
+ unitID = unitID,
3543
+ unitName = unitName,
3544
+ unitType = unitType,
3545
+ position = position
3546
+ }, timer.getTime() + 2)
3547
+ end)
3548
+
3549
+ if not status and splash_damage_options.killfeed_debug then
3550
+ env.info("DeadFeed: Error: " .. tostring(err))
3551
+ end
3552
+ end
3553
+
3554
+ function explodeObject(args)
3555
+ local point = args[1]
3556
+ local distance = args[2]
3557
+ local power = args[3]
3558
+ trigger.action.explosion(point, power)
3559
+ end
3560
+
3561
+ function blastWave(_point, _radius, weapon, power, isShapedCharge)
3562
+ if isShapedCharge then
3563
+ _radius = _radius * splash_damage_options.shaped_charge_multiplier
3564
+ end
3565
+ if splash_damage_options.use_dynamic_blast_radius then
3566
+ local dynamicRadius = math.pow(power, 1/3) * 5 * splash_damage_options.dynamic_blast_radius_modifier
3567
+ _radius = isShapedCharge and dynamicRadius * splash_damage_options.shaped_charge_multiplier or dynamicRadius
3568
+ end
3569
+ if splash_damage_options.debug then
3570
+ debugMsg("blastWave called for weapon '" .. weapon .. "' at X: " .. _point.x .. ", Y: " .. _point.y .. ", Z: " .. _point.z .. " with power " .. power .. " and radius " .. _radius .. "m")
3571
+ end
3572
+
3573
+ local foundUnits = {}
3574
+ local volS = {
3575
+ id = world.VolumeType.SPHERE,
3576
+ params = {
3577
+ point = _point,
3578
+ radius = _radius
3579
+ }
3580
+ }
3581
+
3582
+ local ifFound = function(foundObject, val)
3583
+ if foundObject:getDesc().category == Unit.Category.GROUND_UNIT and foundObject:getCategory() == Object.Category.UNIT then
3584
+ foundUnits[#foundUnits + 1] = foundObject
3585
+ end
3586
+ if foundObject:getDesc().category == Unit.Category.GROUND_UNIT and splash_damage_options.blast_stun then
3587
+ --suppressUnit(foundObject, 2, weapon) --Not implemented, commented out
3588
+ end
3589
+ if splash_damage_options.wave_explosions then
3590
+ local obj = foundObject
3591
+ local obj_location = obj:getPoint()
3592
+ local dist = getDistance(_point, obj_location)
3593
+ if dist > 1 then --Avoid re-exploding at exact impact point
3594
+ local timing = dist / 500
3595
+ if obj:isExist() and tableHasKey(obj:getDesc(), "box") then
3596
+ local length = (obj:getDesc().box.max.x + math.abs(obj:getDesc().box.min.x))
3597
+ local height = (obj:getDesc().box.max.y + math.abs(obj:getDesc().box.min.y))
3598
+ local depth = (obj:getDesc().box.max.z + math.abs(obj:getDesc().box.min.z))
3599
+ local _length = length
3600
+ local _depth = depth
3601
+ if depth > length then
3602
+ _length = depth
3603
+ _depth = length
3604
+ end
3605
+ local surface_distance = dist - _depth / 2
3606
+ local scaled_power_factor = 0.006 * power + 1
3607
+ local intensity = (power * scaled_power_factor) / (4 * math.pi * surface_distance^2)
3608
+ --Apply ground ordnance blastwave modifier
3609
+ local weaponData = explTable[weapon] or {}
3610
+ if splash_damage_options.track_groundunitordnance and weaponData.groundordnance then
3611
+ intensity = intensity * splash_damage_options.groundunitordnance_blastwave_modifier
3612
+ if splash_damage_options.track_groundunitordnance_debug then
3613
+ debugMsg("Applied groundunitordnance_blastwave_modifier " .. splash_damage_options.groundunitordnance_blastwave_modifier .. " to " .. weapon .. ", intensity now: " .. intensity)
3614
+ end
3615
+ end
3616
+ local surface_area = _length * height
3617
+ local damage_for_surface = intensity * surface_area
3618
+ if splash_damage_options.debug then
3619
+ debugMsg("Processing unit '" .. obj:getTypeName() .. "' at dist=" .. string.format("%.1f", dist) .. "m: intensity=" .. string.format("%.4f", intensity) .. ", surface_area=" .. string.format("%.2f", surface_area) .. ", damage_for_surface=" .. string.format("%.4f", damage_for_surface))
3620
+ end
3621
+ if damage_for_surface > splash_damage_options.cascade_damage_threshold then
3622
+ local explosion_size = damage_for_surface
3623
+ if obj:getDesc().category == Unit.Category.STRUCTURE then
3624
+ explosion_size = intensity * splash_damage_options.static_damage_boost
3625
+ end
3626
+ if explosion_size > power then explosion_size = power end
3627
+ local triggerExplosion = false
3628
+ if splash_damage_options.always_cascade_explode then
3629
+ triggerExplosion = true
3630
+ if splash_damage_options.debug then
3631
+ debugMsg("Triggering secondary explosion for '" .. obj:getTypeName() .. "' due to always_cascade_explode")
3632
+ end
3633
+ else
3634
+ if obj:getDesc().life then
3635
+ local health = obj:getLife() or 0
3636
+ local maxHealth = obj:getDesc().life or 1
3637
+ local healthPercent = (health / maxHealth) * 100
3638
+ if splash_damage_options.debug then
3639
+ debugMsg("Health check for '" .. obj:getTypeName() .. "': " .. health .. "/" .. maxHealth .. " (" .. string.format("%.2f", healthPercent) .. "%) vs threshold " .. splash_damage_options.cascade_explode_threshold)
3640
+ end
3641
+ if healthPercent <= splash_damage_options.cascade_explode_threshold then
3642
+ triggerExplosion = true
3643
+ end
3644
+ else
3645
+ triggerExplosion = true
3646
+ if splash_damage_options.debug then
3647
+ debugMsg("Triggering secondary explosion for '" .. obj:getTypeName() .. "' (no life data)")
3648
+ end
3649
+ end
3650
+ if not triggerExplosion and obj:getDesc().category == Unit.Category.GROUND_UNIT then
3651
+ local health = obj:getLife() or 0
3652
+ if health <= 0 then
3653
+ triggerExplosion = true
3654
+ if splash_damage_options.debug then
3655
+ debugMsg("Triggering secondary explosion for '" .. obj:getTypeName() .. "' (health <= 0)")
3656
+ end
3657
+ end
3658
+ end
3659
+ end
3660
+ --Queue cargo effects for units below
3661
+ if obj:getDesc().life then
3662
+ local healthPercent = (obj:getLife() / obj:getDesc().life) * 100
3663
+ local cargoData = cargoUnits[obj:getTypeName()]
3664
+ if cargoData and healthPercent <= splash_damage_options.cargo_damage_threshold and splash_damage_options.enable_cargo_effects then
3665
+ local cargoPower = power * cargoData.cargoExplosionMult
3666
+ table.insert(cargoEffectsQueue, {
3667
+ name = obj:getTypeName(),
3668
+ distance = dist,
3669
+ coords = obj_location,
3670
+ power = cargoPower,
3671
+ explosion = cargoData.cargoExplosion,
3672
+ cookOff = cargoData.cargoCookOff,
3673
+ cookOffCount = cargoData.cookOffCount,
3674
+ cookOffPower = cargoData.cookOffPower,
3675
+ cookOffDuration = cargoData.cookOffDuration,
3676
+ cookOffRandomTiming = cargoData.cookOffRandomTiming,
3677
+ cookOffPowerRandom = cargoData.cookOffPowerRandom,
3678
+ isTanker = cargoData.isTanker,
3679
+ flameSize = cargoData.flameSize,
3680
+ flameDuration = cargoData.flameDuration
3681
+ })
3682
+ if splash_damage_options.debug then
3683
+ debugMsg("Queued cargo effect for '" .. obj:getTypeName() .. "' with power " .. cargoPower)
3684
+ end
3685
+ end
3686
+ end
3687
+ if triggerExplosion then
3688
+ local final_power = explosion_size * splash_damage_options.cascade_scaling
3689
+ if splash_damage_options.debug then
3690
+ debugMsg("Scheduling secondary explosion for '" .. obj:getTypeName() .. "' at X: " .. obj_location.x .. ", Y: " .. obj_location.y .. ", Z: " .. obj_location.z .. ", dist=" .. string.format("%.1f", dist) .. "m, power=" .. string.format("%.2f", final_power))
3691
+ end
3692
+ if splash_damage_options.track_groundunitordnance_debug and weaponData.groundordnance then
3693
+ debugMsg("Calculated power for '" .. obj:getTypeName() .. "' at X: " .. obj_location.x .. ", Y: " .. obj_location.y .. ", Z: " .. obj_location.z .. ", distance " .. dist .. "m: " .. final_power)
3694
+ end
3695
+ local playerName = tracked_weapons[weapon] and tracked_weapons[weapon].init or "unknown"
3696
+ timer.scheduleFunction(function(args)
3697
+ local obj = args[1]
3698
+ local playerName = args[2]
3699
+ if obj:isExist() and obj:getLife() <= 0 then
3700
+ debugMsg("Unit '" .. obj:getTypeName() .. "' destroyed by secondary explosion, credited to player: " .. playerName)
3701
+ end
3702
+ end, {obj, playerName}, timer.getTime() + timing + 0.1)
3703
+ timer.scheduleFunction(explodeObject, {obj_location, dist, final_power}, timer.getTime() + timing)
3704
+ else
3705
+ if splash_damage_options.debug then
3706
+ debugMsg("No secondary explosion for '" .. obj:getTypeName() .. "': health above threshold (" .. string.format("%.2f", (obj:getLife() / obj:getDesc().life) * 100) .. "% > " .. splash_damage_options.cascade_explode_threshold .. "%)")
3707
+ end
3708
+ end
3709
+ else
3710
+ if splash_damage_options.debug then
3711
+ debugMsg("No secondary explosion for '" .. obj:getTypeName() .. "': damage_for_surface=" .. string.format("%.4f", damage_for_surface) .. " below threshold " .. splash_damage_options.cascade_damage_threshold)
3712
+ end
3713
+ end
3714
+ end
3715
+ end
3716
+ end
3717
+ return true
3718
+ end
3719
+
3720
+ --Search all relevant object categories
3721
+ if splash_damage_options.debug then
3722
+ debugMsg("Scanning for objects within " .. _radius .. "m radius")
3723
+ end
3724
+ world.searchObjects(Object.Category.UNIT, volS, ifFound)
3725
+ world.searchObjects(Object.Category.STATIC, volS, ifFound)
3726
+ world.searchObjects(Object.Category.SCENERY, volS, ifFound)
3727
+ world.searchObjects(Object.Category.CARGO, volS, ifFound)
3728
+ if splash_damage_options.debug then
3729
+ debugMsg("Found " .. #foundUnits .. " ground units for damage modeling")
3730
+ end
3731
+ --Apply damage model if enabled
3732
+ if splash_damage_options.damage_model then
3733
+ timer.scheduleFunction(modelUnitDamage, foundUnits, timer.getTime() + 1.5)
3734
+ end
3735
+ end
3736
+
3737
+ function modelUnitDamage(units)
3738
+ for i, unit in ipairs(units) do
3739
+ if unit:isExist() then
3740
+ local health = (unit:getLife() / unit:getDesc().life) * 100
3741
+ if unit:hasAttribute("Infantry") and health > 0 then
3742
+ if health <= splash_damage_options.infantry_cant_fire_health then
3743
+ unit:getController():setOption(AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD)
3744
+ end
3745
+ end
3746
+ if unit:getDesc().category == Unit.Category.GROUND_UNIT and (not unit:hasAttribute("Infantry")) and health > 0 then
3747
+ if health <= splash_damage_options.unit_cant_fire_health then
3748
+ unit:getController():setOption(AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD)
3749
+ --gameMsg(unit:getTypeName() .. " weapons disabled")
3750
+ end
3751
+ if health <= splash_damage_options.unit_disabled_health and health > 0 then
3752
+ unit:getController():setTask({id = 'Hold', params = {}})
3753
+ unit:getController():setOnOff(false)
3754
+ --gameMsg(unit:getTypeName() .. " disabled")
3755
+ end
3756
+ end
3757
+ end
3758
+ end
3759
+ end
3760
+
3761
+ function updateSplashDamageSetting(setting, increment)
3762
+ if not splash_damage_options[setting] then
3763
+ env.info("Error: Setting " .. setting .. " does not exist.")
3764
+ return
3765
+ end
3766
+
3767
+ local newValue = math.max(0, splash_damage_options[setting] + increment)
3768
+ env.info("Updating " .. setting .. " from " .. tostring(splash_damage_options[setting]) .. " to " .. tostring(newValue))
3769
+ splash_damage_options[setting] = newValue
3770
+ trigger.action.outText("Updated " .. setting .. " to: " .. tostring(splash_damage_options[setting]), 5)
3771
+ end
3772
+
3773
+ function toggleSplashDamageSetting(setting)
3774
+ splash_damage_options[setting] = not splash_damage_options[setting]
3775
+ trigger.action.outText("Toggled " .. setting .. " to: " .. tostring(splash_damage_options[setting]), 5)
3776
+
3777
+ if setting == "enable_radio_menu" then
3778
+ if splash_damage_options.enable_radio_menu then
3779
+ addSplashDamageMenu()
3780
+ else
3781
+ missionCommands.removeItem(splash_damage_menu)
3782
+ splash_damage_menu = nil
3783
+ end
3784
+ end
3785
+ end
3786
+
3787
+ function addValueAdjustmentCommands(menu, setting, increments)
3788
+ for _, inc in ipairs(increments) do
3789
+ missionCommands.addCommand("+" .. inc, menu, updateSplashDamageSetting, setting, inc)
3790
+ missionCommands.addCommand("-" .. inc, menu, updateSplashDamageSetting, setting, -inc)
3791
+ end
3792
+ end
1971
3793
 
1972
3794
  function exitSplashDamageMenu()
1973
3795
  if splash_damage_menu then
@@ -1985,129 +3807,305 @@ function addSplashDamageMenu()
1985
3807
 
1986
3808
  splash_damage_menu = missionCommands.addSubMenu("Splash Damage Settings")
1987
3809
 
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)
3810
+ --1. Debug and Messages
3811
+ local debugMenu = missionCommands.addSubMenu("Debug and Messages", splash_damage_menu)
3812
+ local debugSettings = {
3813
+ "game_messages",
3814
+ "debug",
3815
+ "weapon_missing_message",
3816
+ "track_pre_explosion_debug",
3817
+ "track_groundunitordnance_debug",
3818
+ "napalm_unitdamage_debug"
3819
+ }
3820
+ for _, setting in ipairs(debugSettings) do
3821
+ missionCommands.addCommand("Toggle " .. setting:gsub("_", " "), debugMenu, toggleSplashDamageSetting, setting)
3822
+ end
3823
+
3824
+ --2. Basic Splash Settings
3825
+ local splashMenu = missionCommands.addSubMenu("Basic Splash Settings", splash_damage_menu)
3826
+ local splashToggles = {
3827
+ "wave_explosions",
3828
+ "larger_explosions",
3829
+ "damage_model",
3830
+ "blast_stun"
3831
+ }
3832
+ for _, setting in ipairs(splashToggles) do
3833
+ missionCommands.addCommand("Toggle " .. setting:gsub("_", " "), splashMenu, toggleSplashDamageSetting, setting)
3834
+ end
3835
+ local staticDamageMenu = missionCommands.addSubMenu("Static Damage Boost", splashMenu)
3836
+ addValueAdjustmentCommands(staticDamageMenu, "static_damage_boost", {100, 500, 1000})
3837
+
3838
+ --Submenu: Scaling and Cascading
3839
+ local scalingMenu = missionCommands.addSubMenu("Scaling and Cascading", splashMenu)
3840
+ local scalingSettings = {
3841
+ {name = "Overall Scaling", setting = "overall_scaling", increments = {0.1, 0.5, 1}},
3842
+ {name = "Rocket Multiplier", setting = "rocket_multiplier", increments = {0.1, 0.5, 1}},
3843
+ {name = "Cascade Scaling", setting = "cascade_scaling", increments = {0.1, 0.5, 1}},
3844
+ {name = "Cascade Damage Threshold", setting = "cascade_damage_threshold", increments = {0.01, 0.05, 0.1}},
3845
+ {name = "Cascade Explode Threshold", setting = "cascade_explode_threshold", increments = {5, 10, 25}}
3846
+ }
3847
+ for _, s in ipairs(scalingSettings) do
3848
+ local subMenu = missionCommands.addSubMenu(s.name, scalingMenu)
3849
+ addValueAdjustmentCommands(subMenu, s.setting, s.increments)
3850
+ end
3851
+ missionCommands.addCommand("Toggle Always Cascade Explode", scalingMenu, toggleSplashDamageSetting, "always_cascade_explode")
3852
+
3853
+ --Submenu: Blast Radius & Shaped Charge
3854
+ local blastMenu = missionCommands.addSubMenu("Blast Radius & Shaped Charge", splashMenu)
3855
+ local blastRadiusMenu = missionCommands.addSubMenu("Blast Search Radius", blastMenu)
3856
+ addValueAdjustmentCommands(blastRadiusMenu, "blast_search_radius", {5, 10, 25})
3857
+ missionCommands.addCommand("Toggle Dynamic Blast Radius", blastMenu, toggleSplashDamageSetting, "use_dynamic_blast_radius")
3858
+ local dynamicBlastMenu = missionCommands.addSubMenu("Dynamic Blast Radius Modifier", blastMenu)
3859
+ addValueAdjustmentCommands(dynamicBlastMenu, "dynamic_blast_radius_modifier", {0.1, 0.5, 1})
3860
+ missionCommands.addCommand("Toggle Shaped Charge Effects", blastMenu, toggleSplashDamageSetting, "apply_shaped_charge_effects")
3861
+ local shapedChargeMenu = missionCommands.addSubMenu("Shaped Charge Multiplier", blastMenu)
3862
+ addValueAdjustmentCommands(shapedChargeMenu, "shaped_charge_multiplier", {0.1, 0.5, 1})
3863
+
3864
+ --Submenu: Units
3865
+ local unitsMenu = missionCommands.addSubMenu("Units", splashMenu)
3866
+ local unitSettings = {
3867
+ {name = "Unit Disabled Health", setting = "unit_disabled_health", increments = {5, 10, 25}},
3868
+ {name = "Unit Can't Fire Health", setting = "unit_cant_fire_health", increments = {5, 10, 25}},
3869
+ {name = "Infantry Can't Fire Health", setting = "infantry_cant_fire_health", increments = {5, 10, 25}}
3870
+ }
3871
+ for _, s in ipairs(unitSettings) do
3872
+ local subMenu = missionCommands.addSubMenu(s.name, unitsMenu)
3873
+ addValueAdjustmentCommands(subMenu, s.setting, s.increments)
3874
+ end
3875
+
3876
+ --Submenu: Ground Ordnance Tracking
3877
+ local groundOrdnanceMenu = missionCommands.addSubMenu("Ground Ordnance Tracking", splashMenu)
3878
+ missionCommands.addCommand("Toggle Ground Ordnance Tracking", groundOrdnanceMenu, toggleSplashDamageSetting, "track_groundunitordnance")
3879
+ local groundSettings = {
3880
+ {name = "Damage Modifier", setting = "groundunitordnance_damage_modifier", increments = {0.1, 0.5, 1}},
3881
+ {name = "Blastwave Modifier", setting = "groundunitordnance_blastwave_modifier", increments = {0.1, 0.5, 1}},
3882
+ {name = "Max Tracked Count", setting = "groundunitordnance_maxtrackedcount", increments = {5, 10, 25}}
3883
+ }
3884
+ for _, s in ipairs(groundSettings) do
3885
+ local subMenu = missionCommands.addSubMenu(s.name, groundOrdnanceMenu)
3886
+ addValueAdjustmentCommands(subMenu, s.setting, s.increments)
3887
+ end
3888
+ missionCommands.addCommand("Toggle 50m Scan", groundOrdnanceMenu, toggleSplashDamageSetting, "scan_50m_for_groundordnance")
3889
+
3890
+ --3. Cargo Cook-off & Fuel Explosion
3891
+ local cargoMenu = missionCommands.addSubMenu("Cargo Cook-off & Fuel Explosion", splash_damage_menu)
3892
+ missionCommands.addCommand("Toggle Track Pre-Explosion", cargoMenu, toggleSplashDamageSetting, "track_pre_explosion")
3893
+ missionCommands.addCommand("Toggle Cargo Effects", cargoMenu, toggleSplashDamageSetting, "enable_cargo_effects")
3894
+ local cargoThresholdMenu = missionCommands.addSubMenu("Cargo Damage Threshold", cargoMenu)
3895
+ addValueAdjustmentCommands(cargoThresholdMenu, "cargo_damage_threshold", {5, 10, 25})
3896
+ missionCommands.addCommand("Toggle Debris Effects", cargoMenu, toggleSplashDamageSetting, "debris_effects")
3897
+ local debrisSettings = {
3898
+ {name = "Debris Power", setting = "debris_power", increments = {1, 5, 10}},
3899
+ {name = "Min Debris Count", setting = "debris_count_min", increments = {1, 5, 10}},
3900
+ {name = "Max Debris Count", setting = "debris_count_max", increments = {1, 5, 10}},
3901
+ {name = "Max Debris Distance", setting = "debris_max_distance", increments = {1, 5, 10}}
3902
+ }
3903
+ for _, s in ipairs(debrisSettings) do
3904
+ local subMenu = missionCommands.addSubMenu(s.name, cargoMenu)
3905
+ addValueAdjustmentCommands(subMenu, s.setting, s.increments)
3906
+ end
3907
+
3908
+ --Submenu: Cook-off Flares
3909
+ local flareMenu = missionCommands.addSubMenu("Cook-off Flares", cargoMenu)
3910
+ missionCommands.addCommand("Toggle Cook-off Flares", flareMenu, toggleSplashDamageSetting, "cookoff_flares_enabled")
3911
+ local flareColorMenu = missionCommands.addSubMenu("Flare Color", flareMenu)
3912
+ local flareColors = {
3913
+ {name = "Green", value = 0},
3914
+ {name = "White", value = 1},
3915
+ {name = "Red", value = 2},
3916
+ {name = "Yellow", value = 3}
3917
+ }
3918
+ for _, color in ipairs(flareColors) do
3919
+ missionCommands.addCommand(color.name, flareColorMenu, function()
3920
+ splash_damage_options.cookoff_flare_color = color.value
3921
+ trigger.action.outText("Cook-off flare color set to " .. color.name, 5)
2085
3922
  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)
3923
+ end
3924
+ local flareCountMenu = missionCommands.addSubMenu("Flare Count Modifier", flareMenu)
3925
+ addValueAdjustmentCommands(flareCountMenu, "cookoff_flare_count_modifier", {0.1, 0.5, 1})
3926
+ local flareOffsetMenu = missionCommands.addSubMenu("Flare Offset", flareMenu)
3927
+ addValueAdjustmentCommands(flareOffsetMenu, "cookoff_flare_offset", {1, 5, 10})
3928
+
3929
+ --Submenu: All Vehicles Options
3930
+ local allVehiclesMenu = missionCommands.addSubMenu("All Vehicles Options", cargoMenu)
3931
+ local vehicleToggles = {
3932
+ "smokeandcookoffeffectallvehicles",
3933
+ "allunits_enable_smoke",
3934
+ "allunits_enable_cookoff"
3935
+ }
3936
+ for _, setting in ipairs(vehicleToggles) do
3937
+ missionCommands.addCommand("Toggle " .. setting:gsub("_", " "), allVehiclesMenu, toggleSplashDamageSetting, setting)
3938
+ end
3939
+ local vehicleSettings = {
3940
+ {name = "Explosion Power", setting = "allunits_explode_power", increments = {5, 10, 25}},
3941
+ {name = "Default Flame Size", setting = "allunits_default_flame_size", increments = {1, 5, 10}},
3942
+ {name = "Default Flame Duration", setting = "allunits_default_flame_duration", increments = {5, 10, 25}},
3943
+ {name = "Cook-off Count", setting = "allunits_cookoff_count", increments = {1, 5, 10}},
3944
+ {name = "Cook-off Duration", setting = "allunits_cookoff_duration", increments = {5, 10, 25}},
3945
+ {name = "Cook-off Power", setting = "allunits_cookoff_power", increments = {5, 10, 25}},
3946
+ {name = "Cook-off Power Random", setting = "allunits_cookoff_powerrandom", increments = {5, 10, 25}}
3947
+ }
3948
+ for _, s in ipairs(vehicleSettings) do
3949
+ local subMenu = missionCommands.addSubMenu(s.name, allVehiclesMenu)
3950
+ addValueAdjustmentCommands(subMenu, s.setting, s.increments)
3951
+ end
3952
+
3953
+ --4. Ordnance Protection & Cluster
3954
+ local ordnanceMenu = missionCommands.addSubMenu("Ordnance Protection & Cluster", splash_damage_menu)
3955
+ local ordnanceToggles = {
3956
+ "ordnance_protection",
3957
+ "detect_ordnance_destruction",
3958
+ "snap_to_ground_if_destroyed_by_large_explosion",
3959
+ "recent_large_explosion_snap"
3960
+ }
3961
+ for _, setting in ipairs(ordnanceToggles) do
3962
+ missionCommands.addCommand("Toggle " .. setting:gsub("_", " "), ordnanceMenu, toggleSplashDamageSetting, setting)
3963
+ end
3964
+ local ordnanceSettings = {
3965
+ {name = "Ordnance Protection Radius", setting = "ordnance_protection_radius", increments = {5, 10, 25}},
3966
+ {name = "Max Snapped Height", setting = "max_snapped_height", increments = {5, 10, 25}},
3967
+ {name = "Recent Explosion Range", setting = "recent_large_explosion_range", increments = {5, 10, 25}},
3968
+ {name = "Recent Explosion Time", setting = "recent_large_explosion_time", increments = {1, 5, 10}}
3969
+ }
3970
+ for _, s in ipairs(ordnanceSettings) do
3971
+ local subMenu = missionCommands.addSubMenu(s.name, ordnanceMenu)
3972
+ addValueAdjustmentCommands(subMenu, s.setting, s.increments)
3973
+ end
3974
+
3975
+ --Submenu: Cluster Bombs
3976
+ local clusterMenu = missionCommands.addSubMenu("Cluster Bombs", ordnanceMenu)
3977
+ missionCommands.addCommand("Toggle Cluster Enabled", clusterMenu, toggleSplashDamageSetting, "cluster_enabled")
3978
+ local clusterSettings = {
3979
+ {name = "Cluster Base Length", setting = "cluster_base_length", increments = {25, 50, 100}},
3980
+ {name = "Cluster Base Width", setting = "cluster_base_width", increments = {25, 50, 100}},
3981
+ {name = "Cluster Max Length", setting = "cluster_max_length", increments = {25, 50, 100}},
3982
+ {name = "Cluster Max Width", setting = "cluster_max_width", increments = {25, 50, 100}},
3983
+ {name = "Cluster Min Length", setting = "cluster_min_length", increments = {25, 50, 100}},
3984
+ {name = "Cluster Min Width", setting = "cluster_min_width", increments = {25, 50, 100}},
3985
+ {name = "Bomblet Damage Modifier", setting = "cluster_bomblet_damage_modifier", increments = {1, 5, 10}}
3986
+ }
3987
+ for _, s in ipairs(clusterSettings) do
3988
+ local subMenu = missionCommands.addSubMenu(s.name, clusterMenu)
3989
+ addValueAdjustmentCommands(subMenu, s.setting, s.increments)
3990
+ end
3991
+ missionCommands.addCommand("Toggle Bomblet Reduction", clusterMenu, toggleSplashDamageSetting, "cluster_bomblet_reductionmodifier")
3992
+
3993
+ --5. Giant Explosions
3994
+ local giantExplosionMenu = missionCommands.addSubMenu("Giant Explosions", splash_damage_menu)
3995
+ local giantToggles = {
3996
+ "giant_explosion_enabled",
3997
+ "giant_explosion_target_static",
3998
+ "giantexplosion_ondamage",
3999
+ "giantexplosion_ondeath",
4000
+ }
4001
+ for _, setting in ipairs(giantToggles) do
4002
+ missionCommands.addCommand("Toggle " .. setting:gsub("_", " "), giantExplosionMenu, toggleSplashDamageSetting, setting)
4003
+ end
4004
+ local giantSettings = {
4005
+ {name = "Explosion Power", setting = "giant_explosion_power", increments = {500, 1000, 2000}},
4006
+ {name = "Size Scale", setting = "giant_explosion_scale", increments = {0.1, 0.5, 1, 2}},
4007
+ {name = "Duration", setting = "giant_explosion_duration", increments = {0.1, 0.5, 1, 2}},
4008
+ {name = "Explosion Count", setting = "giant_explosion_count", increments = {25, 50, 100}},
4009
+ }
4010
+ for _, s in ipairs(giantSettings) do
4011
+ local subMenu = missionCommands.addSubMenu(s.name, giantExplosionMenu)
4012
+ addValueAdjustmentCommands(subMenu, s.setting, s.increments)
4013
+ end
4014
+ local testExplosionMenu = missionCommands.addSubMenu("Test Explosions", giantExplosionMenu)
4015
+ if splash_damage_options.giantexplosion_testmode then
4016
+ for _, target in ipairs(giantExplosionTestTargets) do
4017
+ missionCommands.addCommand("Detonate " .. target.name, testExplosionMenu, function()
4018
+ triggerGiantExplosion({
4019
+ pos = target.pos,
4020
+ power = splash_damage_options.giant_explosion_power,
4021
+ scale = splash_damage_options.giant_explosion_scale,
4022
+ duration = splash_damage_options.giant_explosion_duration,
4023
+ count = splash_damage_options.giant_explosion_count
4024
+ })
4025
+ end)
2091
4026
  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
-
4027
+ missionCommands.addCommand("Detonate All Giant Targets", testExplosionMenu, function()
4028
+ for _, target in ipairs(giantExplosionTestTargets) do
4029
+ triggerGiantExplosion({
4030
+ pos = target.pos,
4031
+ power = splash_damage_options.giant_explosion_power,
4032
+ scale = splash_damage_options.giant_explosion_scale,
4033
+ duration = splash_damage_options.giant_explosion_duration,
4034
+ count = splash_damage_options.giant_explosion_count
4035
+ })
4036
+ end
4037
+ end)
4038
+ end
4039
+
4040
+ --6. Napalm
4041
+ local napalmMenu = missionCommands.addSubMenu("Napalm", splash_damage_menu)
4042
+ local napalmToggles = {
4043
+ "napalm_mk77_enabled",
4044
+ "napalmoverride_enabled",
4045
+ "napalm_phosphor_enabled",
4046
+ "napalm_addflame"
4047
+ }
4048
+ for _, setting in ipairs(napalmToggles) do
4049
+ missionCommands.addCommand("Toggle " .. setting:gsub("_", " "), napalmMenu, toggleSplashDamageSetting, setting)
4050
+ end
4051
+
4052
+ --Submenu: Spread/Phosphor/Flame
4053
+ local spreadPhosphorFlameMenu = missionCommands.addSubMenu("Spread/Phosphor/Flame", napalmMenu)
4054
+ local napalmSettings = {
4055
+ {name = "Spread Points", setting = "napalm_spread_points", increments = {1, 2, 3}},
4056
+ {name = "Spread Spacing", setting = "napalm_spread_spacing", increments = {1, 5, 10}},
4057
+ {name = "Phosphor Multiplier", setting = "napalm_phosphor_multiplier", increments = {0.1, 0.5, 1}},
4058
+ {name = "Flame Duration", setting = "napalm_addflame_duration", increments = {10, 30, 60}}
4059
+ }
4060
+ for _, s in ipairs(napalmSettings) do
4061
+ local subMenu = missionCommands.addSubMenu(s.name, spreadPhosphorFlameMenu)
4062
+ addValueAdjustmentCommands(subMenu, s.setting, s.increments)
4063
+ end
4064
+ local napalmFlameSizeMenu = missionCommands.addSubMenu("Flame Size", spreadPhosphorFlameMenu)
4065
+ for i = 1, 8 do
4066
+ missionCommands.addCommand("Set " .. i, napalmFlameSizeMenu, function()
4067
+ splash_damage_options.napalm_addflame_size = i
4068
+ trigger.action.outText("Napalm flame size set to " .. i, 5)
4069
+ end)
4070
+ end
4071
+
4072
+ --Submenu: Delay Settings
4073
+ local napalmDelayMenu = missionCommands.addSubMenu("Delay Settings", napalmMenu)
4074
+ local napalmDelaySettings = {
4075
+ {name = "Explode Delay", setting = "napalm_explode_delay", increments = {0.01, 0.05, 0.1}},
4076
+ {name = "Destroy Delay", setting = "napalm_destroy_delay", increments = {0.01, 0.05, 0.1}},
4077
+ {name = "Flame Delay", setting = "napalm_flame_delay", increments = {0.01, 0.05, 0.1}}
4078
+ }
4079
+ for _, s in ipairs(napalmDelaySettings) do
4080
+ local subMenu = missionCommands.addSubMenu(s.name, napalmDelayMenu)
4081
+ addValueAdjustmentCommands(subMenu, s.setting, s.increments)
4082
+ end
4083
+
4084
+ --Submenu: DoubleWide
4085
+ local doubleWideMenu = missionCommands.addSubMenu("DoubleWide", napalmMenu)
4086
+ missionCommands.addCommand("Toggle DoubleWide Enabled", doubleWideMenu, toggleSplashDamageSetting, "napalm_doublewide_enabled")
4087
+ local doubleWideSpreadMenu = missionCommands.addSubMenu("DoubleWide Spread", doubleWideMenu)
4088
+ addValueAdjustmentCommands(doubleWideSpreadMenu, "napalm_doublewide_spread", {1, 5, 10})
4089
+
4090
+ --Submenu: Unit Damage
4091
+ local unitDamageMenu = missionCommands.addSubMenu("Unit Damage", napalmMenu)
4092
+ missionCommands.addCommand("Toggle Unit Damage Enabled", unitDamageMenu, toggleSplashDamageSetting, "napalm_unitdamage_enable")
4093
+ missionCommands.addCommand("Toggle Infantry Fire", unitDamageMenu, toggleSplashDamageSetting, "napalm_unitdamage_infantryfire")
4094
+ local scanDistanceMenu = missionCommands.addSubMenu("Scan Distance", unitDamageMenu)
4095
+ addValueAdjustmentCommands(scanDistanceMenu, "napalm_unitdamage_scandistance", {20, 25, 50})
4096
+ local startDelayMenu = missionCommands.addSubMenu("Start Delay", unitDamageMenu)
4097
+ addValueAdjustmentCommands(startDelayMenu, "napalm_unitdamage_startdelay", {0.1, 0.2, 0.5})
4098
+ local spreadDelayMenu = missionCommands.addSubMenu("Spread Delay", unitDamageMenu)
4099
+ addValueAdjustmentCommands(spreadDelayMenu, "napalm_unitdamage_spreaddelay", {0.1, 0.2, 0.5})
4100
+
4101
+
4102
+ --7. Exit Menu
4103
+ missionCommands.addCommand("Exit Splash Damage Menu", splash_damage_menu, exitSplashDamageMenu)
2106
4104
  end
2107
4105
 
2108
4106
  if (script_enable == 1) then
2109
- gameMsg("SPLASH DAMAGE 3.1 SCRIPT RUNNING")
2110
- env.info("SPLASH DAMAGE 3.1 SCRIPT RUNNING")
4107
+ gameMsg("SPLASH DAMAGE 3.4 SCRIPT RUNNING")
4108
+ env.info("SPLASH DAMAGE 3.4 SCRIPT RUNNING")
2111
4109
 
2112
4110
  timer.scheduleFunction(function()
2113
4111
  protectedCall(track_wpns)
@@ -2115,37 +4113,156 @@ if (script_enable == 1) then
2115
4113
  end, {}, timer.getTime() + refreshRate)
2116
4114
 
2117
4115
  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)
4116
+ scanGiantExplosionTargets()
2144
4117
  if not splash_damage_options.giant_explosion_target_static then
2145
- timer.scheduleFunction(updateTargetPosition, {}, timer.getTime() + 1.0)
4118
+ timer.scheduleFunction(updateGiantExplosionPositions, {}, timer.getTime() + 1.0)
2146
4119
  end
2147
4120
  end
4121
+
4122
+ if splash_damage_options.killfeed_enable then
4123
+ world.addEventHandler({ onEvent = function(self, event) protectedCall(onKillEvent, event) end }) --Add kill event handler
4124
+ end
2148
4125
 
2149
4126
  world.addEventHandler(WpnHandler)
2150
4127
  addSplashDamageMenu()
4128
+
4129
+ --Lekas integration
4130
+ if splash_damage_options.killfeed_enable and splash_damage_options.killfeed_lekas_foothold_integration then
4131
+ timer.scheduleFunction(processSplashKillfeed, {}, timer.getTime() + 60)
4132
+ if splash_damage_options.killfeed_debug then
4133
+ env.info("SplashDamage: Scheduled processSplashKillfeed for Lekas Foothold integration")
4134
+ end
4135
+ end
4136
+
2151
4137
  end
4138
+
4139
+ --[[-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=
4140
+ ##### Changelog #####
4141
+ -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- =--=-=-=-=-=-=-=
4142
+
4143
+ 24th May 2025 - 3.3
4144
+
4145
+ (Stevey666)
4146
+
4147
+ - Added some naval weapons into weapon/expl table
4148
+ - Added some ground unit ordnance to explosive table and allowing a wider area to be tracked
4149
+ - Game_mesages and enable_radio_menu options defaulted to false.
4150
+ -Please be advised that the non debug script has these two defaulted to false, so that users don't see that the script is in use nor can they access the test/config radio options. 
4151
+ -Set either to true as required.   The notice that the Splash Damage 3.x is running uses game_messsages.
4152
+ - Overhauled the radio options
4153
+ - Added optional cook-off effect - signal flares firing at random throughout the cook-off (see cookoff_flares_enabled). Not sure if I like this one so leaving in as optional
4154
+ - Reduced cargo cook off initial explosion values as they were a little too high
4155
+ - New feature: Napalm. MK77 A4 Skyhawk Napalm and Optional Napalm weapon override - Allows napalm effects, overriding specific weapons set in options is possible too.
4156
+ - This feature has been adapated from titi69's Napalm script https://www.digitalcombatsimulator.com/en/files/3340469/ , credit to him and the Olympus mod team for the Napalm method
4157
+
4158
+ (Sniex)
4159
+
4160
+ - Added weapon types in the weapon/expl
4161
+ - Adjusted some rocket explosive power numbers (+1 or 2)
4162
+ - Adjusted explosive power for anti radar, ship missile, cruise missile and some others
4163
+ - Increased script readability
4164
+
4165
+ (Kurdes)
4166
+
4167
+ - Added changed/missing JF17 ordnance to weapons table
4168
+ - Added JF29 mod ordnance to the weapons table
4169
+
4170
+ 10 May 2025 (Stevey666) - 3.2
4171
+ - New feature (user request): ground ordnance tracking, this tracks ground artillery etc if in the explosives table, set to false by default.
4172
+ - New feature (user request): option to create additional smoke and cargo cookoff effect for all ground vehicles initially destroyed by your ordnance or the script, set to false by default.
4173
+ - Adjusted blastwave explosion
4174
+ - Changes to debug output, ordering by vehicle distance
4175
+ - Thanks to tae. for the report, adjusted Ural-4320 in vehicle table, had incorrect name so wasn't triggering cook off.
4176
+ - Fixed error popup when using Mig 21's SPRD-99
4177
+ - Added Cargo Cook off / fireball to some static objects i.e crates/barrels
4178
+ - Reworked Giant Explosion tracking - no mission editor trigger needed, just name static unit or unit "GiantExplosionTarget[X]"
4179
+ - Allow for Giant Explosion trigger on damage or on death
4180
+
4181
+ 04 April 2025 (Stevey666) - 3.1
4182
+ - Set default cluster munitions option to false, set this to true in the options if you want it
4183
+ - Added missing radio commands for Cascade Scaling
4184
+ - Adjust default cascading to 2 (from 1)
4185
+ - Adjusted Ural-4320 to be a tanker and ammo carrier for cargo cookoff
4186
+ - Prevent weapons not in the list from being tracked
4187
+ - Moved some logging behind the debug mode flag
4188
+ - Ordnance Protection, added a max height ordnance protection will snap explosion to ground
4189
+ - Ordnance Protection, fixed enable/disable option
4190
+ - Added Giant Explosion feature
4191
+ - Adjusted some hydra70 values on recom. from ETBSmorgan
4192
+
4193
+
4194
+ 09 March 2025 (Stevey666) - 3.0
4195
+ - 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
4196
+ - 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)
4197
+ - Added vehicle scanning around a weapon to allow for..
4198
+ - Cook offs - you can set vehicles that will cook off i.e ammo trucks, number of explosions, debris explosions, power adjustable
4199
+ - Fuel/Tanker explosion and flames - when a fuel tanker blows it will through up a big flame - adjustable in the scripts
4200
+ - Added section for vehicles for the above
4201
+ - Added radio commands for everything
4202
+ - Added in cluster munitions changes (note: barely tested, its not particularly accurate or that useful at this point so leaving disabled)
4203
+ - Potential bug - testing, stacking too many units together may cause a MIST error if you're using mist
4204
+
4205
+ - Setting this as 3.0 as I'd like to be responsive to requests, updates etc - creating a new fork to track this
4206
+
4207
+
4208
+ 10 Feb 2025 (Stevey666) - 2.0.7
4209
+ - Fixed AGM 154/Adjusted weapons
4210
+ - Added overall damage scaling
4211
+ - Added modifier for shaped charges (i.e. Mavericks), adjusted weapon list accordingly
4212
+ - Adjusted blast radius and damage calculations, created option for dynamic blast radius
4213
+ - Adjusted cascading explosions, added additional "cascade_scaling" modifier and cascade explode threshold modifier. Units wont explode on initial impact unless health drops under threshold
4214
+ - Added always_cascade_explode option so you can set it to the old ways of making everything in the blast wave go kaboom
4215
+ - Added in game radio commands to change the new options ingame without having to reload everything in mission editor to test it out
4216
+
4217
+ 12 November 2024 (by JGi | Quéton 1-1)
4218
+ - Tweak down radius 100>90 (Thanks Arhibeau)
4219
+ - Tweak down some values
4220
+
4221
+ 20 January 2024 (by JGi | Quéton 1-1)
4222
+ - Added missing weapons to explTable
4223
+ - Sort weapons in explTable by type
4224
+ - Added aircraft type in log when missing
4225
+
4226
+ 03 May 2023 (KERV)
4227
+ Correction AGM 154 (https://forum.dcs.world/topic/289290-splash-damage-20-script-make-explosions-better/page/5/#comment-5207760)
4228
+
4229
+ 06 March 2023 (Kerv)
4230
+ - Add some data for new ammunition
4231
+
4232
+ 16 April 2022
4233
+ spencershepard (GRIMM):
4234
+ - Added new/missing weapons to explTable
4235
+ - Added new option rocket_multiplier
4236
+
4237
+ 31 December 2021
4238
+ spencershepard (GRIMM):
4239
+ - Added many new weapons
4240
+ - Added filter for weapons.shells events
4241
+ - Fixed mission weapon message option
4242
+ - Changed default for damage_model option
4243
+
4244
+ 21 December 2021
4245
+ spencershepard (GRIMM):
4246
+ SPLASH DAMAGE 2.0:
4247
+ - Added blast wave effect to add timed and scaled secondary explosions on top of game objects
4248
+ - Object geometry within blast wave changes damage intensity
4249
+ - Damage boost for structures since they are hard to kill, even if very close to large explosions
4250
+ - Increased some rocket values in explTable
4251
+ - Missing weapons from explTable will display message to user and log to DCS.log so that we can add what's missing
4252
+ - Damage model for ground units that will disable their weapons and ability to move with partial damage before they are killed
4253
+ - Added options table to allow easy adjustments before release
4254
+ - General refactoring and restructure
4255
+
4256
+ 28 October 2020
4257
+ FrozenDroid:
4258
+ - Uncommented error logging, actually made it an error log which shows a message box on error.
4259
+ - Fixed the too restrictive weapon filter (took out the HE warhead requirement)
4260
+
4261
+ 2 October 2020
4262
+ FrozenDroid:
4263
+ - Added error handling to all event handler and scheduled functions. Lua script errors can no longer bring the server down.
4264
+ - 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...?)
4265
+
4266
+ -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=
4267
+ ##### END of Changelog #####
4268
+ -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-]]