@jtff/miztemplate-lib 2.2.0 → 3.0.0-rc10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/index.js +4 -3
  2. package/lib/jtff-lib-ci.js +4 -308
  3. package/lib/mizlib.js +321 -0
  4. package/lua/lib/Hercules_Cargo.lua +686 -0
  5. package/lua/lib/Moose_.lua +117314 -0
  6. package/lua/lib/Splash_Damage_2_0.lua +472 -0
  7. package/lua/lib/gemman.lua +1 -0
  8. package/lua/lib/mist_4_5_107.lua +9084 -0
  9. package/lua/lib/skynet-iads-compiled.lua +3864 -0
  10. package/lua/settings/settings-RAT.lua +235 -0
  11. package/lua/settings/settings-airboss.lua +142 -0
  12. package/lua/settings/settings-atis.lua +31 -0
  13. package/lua/settings/settings-awacs.lua +58 -0
  14. package/lua/settings/settings-awacsondemand.lua +26 -0
  15. package/lua/settings/settings-beacons.lua +10 -0
  16. package/lua/settings/settings-capwarzone.lua +164 -0
  17. package/lua/settings/settings-capzone.lua +32 -0
  18. package/lua/settings/settings-fac_ranges.lua +17 -0
  19. package/lua/settings/settings-foxzone.lua +14 -0
  20. package/lua/settings/settings-gemman.lua +6 -0
  21. package/lua/settings/settings-global.lua +31 -0
  22. package/lua/settings/settings-intercept.lua +23 -0
  23. package/lua/settings/settings-logistics.lua +22 -0
  24. package/lua/settings/settings-ondemandawacs.lua +29 -0
  25. package/lua/settings/settings-ondemandtankers.lua +29 -0
  26. package/lua/settings/settings-pedros.lua +11 -0
  27. package/lua/settings/settings-ranges.lua +28 -0
  28. package/lua/settings/settings-reapers.lua +25 -0
  29. package/lua/settings/settings-sams.lua +19 -0
  30. package/lua/settings/settings-skynet.lua +239 -0
  31. package/lua/settings/settings-tankers.lua +32 -0
  32. package/lua/settings/settings-training_ranges.lua +37 -0
  33. package/lua/src/010-root_menus.lua +5 -0
  34. package/lua/src/020-mission_functions.lua +1059 -0
  35. package/lua/src/110-set_clients.lua +61 -0
  36. package/lua/src/120-tankers.lua +589 -0
  37. package/lua/src/130-airboss.lua +621 -0
  38. package/lua/src/135-pedro.lua +21 -0
  39. package/lua/src/140-beacons.lua +19 -0
  40. package/lua/src/150-awacs.lua +599 -0
  41. package/lua/src/160-atis.lua +53 -0
  42. package/lua/src/170-cap_zone_training.lua +127 -0
  43. package/lua/src/172-cap_zone_war.lua +190 -0
  44. package/lua/src/173-fox_zone_training.lua +87 -0
  45. package/lua/src/176-random_air_traffic.lua +73 -0
  46. package/lua/src/178-training-intercept.lua +263 -0
  47. package/lua/src/180-logistics.lua +80 -0
  48. package/lua/src/190-ranges.lua +54 -0
  49. package/lua/src/191-sams.lua +49 -0
  50. package/lua/src/193-training_ranges.lua +191 -0
  51. package/lua/src/195-reaper-ondemand.lua +522 -0
  52. package/lua/src/196-fac_ranges.lua +34 -0
  53. package/lua/src/199-skynet.lua +721 -0
  54. package/lua/src/200-mission.lua +3 -0
  55. package/package.json +4 -3
  56. package/resources/radios/.gitkeep +0 -0
  57. package/resources/sounds/CTLD/beacon.ogg +0 -0
  58. package/resources/sounds/CTLD/beaconsilent.ogg +0 -0
  59. package/resources/sounds/Misc/.gitkeep +0 -0
  60. package/resources/sounds/Misc/2_Bips.ogg +0 -0
  61. package/resources/sounds/Misc/Bip.ogg +0 -0
  62. package/resources/sounds/Misc/crash_wood.ogg +0 -0
  63. package/scripts/build.js +1 -1
  64. package/scripts/inject-scripts.js +124 -228
  65. package/scripts/template-update.js +1 -1
@@ -0,0 +1,3864 @@
1
+ env.info("--- SKYNET VERSION: 3.1.0 | BUILD TIME: 09.02.2023 1925Z ---")
2
+ do
3
+ --this file contains the required units per sam type
4
+ samTypesDB = {
5
+ ['S-200'] = {
6
+ ['type'] = 'complex',
7
+ ['searchRadar'] = {
8
+ ['RLS_19J6'] = {
9
+ ['name'] = {
10
+ ['NATO'] = 'Tin Shield',
11
+ },
12
+ },
13
+ ['p-19 s-125 sr'] = {
14
+ ['name'] = {
15
+ ['NATO'] = 'Flat Face',
16
+ },
17
+ },
18
+ },
19
+ ['EWR P-37 BAR LOCK'] = {
20
+ ['Name'] = {
21
+ ['NATO'] = "Bar lock",
22
+ },
23
+ },
24
+ ['trackingRadar'] = {
25
+ ['RPC_5N62V'] = {
26
+ },
27
+ },
28
+ ['launchers'] = {
29
+ ['S-200_Launcher'] = {
30
+ },
31
+ },
32
+ ['name'] = {
33
+ ['NATO'] = 'SA-5 Gammon',
34
+ },
35
+ ['harm_detection_chance'] = 60
36
+ },
37
+ ['S-300'] = {
38
+ ['type'] = 'complex',
39
+ ['searchRadar'] = {
40
+ ['S-300PS 40B6MD sr'] = {
41
+ ['name'] = {
42
+ ['NATO'] = 'Clam Shell',
43
+ },
44
+ },
45
+ ['S-300PS 64H6E sr'] = {
46
+ ['name'] = {
47
+ ['NATO'] = 'Big Bird',
48
+ },
49
+ },
50
+ },
51
+ ['trackingRadar'] = {
52
+ ['S-300PS 40B6M tr'] = {
53
+ },
54
+ },
55
+ ['launchers'] = {
56
+ ['S-300PS 5P85D ln'] = {
57
+ },
58
+ ['S-300PS 5P85C ln'] = {
59
+ },
60
+ },
61
+ ['misc'] = {
62
+ ['S-300PS 54K6 cp'] = {
63
+ ['required'] = true,
64
+ },
65
+ },
66
+ ['name'] = {
67
+ ['NATO'] = 'SA-10 Grumble',
68
+ },
69
+ ['harm_detection_chance'] = 90,
70
+ ['can_engage_harm'] = true
71
+ },
72
+ ['Buk'] = {
73
+ ['type'] = 'complex',
74
+ ['searchRadar'] = {
75
+ ['SA-11 Buk SR 9S18M1'] = {
76
+ ['name'] = {
77
+ ['NATO'] = 'Snow Drift',
78
+ },
79
+ },
80
+ },
81
+ ['launchers'] = {
82
+ ['SA-11 Buk LN 9A310M1'] = {
83
+ },
84
+ },
85
+ ['misc'] = {
86
+ ['SA-11 Buk CC 9S470M1'] = {
87
+ ['required'] = true,
88
+ },
89
+ },
90
+ ['name'] = {
91
+ ['NATO'] = 'SA-11 Gadfly',
92
+ },
93
+ ['harm_detection_chance'] = 70
94
+ },
95
+ ['S-125'] = {
96
+ ['type'] = 'complex',
97
+ ['searchRadar'] = {
98
+ ['p-19 s-125 sr'] = {
99
+ ['name'] = {
100
+ ['NATO'] = 'Flat Face',
101
+ },
102
+ },
103
+ },
104
+ ['trackingRadar'] = {
105
+ ['snr s-125 tr'] = {
106
+ },
107
+ },
108
+ ['launchers'] = {
109
+ ['5p73 s-125 ln'] = {
110
+ },
111
+ },
112
+ ['name'] = {
113
+ ['NATO'] = 'SA-3 Goa',
114
+ },
115
+ ['harm_detection_chance'] = 30
116
+ },
117
+ ['S-75'] = {
118
+ ['type'] = 'complex',
119
+ ['searchRadar'] = {
120
+ ['p-19 s-125 sr'] = {
121
+ ['name'] = {
122
+ ['NATO'] = 'Flat Face',
123
+ },
124
+ },
125
+ },
126
+ ['trackingRadar'] = {
127
+ ['SNR_75V'] = {
128
+ },
129
+ },
130
+ ['launchers'] = {
131
+ ['S_75M_Volhov'] = {
132
+ },
133
+ },
134
+ ['name'] = {
135
+ ['NATO'] = 'SA-2 Guideline',
136
+ },
137
+ ['harm_detection_chance'] = 30
138
+ },
139
+ ['Kub'] = {
140
+ ['type'] = 'complex',
141
+ ['searchRadar'] = {
142
+ ['Kub 1S91 str'] = {
143
+ ['name'] = {
144
+ ['NATO'] = 'Straight Flush',
145
+ },
146
+ },
147
+ },
148
+ ['launchers'] = {
149
+ ['Kub 2P25 ln'] = {
150
+ },
151
+ },
152
+ ['name'] = {
153
+ ['NATO'] = 'SA-6 Gainful',
154
+ },
155
+ ['harm_detection_chance'] = 40
156
+ },
157
+ ['Patriot'] = {
158
+ ['type'] = 'complex',
159
+ ['searchRadar'] = {
160
+ ['Patriot str'] = {
161
+ ['name'] = {
162
+ ['NATO'] = 'Patriot str',
163
+ },
164
+ },
165
+ },
166
+ ['launchers'] = {
167
+ ['Patriot ln'] = {
168
+ },
169
+ },
170
+ ['misc'] = {
171
+ ['Patriot cp'] = {
172
+ ['required'] = false,
173
+ },
174
+ ['Patriot EPP'] = {
175
+ ['required'] = false,
176
+ },
177
+ ['Patriot ECS'] = {
178
+ ['required'] = true,
179
+ },
180
+ ['Patriot AMG'] = {
181
+ ['required'] = false,
182
+ },
183
+ },
184
+ ['name'] = {
185
+ ['NATO'] = 'Patriot',
186
+ },
187
+ ['harm_detection_chance'] = 90,
188
+ ['can_engage_harm'] = true
189
+ },
190
+ ['Hawk'] = {
191
+ ['type'] = 'complex',
192
+ ['searchRadar'] = {
193
+ ['Hawk sr'] = {
194
+ ['name'] = {
195
+ ['NATO'] = 'Hawk str',
196
+ },
197
+ },
198
+ },
199
+ ['trackingRadar'] = {
200
+ ['Hawk tr'] = {
201
+ },
202
+ },
203
+ ['launchers'] = {
204
+ ['Hawk ln'] = {
205
+ },
206
+ },
207
+
208
+ ['name'] = {
209
+ ['NATO'] = 'Hawk',
210
+ },
211
+ ['harm_detection_chance'] = 40
212
+
213
+ },
214
+ ['Roland ADS'] = {
215
+ ['type'] = 'single',
216
+ ['searchRadar'] = {
217
+ ['Roland ADS'] = {
218
+ },
219
+ },
220
+ ['launchers'] = {
221
+ ['Roland ADS'] = {
222
+ },
223
+ },
224
+
225
+ ['name'] = {
226
+ ['NATO'] = 'Roland ADS',
227
+ },
228
+ ['harm_detection_chance'] = 60
229
+ },
230
+ ['NASAMS'] = {
231
+ ['type'] = 'complex',
232
+ ['searchRadar'] = {
233
+ ['NASAMS_Radar_MPQ64F1'] = {
234
+ },
235
+ },
236
+ ['launchers'] = {
237
+ ['NASAMS_LN_B'] = {
238
+ },
239
+ ['NASAMS_LN_C'] = {
240
+ },
241
+ },
242
+
243
+ ['name'] = {
244
+ ['NATO'] = 'NASAMS',
245
+ },
246
+ ['misc'] = {
247
+ ['NASAMS_Command_Post'] = {
248
+ ['required'] = false,
249
+ },
250
+ },
251
+ ['can_engage_harm'] = true,
252
+ ['harm_detection_chance'] = 90
253
+ },
254
+ ['2S6 Tunguska'] = {
255
+ ['type'] = 'single',
256
+ ['searchRadar'] = {
257
+ ['2S6 Tunguska'] = {
258
+ },
259
+ },
260
+ ['launchers'] = {
261
+ ['2S6 Tunguska'] = {
262
+ },
263
+ },
264
+ ['name'] = {
265
+ ['NATO'] = 'SA-19 Grison',
266
+ },
267
+ },
268
+ ['Osa'] = {
269
+ ['type'] = 'single',
270
+ ['searchRadar'] = {
271
+ ['Osa 9A33 ln'] = {
272
+ },
273
+ },
274
+ ['launchers'] = {
275
+ ['Osa 9A33 ln'] = {
276
+
277
+ },
278
+ },
279
+ ['name'] = {
280
+ ['NATO'] = 'SA-8 Gecko',
281
+ },
282
+ ['harm_detection_chance'] = 20
283
+ },
284
+ ['Strela-10M3'] = {
285
+ ['type'] = 'single',
286
+ ['searchRadar'] = {
287
+ ['Strela-10M3'] = {
288
+ ['trackingRadar'] = true,
289
+ },
290
+ },
291
+ ['launchers'] = {
292
+ ['Strela-10M3'] = {
293
+ },
294
+ },
295
+ ['name'] = {
296
+ ['NATO'] = 'SA-13 Gopher',
297
+ },
298
+ },
299
+ ['Strela-1 9P31'] = {
300
+ ['type'] = 'single',
301
+ ['searchRadar'] = {
302
+ ['Strela-1 9P31'] = {
303
+ },
304
+ },
305
+ ['launchers'] = {
306
+ ['Strela-1 9P31'] = {
307
+ },
308
+ },
309
+ ['name'] = {
310
+ ['NATO'] = 'SA-9 Gaskin',
311
+ },
312
+ ['harm_detection_chance'] = 20
313
+ },
314
+ ['Tor'] = {
315
+ ['type'] = 'single',
316
+ ['searchRadar'] = {
317
+ ['Tor 9A331'] = {
318
+ },
319
+ },
320
+ ['launchers'] = {
321
+ ['Tor 9A331'] = {
322
+ },
323
+ },
324
+ ['name'] = {
325
+ ['NATO'] = 'SA-15 Gauntlet',
326
+ },
327
+ ['harm_detection_chance'] = 90,
328
+ ['can_engage_harm'] = true
329
+
330
+ },
331
+ ['Gepard'] = {
332
+ ['type'] = 'single',
333
+ ['searchRadar'] = {
334
+ ['Gepard'] = {
335
+ },
336
+ },
337
+ ['launchers'] = {
338
+ ['Gepard'] = {
339
+ },
340
+ },
341
+ ['name'] = {
342
+ ['NATO'] = 'Gepard',
343
+ },
344
+ ['harm_detection_chance'] = 10
345
+ },
346
+ ['Rapier'] = {
347
+ ['searchRadar'] = {
348
+ ['rapier_fsa_blindfire_radar'] = {
349
+ },
350
+ },
351
+ ['launchers'] = {
352
+ ['rapier_fsa_launcher'] = {
353
+ ['trackingRadar'] = true,
354
+ },
355
+ },
356
+ ['misc'] = {
357
+ ['rapier_fsa_optical_tracker_unit'] = {
358
+ ['required'] = true,
359
+ },
360
+ },
361
+ ['name'] = {
362
+ ['NATO'] = 'Rapier',
363
+ },
364
+ ['harm_detection_chance'] = 10
365
+ },
366
+ ['ZSU-23-4 Shilka'] = {
367
+ ['type'] = 'single',
368
+ ['searchRadar'] = {
369
+ ['ZSU-23-4 Shilka'] = {
370
+ },
371
+ },
372
+ ['launchers'] = {
373
+ ['ZSU-23-4 Shilka'] = {
374
+ },
375
+ },
376
+ ['name'] = {
377
+ ['NATO'] = 'Zues',
378
+ },
379
+ ['harm_detection_chance'] = 10
380
+ },
381
+ ['HQ-7'] = {
382
+ ['searchRadar'] = {
383
+ ['HQ-7_STR_SP'] = {
384
+ ['name'] = {
385
+ ['NATO'] = 'CSA-4',
386
+ },
387
+ },
388
+ },
389
+ ['launchers'] = {
390
+ ['HQ-7_LN_SP'] = {
391
+ },
392
+ },
393
+ ['name'] = {
394
+ ['NATO'] = 'CSA-4',
395
+ },
396
+ ['harm_detection_chance'] = 30
397
+ },
398
+ --- Start of EW radars:
399
+ ['1L13 EWR'] = {
400
+ ['type'] = 'ewr',
401
+ ['searchRadar'] = {
402
+ ['1L13 EWR'] = {
403
+ ['name'] = {
404
+ ['NATO'] = 'Box Spring',
405
+ },
406
+ },
407
+ },
408
+ ['harm_detection_chance'] = 60
409
+ },
410
+ ['55G6 EWR'] = {
411
+ ['type'] = 'ewr',
412
+ ['searchRadar'] = {
413
+ ['55G6 EWR'] = {
414
+ ['name'] = {
415
+ ['NATO'] = 'Tall Rack',
416
+ },
417
+ },
418
+ },
419
+ ['harm_detection_chance'] = 60
420
+ },
421
+ ['Dog Ear'] = {
422
+ ['type'] = 'ewr',
423
+ ['searchRadar'] = {
424
+ ['Dog Ear radar'] = {
425
+ ['name'] = {
426
+ ['NATO'] = 'Dog Ear',
427
+ },
428
+ },
429
+ },
430
+ ['harm_detection_chance'] = 20
431
+ },
432
+ ['Roland Radar'] = {
433
+ ['type'] = 'ewr',
434
+ ['searchRadar'] = {
435
+ ['Roland Radar'] = {
436
+ ['name'] = {
437
+ ['NATO'] = 'Roland EWR',
438
+ },
439
+ },
440
+ },
441
+
442
+ ['harm_detection_chance'] = 60
443
+ },
444
+ }
445
+ end
446
+ do
447
+ -- this file contains the definitions for the HightDigitSAMSs: https://github.com/Auranis/HighDigitSAMs
448
+
449
+ --EW radars used in multiple SAM systems:
450
+
451
+ s300PMU164N6Esr = {
452
+ ['name'] = {
453
+ ['NATO'] = 'Big Bird',
454
+ },
455
+ }
456
+
457
+ s300PMU140B6MDsr = {
458
+ ['name'] = {
459
+ ['NATO'] = 'Clam Shell',
460
+ },
461
+ }
462
+
463
+ --[[ units in SA-10 group Gargoyle:
464
+ 2020-12-10 18:27:27.050 INFO SCRIPTING: S-300PMU1 54K6 cp
465
+ 2020-12-10 18:27:27.050 INFO SCRIPTING: S-300PMU1 5P85CE ln
466
+ 2020-12-10 18:27:27.050 INFO SCRIPTING: S-300PMU1 5P85DE ln
467
+ 2020-12-10 18:27:27.050 INFO SCRIPTING: S-300PMU1 40B6MD sr
468
+ 2020-12-10 18:27:27.050 INFO SCRIPTING: S-300PMU1 64N6E sr
469
+ 2020-12-10 18:27:27.050 INFO SCRIPTING: S-300PMU1 40B6M tr
470
+ 2020-12-10 18:27:27.050 INFO SCRIPTING: S-300PMU1 30N6E tr
471
+ --]]
472
+ samTypesDB['S-300PMU1'] = {
473
+ ['type'] = 'complex',
474
+ ['searchRadar'] = {
475
+ ['S-300PMU1 40B6MD sr'] = s300PMU140B6MDsr,
476
+ ['S-300PMU1 64N6E sr'] = s300PMU164N6Esr,
477
+
478
+ ['S-300PS 40B6MD sr'] = {
479
+ ['name'] = {
480
+ ['NATO'] = '',
481
+ },
482
+ },
483
+ ['S-300PS 64H6E sr'] = {
484
+ ['name'] = {
485
+ ['NATO'] = '',
486
+ },
487
+ },
488
+ },
489
+ ['trackingRadar'] = {
490
+ ['S-300PMU1 40B6M tr'] = {
491
+ ['name'] = {
492
+ ['NATO'] = 'Grave Stone',
493
+ },
494
+ },
495
+ ['S-300PMU1 30N6E tr'] = {
496
+ ['name'] = {
497
+ ['NATO'] = 'Flap Lid',
498
+ },
499
+
500
+ },
501
+ ['S-300PS 40B6M tr'] = {
502
+ ['name'] = {
503
+ ['NATO'] = '',
504
+ },
505
+ },
506
+ },
507
+ ['misc'] = {
508
+ ['S-300PMU1 54K6 cp'] = {
509
+ ['required'] = true,
510
+ },
511
+ },
512
+ ['launchers'] = {
513
+ ['S-300PMU1 5P85CE ln'] = {
514
+ },
515
+ ['S-300PMU1 5P85DE ln'] = {
516
+ },
517
+ },
518
+ ['name'] = {
519
+ ['NATO'] = 'SA-20A Gargoyle'
520
+ },
521
+ ['harm_detection_chance'] = 90,
522
+ ['can_engage_harm'] = true
523
+ }
524
+
525
+ --[[ Units in the SA-23 Group:
526
+ 2020-12-11 16:40:52.072 INFO SCRIPTING: S-300VM 9A82ME ln
527
+ 2020-12-11 16:40:52.072 INFO SCRIPTING: S-300VM 9A83ME ln
528
+ 2020-12-11 16:40:52.072 INFO SCRIPTING: S-300VM 9S15M2 sr
529
+ 2020-12-11 16:40:52.072 INFO SCRIPTING: S-300VM 9S19M2 sr
530
+ 2020-12-11 16:40:52.072 INFO SCRIPTING: S-300VM 9S32ME tr
531
+ 2020-12-11 16:40:52.072 INFO SCRIPTING: S-300VM 9S457ME cp
532
+
533
+ ]]--
534
+ samTypesDB['S-300VM'] = {
535
+ ['type'] = 'complex',
536
+ ['searchRadar'] = {
537
+ ['S-300VM 9S15M2 sr'] = {
538
+ ['name'] = {
539
+ ['NATO'] = 'Bill Board-C',
540
+ },
541
+ },
542
+ ['S-300VM 9S19M2 sr'] = {
543
+ ['name'] = {
544
+ ['NATO'] = 'High Screen-B',
545
+ },
546
+ },
547
+ },
548
+ ['trackingRadar'] = {
549
+ ['S-300VM 9S32ME tr'] = {
550
+ },
551
+ },
552
+ ['misc'] = {
553
+ ['S-300VM 9S457ME cp'] = {
554
+ ['required'] = true,
555
+ },
556
+ },
557
+ ['launchers'] = {
558
+ ['S-300VM 9A82ME ln'] = {
559
+ },
560
+ ['S-300VM 9A83ME ln'] = {
561
+ },
562
+ },
563
+ ['name'] = {
564
+ ['NATO'] = 'SA-23 Antey-2500'
565
+ },
566
+ ['harm_detection_chance'] = 90,
567
+ ['can_engage_harm'] = true
568
+ }
569
+
570
+ --[[ Units in the SA-10B Group:
571
+ 2021-01-01 20:39:14.413 INFO SCRIPTING: S-300PS SA-10B 40B6MD MAST sr
572
+ 2021-01-01 20:39:14.413 INFO SCRIPTING: S-300PS SA-10B 54K6 cp
573
+ 2021-01-01 20:39:14.413 INFO SCRIPTING: S-300PS 5P85SE_mod ln
574
+ 2021-01-01 20:39:14.413 INFO SCRIPTING: S-300PS 5P85SU_mod ln
575
+ 2021-01-01 20:39:14.413 INFO SCRIPTING: S-300PS 64H6E TRAILER sr
576
+ 2021-01-01 20:39:14.413 INFO SCRIPTING: S-300PS 30N6 TRAILER tr
577
+ 2021-01-01 20:39:14.413 INFO SCRIPTING: S-300PS SA-10B 40B6M MAST tr
578
+ --]]
579
+ samTypesDB['S-300PS'] = {
580
+ ['type'] = 'complex',
581
+ ['searchRadar'] = {
582
+ ['S-300PS SA-10B 40B6MD MAST sr'] = {
583
+ ['name'] = {
584
+ ['NATO'] = 'Clam Shell',
585
+ },
586
+ },
587
+ ['S-300PS 64H6E TRAILER sr'] = {
588
+ },
589
+ },
590
+ ['trackingRadar'] = {
591
+ ['S-300PS 30N6 TRAILER tr'] = {
592
+ },
593
+ ['S-300PS SA-10B 40B6M MAST tr'] = {
594
+ },
595
+ ['S-300PS 40B6M tr'] = {
596
+ },
597
+ ['S-300PMU1 40B6M tr'] = {
598
+ },
599
+ ['S-300PMU1 30N6E tr'] = {
600
+ },
601
+ },
602
+ ['misc'] = {
603
+ ['S-300PS SA-10B 54K6 cp'] = {
604
+ ['required'] = true,
605
+ },
606
+ },
607
+ ['launchers'] = {
608
+ ['S-300PS 5P85SE_mod ln'] = {
609
+ },
610
+ ['S-300PS 5P85SU_mod ln'] = {
611
+ },
612
+ },
613
+ ['name'] = {
614
+ ['NATO'] = 'SA-10B Grumble'
615
+ },
616
+ ['harm_detection_chance'] = 90,
617
+ ['can_engage_harm'] = true
618
+ }
619
+
620
+ --[[ Extra launchers for the in game SA-10C and HighDigitSAMs SA-10B, SA-20B
621
+ 2021-01-01 21:04:19.908 INFO SCRIPTING: S-300PS 5P85DE ln
622
+ 2021-01-01 21:04:19.908 INFO SCRIPTING: S-300PS 5P85CE ln
623
+ --]]
624
+
625
+ local s300launchers = samTypesDB['S-300']['launchers']
626
+ s300launchers['S-300PS 5P85DE ln'] = {}
627
+ s300launchers['S-300PS 5P85CE ln'] = {}
628
+
629
+ local s300launchers = samTypesDB['S-300PS']['launchers']
630
+ s300launchers['S-300PS 5P85DE ln'] = {}
631
+ s300launchers['S-300PS 5P85CE ln'] = {}
632
+
633
+ local s300launchers = samTypesDB['S-300PMU1']['launchers']
634
+ s300launchers['S-300PS 5P85DE ln'] = {}
635
+ s300launchers['S-300PS 5P85CE ln'] = {}
636
+
637
+ --[[
638
+ New launcher for the SA-11 complex, will identify as SA-17
639
+ SA-17 Buk M1-2 LN 9A310M1-2
640
+ --]]
641
+ samTypesDB['Buk-M2'] = {
642
+ ['type'] = 'complex',
643
+ ['searchRadar'] = {
644
+ ['SA-11 Buk SR 9S18M1'] = {
645
+ ['name'] = {
646
+ ['NATO'] = 'Snow Drift',
647
+ },
648
+ },
649
+ },
650
+ ['launchers'] = {
651
+ ['SA-17 Buk M1-2 LN 9A310M1-2'] = {
652
+ },
653
+ },
654
+ ['misc'] = {
655
+ ['SA-11 Buk CC 9S470M1'] = {
656
+ ['required'] = true,
657
+ },
658
+ },
659
+ ['name'] = {
660
+ ['NATO'] = 'SA-17 Grizzly',
661
+ },
662
+ ['harm_detection_chance'] = 90
663
+ }
664
+
665
+ --[[
666
+ New launcher for the SA-2 complex: S_75M_Volhov_V759
667
+ --]]
668
+ local s75launchers = samTypesDB['S-75']['launchers']
669
+ s75launchers['S_75M_Volhov_V759'] = {}
670
+
671
+ --[[
672
+ New launcher for the SA-3 complex:
673
+ --]]
674
+ local s125launchers = samTypesDB['S-125']['launchers']
675
+ s125launchers['5p73 V-601P ln'] = {}
676
+
677
+ --[[
678
+ New launcher for the SA-2 complex: HQ_2_Guideline_LN
679
+ --]]
680
+ local s125launchers = samTypesDB['S-75']['launchers']
681
+ s125launchers['HQ_2_Guideline_LN'] = {}
682
+
683
+ --[[
684
+ SA-12 Gladiator / Giant:
685
+ 2021-03-19 21:24:22.620 INFO SCRIPTING: S-300V 9S15 sr
686
+ 2021-03-19 21:24:22.620 INFO SCRIPTING: S-300V 9S19 sr
687
+ 2021-03-19 21:24:22.620 INFO SCRIPTING: S-300V 9S32 tr
688
+ 2021-03-19 21:24:22.620 INFO SCRIPTING: S-300V 9S457 cp
689
+ 2021-03-19 21:24:22.620 INFO SCRIPTING: S-300V 9A83 ln
690
+ 2021-03-19 21:24:22.620 INFO SCRIPTING: S-300V 9A82 ln
691
+ --]]
692
+ samTypesDB['S-300V'] = {
693
+ ['type'] = 'complex',
694
+ ['searchRadar'] = {
695
+ ['S-300V 9S15 sr'] = {
696
+ ['name'] = {
697
+ ['NATO'] = 'Bill Board',
698
+ },
699
+ },
700
+ ['S-300V 9S19 sr'] = {
701
+ ['name'] = {
702
+ ['NATO'] = 'High Screen',
703
+ },
704
+ },
705
+ },
706
+ ['trackingRadar'] = {
707
+ ['S-300V 9S32 tr'] = {
708
+ ['NATO'] = 'Grill Pan',
709
+ },
710
+ },
711
+ ['misc'] = {
712
+ ['S-300V 9S457 cp'] = {
713
+ ['required'] = true,
714
+ },
715
+ },
716
+ ['launchers'] = {
717
+ ['S-300V 9A83 ln'] = {
718
+ },
719
+ ['S-300V 9A82 ln'] = {
720
+ },
721
+ },
722
+ ['name'] = {
723
+ ['NATO'] = 'SA-12 Gladiator/Giant'
724
+ },
725
+ ['harm_detection_chance'] = 90,
726
+ ['can_engage_harm'] = true
727
+ }
728
+
729
+ --[[
730
+ SA-20B Gargoyle B:
731
+
732
+ 2021-03-25 19:15:02.135 INFO SCRIPTING: S-300PMU2 64H6E2 sr
733
+ 2021-03-25 19:15:02.135 INFO SCRIPTING: S-300PMU2 92H6E tr
734
+ 2021-03-25 19:15:02.135 INFO SCRIPTING: S-300PMU2 5P85SE2 ln
735
+ 2021-03-25 19:15:02.135 INFO SCRIPTING: S-300PMU2 54K6E2 cp
736
+ --]]
737
+
738
+ samTypesDB['S-300PMU2'] = {
739
+ ['type'] = 'complex',
740
+ ['searchRadar'] = {
741
+ ['S-300PMU2 64H6E2 sr'] = {
742
+ ['name'] = {
743
+ ['NATO'] = '',
744
+ },
745
+ },
746
+ ['S-300PMU1 40B6MD sr'] = s300PMU140B6MDsr,
747
+ ['S-300PMU1 64N6E sr'] = s300PMU164N6Esr,
748
+
749
+ ['S-300PS 40B6MD sr'] = {
750
+ ['name'] = {
751
+ ['NATO'] = '',
752
+ },
753
+ },
754
+ ['S-300PS 64H6E sr'] = {
755
+ ['name'] = {
756
+ ['NATO'] = '',
757
+ },
758
+ },
759
+ },
760
+ ['trackingRadar'] = {
761
+ ['S-300PMU2 92H6E tr'] = {
762
+ },
763
+ ['S-300PS 40B6M tr'] = {
764
+ },
765
+ ['S-300PMU1 40B6M tr'] = {
766
+ },
767
+ ['S-300PMU1 30N6E tr'] = {
768
+ },
769
+ },
770
+ ['misc'] = {
771
+ ['S-300PMU2 54K6E2 cp'] = {
772
+ ['required'] = true,
773
+ },
774
+ },
775
+ ['launchers'] = {
776
+ ['S-300PMU2 5P85SE2 ln'] = {
777
+ },
778
+ },
779
+ ['name'] = {
780
+ ['NATO'] = 'SA-20B Gargoyle B'
781
+ },
782
+ ['harm_detection_chance'] = 90,
783
+ ['can_engage_harm'] = true
784
+ }
785
+
786
+ --[[
787
+
788
+ --]]
789
+ end
790
+
791
+
792
+
793
+ do
794
+
795
+ SkynetIADSLogger = {}
796
+ SkynetIADSLogger.__index = SkynetIADSLogger
797
+
798
+ function SkynetIADSLogger:create(iads)
799
+ local logger = {}
800
+ setmetatable(logger, SkynetIADSLogger)
801
+ logger.debugOutput = {}
802
+ logger.debugOutput.IADSStatus = false
803
+ logger.debugOutput.samWentDark = false
804
+ logger.debugOutput.contacts = false
805
+ logger.debugOutput.radarWentLive = false
806
+ logger.debugOutput.jammerProbability = false
807
+ logger.debugOutput.addedEWRadar = false
808
+ logger.debugOutput.addedSAMSite = false
809
+ logger.debugOutput.warnings = true
810
+ logger.debugOutput.harmDefence = false
811
+ logger.debugOutput.samSiteStatusEnvOutput = false
812
+ logger.debugOutput.earlyWarningRadarStatusEnvOutput = false
813
+ logger.debugOutput.commandCenterStatusEnvOutput = false
814
+ logger.iads = iads
815
+ return logger
816
+ end
817
+
818
+ function SkynetIADSLogger:getDebugSettings()
819
+ return self.debugOutput
820
+ end
821
+
822
+ function SkynetIADSLogger:printOutput(output, typeWarning)
823
+ if typeWarning == true and self:getDebugSettings().warnings or typeWarning == nil then
824
+ if typeWarning == true then
825
+ output = "WARNING: "..output
826
+ end
827
+ trigger.action.outText(output, 4)
828
+ end
829
+ end
830
+
831
+ function SkynetIADSLogger:printOutputToLog(output)
832
+ env.info("SKYNET: "..output, 4)
833
+ end
834
+
835
+ function SkynetIADSLogger:printEarlyWarningRadarStatus()
836
+ local ewRadars = self.iads:getEarlyWarningRadars()
837
+ self:printOutputToLog("------------------------------------------ EW RADAR STATUS: "..self.iads:getCoalitionString().." -------------------------------")
838
+ for i = 1, #ewRadars do
839
+ local ewRadar = ewRadars[i]
840
+ local numConnectionNodes = #ewRadar:getConnectionNodes()
841
+ local numPowerSources = #ewRadar:getPowerSources()
842
+ local isActive = ewRadar:isActive()
843
+ local connectionNodes = ewRadar:getConnectionNodes()
844
+ local firstRadar = nil
845
+ local radars = ewRadar:getRadars()
846
+
847
+ --get the first existing radar to prevent issues in calculating the distance later on:
848
+ for i = 1, #radars do
849
+ if radars[i]:isExist() then
850
+ firstRadar = radars[i]
851
+ break
852
+ end
853
+
854
+ end
855
+ local numDamagedConnectionNodes = 0
856
+
857
+
858
+ for j = 1, #connectionNodes do
859
+ local connectionNode = connectionNodes[j]
860
+ if connectionNode:isExist() == false then
861
+ numDamagedConnectionNodes = numDamagedConnectionNodes + 1
862
+ end
863
+ end
864
+ local intactConnectionNodes = numConnectionNodes - numDamagedConnectionNodes
865
+
866
+ local powerSources = ewRadar:getPowerSources()
867
+ local numDamagedPowerSources = 0
868
+ for j = 1, #powerSources do
869
+ local powerSource = powerSources[j]
870
+ if powerSource:isExist() == false then
871
+ numDamagedPowerSources = numDamagedPowerSources + 1
872
+ end
873
+ end
874
+ local intactPowerSources = numPowerSources - numDamagedPowerSources
875
+
876
+ local detectedTargets = ewRadar:getDetectedTargets()
877
+ local samSitesInCoveredArea = ewRadar:getChildRadars()
878
+
879
+ local unitName = "DESTROYED"
880
+
881
+ if ewRadar:getDCSRepresentation():isExist() then
882
+ unitName = ewRadar:getDCSName()
883
+ end
884
+
885
+ self:printOutputToLog("UNIT: "..unitName.." | TYPE: "..ewRadar:getNatoName())
886
+ self:printOutputToLog("ACTIVE: "..tostring(isActive).."| DETECTED TARGETS: "..#detectedTargets.." | DEFENDING HARM: "..tostring(ewRadar:isDefendingHARM()))
887
+ if numConnectionNodes > 0 then
888
+ self:printOutputToLog("CONNECTION NODES: "..numConnectionNodes.." | DAMAGED: "..numDamagedConnectionNodes.." | INTACT: "..intactConnectionNodes)
889
+ else
890
+ self:printOutputToLog("NO CONNECTION NODES SET")
891
+ end
892
+ if numPowerSources > 0 then
893
+ self:printOutputToLog("POWER SOURCES : "..numPowerSources.." | DAMAGED:"..numDamagedPowerSources.." | INTACT: "..intactPowerSources)
894
+ else
895
+ self:printOutputToLog("NO POWER SOURCES SET")
896
+ end
897
+
898
+ self:printOutputToLog("SAM SITES IN COVERED AREA: "..#samSitesInCoveredArea)
899
+ for j = 1, #samSitesInCoveredArea do
900
+ local samSiteCovered = samSitesInCoveredArea[j]
901
+ self:printOutputToLog(samSiteCovered:getDCSName())
902
+ end
903
+
904
+ for j = 1, #detectedTargets do
905
+ local contact = detectedTargets[j]
906
+ if firstRadar ~= nil and firstRadar:isExist() then
907
+ local distance = mist.utils.round(mist.utils.metersToNM(ewRadar:getDistanceInMetersToContact(firstRadar:getDCSRepresentation(), contact:getPosition().p)), 2)
908
+ self:printOutputToLog("CONTACT: "..contact:getName().." | TYPE: "..contact:getTypeName().." | DISTANCE NM: "..distance)
909
+ end
910
+ end
911
+
912
+ self:printOutputToLog("---------------------------------------------------")
913
+
914
+ end
915
+
916
+ end
917
+
918
+ function SkynetIADSLogger:getMetaInfo(abstractElementSupport)
919
+ local info = {}
920
+ info.numSources = #abstractElementSupport
921
+ info.numDamagedSources = 0
922
+ info.numIntactSources = 0
923
+ for j = 1, #abstractElementSupport do
924
+ local source = abstractElementSupport[j]
925
+ if source:isExist() == false then
926
+ info.numDamagedSources = info.numDamagedSources + 1
927
+ end
928
+ end
929
+ info.numIntactSources = info.numSources - info.numDamagedSources
930
+ return info
931
+ end
932
+
933
+ function SkynetIADSLogger:printSAMSiteStatus()
934
+ local samSites = self.iads:getSAMSites()
935
+
936
+ self:printOutputToLog("------------------------------------------ SAM STATUS: "..self.iads:getCoalitionString().." -------------------------------")
937
+ for i = 1, #samSites do
938
+ local samSite = samSites[i]
939
+ local numConnectionNodes = #samSite:getConnectionNodes()
940
+ local numPowerSources = #samSite:getPowerSources()
941
+ local isAutonomous = samSite:getAutonomousState()
942
+ local isActive = samSite:isActive()
943
+
944
+ local connectionNodes = samSite:getConnectionNodes()
945
+ local firstRadar = samSite:getRadars()[1]
946
+ local numDamagedConnectionNodes = 0
947
+ for j = 1, #connectionNodes do
948
+ local connectionNode = connectionNodes[j]
949
+ if connectionNode:isExist() == false then
950
+ numDamagedConnectionNodes = numDamagedConnectionNodes + 1
951
+ end
952
+ end
953
+ local intactConnectionNodes = numConnectionNodes - numDamagedConnectionNodes
954
+
955
+ local powerSources = samSite:getPowerSources()
956
+ local numDamagedPowerSources = 0
957
+ for j = 1, #powerSources do
958
+ local powerSource = powerSources[j]
959
+ if powerSource:isExist() == false then
960
+ numDamagedPowerSources = numDamagedPowerSources + 1
961
+ end
962
+ end
963
+ local intactPowerSources = numPowerSources - numDamagedPowerSources
964
+
965
+ local detectedTargets = samSite:getDetectedTargets()
966
+
967
+ local samSitesInCoveredArea = samSite:getChildRadars()
968
+
969
+ local engageAirWeapons = samSite:getCanEngageAirWeapons()
970
+
971
+ local engageHARMS = samSite:getCanEngageHARM()
972
+
973
+ local hasAmmo = samSite:hasRemainingAmmo()
974
+
975
+ self:printOutputToLog("GROUP: "..samSite:getDCSName().." | TYPE: "..samSite:getNatoName())
976
+ self:printOutputToLog("ACTIVE: "..tostring(isActive).." | AUTONOMOUS: "..tostring(isAutonomous).." | IS ACTING AS EW: "..tostring(samSite:getActAsEW()).." | CAN ENGAGE AIR WEAPONS : "..tostring(engageAirWeapons).." | CAN ENGAGE HARMS : "..tostring(engageHARMS).." | HAS AMMO: "..tostring(hasAmmo).." | DETECTED TARGETS: "..#detectedTargets.." | DEFENDING HARM: "..tostring(samSite:isDefendingHARM()).." | MISSILES IN FLIGHT: "..tostring(samSite:getNumberOfMissilesInFlight()))
977
+
978
+ if numConnectionNodes > 0 then
979
+ self:printOutputToLog("CONNECTION NODES: "..numConnectionNodes.." | DAMAGED: "..numDamagedConnectionNodes.." | INTACT: "..intactConnectionNodes)
980
+ else
981
+ self:printOutputToLog("NO CONNECTION NODES SET")
982
+ end
983
+ if numPowerSources > 0 then
984
+ self:printOutputToLog("POWER SOURCES : "..numPowerSources.." | DAMAGED:"..numDamagedPowerSources.." | INTACT: "..intactPowerSources)
985
+ else
986
+ self:printOutputToLog("NO POWER SOURCES SET")
987
+ end
988
+
989
+ self:printOutputToLog("SAM SITES IN COVERED AREA: "..#samSitesInCoveredArea)
990
+ for j = 1, #samSitesInCoveredArea do
991
+ local samSiteCovered = samSitesInCoveredArea[j]
992
+ self:printOutputToLog(samSiteCovered:getDCSName())
993
+ end
994
+
995
+ for j = 1, #detectedTargets do
996
+ local contact = detectedTargets[j]
997
+ if firstRadar ~= nil and firstRadar:isExist() then
998
+ local distance = mist.utils.round(mist.utils.metersToNM(samSite:getDistanceInMetersToContact(firstRadar:getDCSRepresentation(), contact:getPosition().p)), 2)
999
+ self:printOutputToLog("CONTACT: "..contact:getName().." | TYPE: "..contact:getTypeName().." | DISTANCE NM: "..distance)
1000
+ end
1001
+ end
1002
+
1003
+ self:printOutputToLog("---------------------------------------------------")
1004
+ end
1005
+ end
1006
+
1007
+ function SkynetIADSLogger:printCommandCenterStatus()
1008
+ local commandCenters = self.iads:getCommandCenters()
1009
+ self:printOutputToLog("------------------------------------------ COMMAND CENTER STATUS: "..self.iads:getCoalitionString().." -------------------------------")
1010
+
1011
+ for i = 1, #commandCenters do
1012
+ local commandCenter = commandCenters[i]
1013
+ local numConnectionNodes = #commandCenter:getConnectionNodes()
1014
+ local powerSourceInfo = self:getMetaInfo(commandCenter:getPowerSources())
1015
+ local connectionNodeInfo = self:getMetaInfo(commandCenter:getConnectionNodes())
1016
+ self:printOutputToLog("GROUP: "..commandCenter:getDCSName().." | TYPE: "..commandCenter:getNatoName())
1017
+ if connectionNodeInfo.numSources > 0 then
1018
+ self:printOutputToLog("CONNECTION NODES: "..connectionNodeInfo.numSources.." | DAMAGED: "..connectionNodeInfo.numDamagedSources.." | INTACT: "..connectionNodeInfo.numIntactSources)
1019
+ else
1020
+ self:printOutputToLog("NO CONNECTION NODES SET")
1021
+ end
1022
+ if powerSourceInfo.numSources > 0 then
1023
+ self:printOutputToLog("POWER SOURCES : "..powerSourceInfo.numSources.." | DAMAGED: "..powerSourceInfo.numDamagedSources.." | INTACT: "..powerSourceInfo.numIntactSources)
1024
+ else
1025
+ self:printOutputToLog("NO POWER SOURCES SET")
1026
+ end
1027
+ self:printOutputToLog("---------------------------------------------------")
1028
+ end
1029
+ end
1030
+
1031
+ function SkynetIADSLogger:printSystemStatus()
1032
+
1033
+ if self:getDebugSettings().IADSStatus or self:getDebugSettings().contacts then
1034
+ local coalitionStr = self.iads:getCoalitionString()
1035
+ self:printOutput("---- IADS: "..coalitionStr.." ------")
1036
+ end
1037
+
1038
+ if self:getDebugSettings().IADSStatus then
1039
+
1040
+ local commandCenters = self.iads:getCommandCenters()
1041
+ local numComCenters = #commandCenters
1042
+ local numDestroyedComCenters = 0
1043
+ local numComCentersNoPower = 0
1044
+ local numComCentersNoConnectionNode = 0
1045
+ local numIntactComCenters = 0
1046
+ for i = 1, #commandCenters do
1047
+ local commandCenter = commandCenters[i]
1048
+ if commandCenter:hasWorkingPowerSource() == false then
1049
+ numComCentersNoPower = numComCentersNoPower + 1
1050
+ end
1051
+ if commandCenter:hasActiveConnectionNode() == false then
1052
+ numComCentersNoConnectionNode = numComCentersNoConnectionNode + 1
1053
+ end
1054
+ if commandCenter:isDestroyed() == false then
1055
+ numIntactComCenters = numIntactComCenters + 1
1056
+ end
1057
+ end
1058
+
1059
+ numDestroyedComCenters = numComCenters - numIntactComCenters
1060
+
1061
+
1062
+ self:printOutput("COMMAND CENTERS: "..numComCenters.." | Destroyed: "..numDestroyedComCenters.." | NoPowr: "..numComCentersNoPower.." | NoCon: "..numComCentersNoConnectionNode)
1063
+
1064
+ local ewNoPower = 0
1065
+ local earlyWarningRadars = self.iads:getEarlyWarningRadars()
1066
+ local ewTotal = #earlyWarningRadars
1067
+ local ewNoConnectionNode = 0
1068
+ local ewActive = 0
1069
+ local ewRadarsInactive = 0
1070
+
1071
+ for i = 1, #earlyWarningRadars do
1072
+ local ewRadar = earlyWarningRadars[i]
1073
+ if ewRadar:hasWorkingPowerSource() == false then
1074
+ ewNoPower = ewNoPower + 1
1075
+ end
1076
+ if ewRadar:hasActiveConnectionNode() == false then
1077
+ ewNoConnectionNode = ewNoConnectionNode + 1
1078
+ end
1079
+ if ewRadar:isActive() then
1080
+ ewActive = ewActive + 1
1081
+ end
1082
+ end
1083
+
1084
+ ewRadarsInactive = ewTotal - ewActive
1085
+ local numEWRadarsDestroyed = #self.iads:getDestroyedEarlyWarningRadars()
1086
+ self:printOutput("EW: "..ewTotal.." | On: "..ewActive.." | Off: "..ewRadarsInactive.." | Destroyed: "..numEWRadarsDestroyed.." | NoPowr: "..ewNoPower.." | NoCon: "..ewNoConnectionNode)
1087
+
1088
+ local samSitesInactive = 0
1089
+ local samSitesActive = 0
1090
+ local samSites = self.iads:getSAMSites()
1091
+ local samSitesTotal = #samSites
1092
+ local samSitesNoPower = 0
1093
+ local samSitesNoConnectionNode = 0
1094
+ local samSitesOutOfAmmo = 0
1095
+ local samSiteAutonomous = 0
1096
+ local samSiteRadarDestroyed = 0
1097
+ for i = 1, #samSites do
1098
+ local samSite = samSites[i]
1099
+ if samSite:hasWorkingPowerSource() == false then
1100
+ samSitesNoPower = samSitesNoPower + 1
1101
+ end
1102
+ if samSite:hasActiveConnectionNode() == false then
1103
+ samSitesNoConnectionNode = samSitesNoConnectionNode + 1
1104
+ end
1105
+ if samSite:isActive() then
1106
+ samSitesActive = samSitesActive + 1
1107
+ end
1108
+ if samSite:hasRemainingAmmo() == false then
1109
+ samSitesOutOfAmmo = samSitesOutOfAmmo + 1
1110
+ end
1111
+ if samSite:getAutonomousState() == true then
1112
+ samSiteAutonomous = samSiteAutonomous + 1
1113
+ end
1114
+ if samSite:hasWorkingRadar() == false then
1115
+ samSiteRadarDestroyed = samSiteRadarDestroyed + 1
1116
+ end
1117
+ end
1118
+
1119
+ samSitesInactive = samSitesTotal - samSitesActive
1120
+ self:printOutput("SAM: "..samSitesTotal.." | On: "..samSitesActive.." | Off: "..samSitesInactive.." | Autonm: "..samSiteAutonomous.." | Raddest: "..samSiteRadarDestroyed.." | NoPowr: "..samSitesNoPower.." | NoCon: "..samSitesNoConnectionNode.." | NoAmmo: "..samSitesOutOfAmmo)
1121
+ end
1122
+
1123
+ if self:getDebugSettings().contacts then
1124
+ local contacts = self.iads:getContacts()
1125
+ if contacts then
1126
+ for i = 1, #contacts do
1127
+ local contact = contacts[i]
1128
+ self:printOutput("CONTACT: "..contact:getName().." | TYPE: "..contact:getTypeName().." | GS: "..tostring(contact:getGroundSpeedInKnots()).." | LAST SEEN: "..contact:getAge())
1129
+ end
1130
+ end
1131
+ end
1132
+
1133
+ if self:getDebugSettings().commandCenterStatusEnvOutput then
1134
+ self:printCommandCenterStatus()
1135
+ end
1136
+
1137
+ if self:getDebugSettings().earlyWarningRadarStatusEnvOutput then
1138
+ self:printEarlyWarningRadarStatus()
1139
+ end
1140
+
1141
+ if self:getDebugSettings().samSiteStatusEnvOutput then
1142
+ self:printSAMSiteStatus()
1143
+ end
1144
+
1145
+ end
1146
+
1147
+ end
1148
+ do
1149
+
1150
+ SkynetIADS = {}
1151
+ SkynetIADS.__index = SkynetIADS
1152
+
1153
+ SkynetIADS.database = samTypesDB
1154
+
1155
+ function SkynetIADS:create(name)
1156
+ local iads = {}
1157
+ setmetatable(iads, SkynetIADS)
1158
+ iads.radioMenu = nil
1159
+ iads.earlyWarningRadars = {}
1160
+ iads.samSites = {}
1161
+ iads.commandCenters = {}
1162
+ iads.ewRadarScanMistTaskID = nil
1163
+ iads.coalition = nil
1164
+ iads.contacts = {}
1165
+ iads.maxTargetAge = 32
1166
+ iads.name = name
1167
+ iads.harmDetection = SkynetIADSHARMDetection:create(iads)
1168
+ iads.logger = SkynetIADSLogger:create(iads)
1169
+ if iads.name == nil then
1170
+ iads.name = ""
1171
+ end
1172
+ iads.contactUpdateInterval = 5
1173
+ return iads
1174
+ end
1175
+
1176
+ function SkynetIADS:setUpdateInterval(interval)
1177
+ self.contactUpdateInterval = interval
1178
+ end
1179
+
1180
+ function SkynetIADS:setCoalition(item)
1181
+ if item then
1182
+ local coalitionID = item:getCoalition()
1183
+ if self.coalitionID == nil then
1184
+ self.coalitionID = coalitionID
1185
+ end
1186
+ if self.coalitionID ~= coalitionID then
1187
+ self:printOutputToLog("element: "..item:getName().." has a different coalition than the IADS", true)
1188
+ end
1189
+ end
1190
+ end
1191
+
1192
+ function SkynetIADS:addJammer(jammer)
1193
+ table.insert(self.jammers, jammer)
1194
+ end
1195
+
1196
+ function SkynetIADS:getCoalition()
1197
+ return self.coalitionID
1198
+ end
1199
+
1200
+ function SkynetIADS:getDestroyedEarlyWarningRadars()
1201
+ local destroyedSites = {}
1202
+ for i = 1, #self.earlyWarningRadars do
1203
+ local ewSite = self.earlyWarningRadars[i]
1204
+ if ewSite:isDestroyed() then
1205
+ table.insert(destroyedSites, ewSite)
1206
+ end
1207
+ end
1208
+ return destroyedSites
1209
+ end
1210
+
1211
+ function SkynetIADS:getUsableAbstractRadarElemtentsOfTable(abstractRadarTable)
1212
+ local usable = {}
1213
+ for i = 1, #abstractRadarTable do
1214
+ local abstractRadarElement = abstractRadarTable[i]
1215
+ if abstractRadarElement:hasActiveConnectionNode() and abstractRadarElement:hasWorkingPowerSource() and abstractRadarElement:isDestroyed() == false then
1216
+ table.insert(usable, abstractRadarElement)
1217
+ end
1218
+ end
1219
+ return usable
1220
+ end
1221
+
1222
+ function SkynetIADS:getUsableEarlyWarningRadars()
1223
+ return self:getUsableAbstractRadarElemtentsOfTable(self.earlyWarningRadars)
1224
+ end
1225
+
1226
+ function SkynetIADS:createTableDelegator(units)
1227
+ local sites = SkynetIADSTableDelegator:create()
1228
+ for i = 1, #units do
1229
+ local site = units[i]
1230
+ table.insert(sites, site)
1231
+ end
1232
+ return sites
1233
+ end
1234
+
1235
+ function SkynetIADS:addEarlyWarningRadarsByPrefix(prefix)
1236
+ self:deactivateEarlyWarningRadars()
1237
+ self.earlyWarningRadars = {}
1238
+ for unitName, unit in pairs(mist.DBs.unitsByName) do
1239
+ local pos = self:findSubString(unitName, prefix)
1240
+ --somehow the MIST unit db contains StaticObject, we check to see we only add Units
1241
+ local unit = Unit.getByName(unitName)
1242
+ if pos and pos == 1 and unit then
1243
+ self:addEarlyWarningRadar(unitName)
1244
+ end
1245
+ end
1246
+ return self:createTableDelegator(self.earlyWarningRadars)
1247
+ end
1248
+
1249
+ function SkynetIADS:addEarlyWarningRadar(earlyWarningRadarUnitName)
1250
+ local earlyWarningRadarUnit = Unit.getByName(earlyWarningRadarUnitName)
1251
+ if earlyWarningRadarUnit == nil then
1252
+ self:printOutputToLog("you have added an EW Radar that does not exist, check name of Unit in Setup and Mission editor: "..earlyWarningRadarUnitName, true)
1253
+ return
1254
+ end
1255
+ self:setCoalition(earlyWarningRadarUnit)
1256
+ local ewRadar = nil
1257
+ local category = earlyWarningRadarUnit:getDesc().category
1258
+ if category == Unit.Category.AIRPLANE or category == Unit.Category.SHIP then
1259
+ ewRadar = SkynetIADSAWACSRadar:create(earlyWarningRadarUnit, self)
1260
+ else
1261
+ ewRadar = SkynetIADSEWRadar:create(earlyWarningRadarUnit, self)
1262
+ end
1263
+ ewRadar:setupElements()
1264
+ ewRadar:setCachedTargetsMaxAge(self:getCachedTargetsMaxAge())
1265
+ -- for performance improvement, if iads is not scanning no update coverage update needs to be done, will be executed once when iads activates
1266
+ if self.ewRadarScanMistTaskID ~= nil then
1267
+ self:buildRadarCoverageForEarlyWarningRadar(ewRadar)
1268
+ end
1269
+ ewRadar:setActAsEW(true)
1270
+ ewRadar:setToCorrectAutonomousState()
1271
+ ewRadar:goLive()
1272
+ table.insert(self.earlyWarningRadars, ewRadar)
1273
+ if self:getDebugSettings().addedEWRadar then
1274
+ self:printOutputToLog("ADDED: "..ewRadar:getDescription())
1275
+ end
1276
+ return ewRadar
1277
+ end
1278
+
1279
+ function SkynetIADS:getCachedTargetsMaxAge()
1280
+ return self.contactUpdateInterval
1281
+ end
1282
+
1283
+ function SkynetIADS:getEarlyWarningRadars()
1284
+ return self:createTableDelegator(self.earlyWarningRadars)
1285
+ end
1286
+
1287
+ function SkynetIADS:getEarlyWarningRadarByUnitName(unitName)
1288
+ for i = 1, #self.earlyWarningRadars do
1289
+ local ewRadar = self.earlyWarningRadars[i]
1290
+ if ewRadar:getDCSName() == unitName then
1291
+ return ewRadar
1292
+ end
1293
+ end
1294
+ end
1295
+
1296
+ function SkynetIADS:findSubString(haystack, needle)
1297
+ return string.find(haystack, needle, 1, true)
1298
+ end
1299
+
1300
+ function SkynetIADS:addSAMSitesByPrefix(prefix)
1301
+ self:deativateSAMSites()
1302
+ self.samSites = {}
1303
+ for groupName, groupData in pairs(mist.DBs.groupsByName) do
1304
+ local pos = self:findSubString(groupName, prefix)
1305
+ if pos and pos == 1 then
1306
+ --mist returns groups, units and, StaticObjects
1307
+ local dcsObject = Group.getByName(groupName)
1308
+ if dcsObject then
1309
+ self:addSAMSite(groupName)
1310
+ end
1311
+ end
1312
+ end
1313
+ return self:createTableDelegator(self.samSites)
1314
+ end
1315
+
1316
+ function SkynetIADS:getSAMSitesByPrefix(prefix)
1317
+ local returnSams = {}
1318
+ for i = 1, #self.samSites do
1319
+ local samSite = self.samSites[i]
1320
+ local groupName = samSite:getDCSName()
1321
+ local pos = self:findSubString(groupName, prefix)
1322
+ if pos and pos == 1 then
1323
+ table.insert(returnSams, samSite)
1324
+ end
1325
+ end
1326
+ return self:createTableDelegator(returnSams)
1327
+ end
1328
+
1329
+ function SkynetIADS:addSAMSite(samSiteName)
1330
+ local samSiteDCS = Group.getByName(samSiteName)
1331
+ if samSiteDCS == nil then
1332
+ self:printOutputToLog("you have added an SAM Site that does not exist, check name of Group in Setup and Mission editor: "..tostring(samSiteName), true)
1333
+ return
1334
+ end
1335
+ self:setCoalition(samSiteDCS)
1336
+ local samSite = SkynetIADSSamSite:create(samSiteDCS, self)
1337
+ samSite:setupElements()
1338
+ samSite:setCanEngageAirWeapons(true)
1339
+ samSite:goLive()
1340
+ samSite:setCachedTargetsMaxAge(self:getCachedTargetsMaxAge())
1341
+ if samSite:getNatoName() == "UNKNOWN" then
1342
+ self:printOutputToLog("you have added an SAM site that Skynet IADS can not handle: "..samSite:getDCSName(), true)
1343
+ samSite:cleanUp()
1344
+ else
1345
+ samSite:goDark()
1346
+ table.insert(self.samSites, samSite)
1347
+ if self:getDebugSettings().addedSAMSite then
1348
+ self:printOutputToLog("ADDED: "..samSite:getDescription())
1349
+ end
1350
+ -- for performance improvement, if iads is not scanning no update coverage update needs to be done, will be executed once when iads activates
1351
+ if self.ewRadarScanMistTaskID ~= nil then
1352
+ self:buildRadarCoverageForSAMSite(samSite)
1353
+ end
1354
+ return samSite
1355
+ end
1356
+ end
1357
+
1358
+ function SkynetIADS:getUsableSAMSites()
1359
+ return self:getUsableAbstractRadarElemtentsOfTable(self.samSites)
1360
+ end
1361
+
1362
+ function SkynetIADS:getDestroyedSAMSites()
1363
+ local destroyedSites = {}
1364
+ for i = 1, #self.samSites do
1365
+ local samSite = self.samSites[i]
1366
+ if samSite:isDestroyed() then
1367
+ table.insert(destroyedSites, samSite)
1368
+ end
1369
+ end
1370
+ return destroyedSites
1371
+ end
1372
+
1373
+ function SkynetIADS:getSAMSites()
1374
+ return self:createTableDelegator(self.samSites)
1375
+ end
1376
+
1377
+ function SkynetIADS:getActiveSAMSites()
1378
+ local activeSAMSites = {}
1379
+ for i = 1, #self.samSites do
1380
+ if self.samSites[i]:isActive() then
1381
+ table.insert(activeSAMSites, self.samSites[i])
1382
+ end
1383
+ end
1384
+ return activeSAMSites
1385
+ end
1386
+
1387
+ function SkynetIADS:getSAMSiteByGroupName(groupName)
1388
+ for i = 1, #self.samSites do
1389
+ local samSite = self.samSites[i]
1390
+ if samSite:getDCSName() == groupName then
1391
+ return samSite
1392
+ end
1393
+ end
1394
+ end
1395
+
1396
+ function SkynetIADS:getSAMSitesByNatoName(natoName)
1397
+ local selectedSAMSites = SkynetIADSTableDelegator:create()
1398
+ for i = 1, #self.samSites do
1399
+ local samSite = self.samSites[i]
1400
+ if samSite:getNatoName() == natoName then
1401
+ table.insert(selectedSAMSites, samSite)
1402
+ end
1403
+ end
1404
+ return selectedSAMSites
1405
+ end
1406
+
1407
+ function SkynetIADS:addCommandCenter(commandCenter)
1408
+ self:setCoalition(commandCenter)
1409
+ local comCenter = SkynetIADSCommandCenter:create(commandCenter, self)
1410
+ table.insert(self.commandCenters, comCenter)
1411
+ -- when IADS is active the radars will be added to the new command center. If it not active this will happen when radar coverage is built
1412
+ if self.ewRadarScanMistTaskID ~= nil then
1413
+ self:addRadarsToCommandCenters()
1414
+ end
1415
+ return comCenter
1416
+ end
1417
+
1418
+ function SkynetIADS:isCommandCenterUsable()
1419
+ if #self:getCommandCenters() == 0 then
1420
+ return true
1421
+ end
1422
+ local usableComCenters = self:getUsableAbstractRadarElemtentsOfTable(self:getCommandCenters())
1423
+ return (#usableComCenters > 0)
1424
+ end
1425
+
1426
+ function SkynetIADS:getCommandCenters()
1427
+ return self.commandCenters
1428
+ end
1429
+
1430
+
1431
+ function SkynetIADS.evaluateContacts(self)
1432
+
1433
+ local ewRadars = self:getUsableEarlyWarningRadars()
1434
+ local samSites = self:getUsableSAMSites()
1435
+
1436
+ --will add SAM Sites acting as EW Rardars to the ewRadars array:
1437
+ for i = 1, #samSites do
1438
+ local samSite = samSites[i]
1439
+ --We inform SAM sites that a target update is about to happen. If they have no targets in range after the cycle they go dark
1440
+ samSite:targetCycleUpdateStart()
1441
+ if samSite:getActAsEW() then
1442
+ table.insert(ewRadars, samSite)
1443
+ end
1444
+ --if the sam site is not in ew mode and active we grab the detected targets right here
1445
+ if samSite:isActive() and samSite:getActAsEW() == false then
1446
+ local contacts = samSite:getDetectedTargets()
1447
+ for j = 1, #contacts do
1448
+ local contact = contacts[j]
1449
+ self:mergeContact(contact)
1450
+ end
1451
+ end
1452
+ end
1453
+
1454
+ local samSitesToTrigger = {}
1455
+
1456
+ for i = 1, #ewRadars do
1457
+ local ewRadar = ewRadars[i]
1458
+ --call go live in case ewRadar had to shut down (HARM attack)
1459
+ ewRadar:goLive()
1460
+ -- if an awacs has traveled more than a predeterminded distance we update the autonomous state of the SAMs
1461
+ if getmetatable(ewRadar) == SkynetIADSAWACSRadar and ewRadar:isUpdateOfAutonomousStateOfSAMSitesRequired() then
1462
+ self:buildRadarCoverageForEarlyWarningRadar(ewRadar)
1463
+ end
1464
+ local ewContacts = ewRadar:getDetectedTargets()
1465
+ if #ewContacts > 0 then
1466
+ local samSitesUnderCoverage = ewRadar:getUsableChildRadars()
1467
+ for j = 1, #samSitesUnderCoverage do
1468
+ local samSiteUnterCoverage = samSitesUnderCoverage[j]
1469
+ -- only if a SAM site is not active we add it to the hash of SAM sites to be iterated later on
1470
+ if samSiteUnterCoverage:isActive() == false then
1471
+ --we add them to a hash to make sure each SAM site is in the collection only once, reducing the number of loops we conduct later on
1472
+ samSitesToTrigger[samSiteUnterCoverage:getDCSName()] = samSiteUnterCoverage
1473
+ end
1474
+ end
1475
+ for j = 1, #ewContacts do
1476
+ local contact = ewContacts[j]
1477
+ self:mergeContact(contact)
1478
+ end
1479
+ end
1480
+ end
1481
+
1482
+ self:cleanAgedTargets()
1483
+
1484
+ for samName, samToTrigger in pairs(samSitesToTrigger) do
1485
+ for j = 1, #self.contacts do
1486
+ local contact = self.contacts[j]
1487
+ -- the DCS Radar only returns enemy aircraft, if that should change a coalition check will be required
1488
+ -- currently every type of object in the air is handed of to the SAM site, including missiles
1489
+ local description = contact:getDesc()
1490
+ local category = description.category
1491
+ if category and category ~= Unit.Category.GROUND_UNIT and category ~= Unit.Category.SHIP and category ~= Unit.Category.STRUCTURE then
1492
+ samToTrigger:informOfContact(contact)
1493
+ end
1494
+ end
1495
+ end
1496
+
1497
+ for i = 1, #samSites do
1498
+ local samSite = samSites[i]
1499
+ samSite:targetCycleUpdateEnd()
1500
+ end
1501
+
1502
+ self.harmDetection:setContacts(self:getContacts())
1503
+ self.harmDetection:evaluateContacts()
1504
+
1505
+ self.logger:printSystemStatus()
1506
+ end
1507
+
1508
+ function SkynetIADS:cleanAgedTargets()
1509
+ local contactsToKeep = {}
1510
+ for i = 1, #self.contacts do
1511
+ local contact = self.contacts[i]
1512
+ if contact:getAge() < self.maxTargetAge then
1513
+ table.insert(contactsToKeep, contact)
1514
+ end
1515
+ end
1516
+ self.contacts = contactsToKeep
1517
+ end
1518
+
1519
+ --TODO unit test this method:
1520
+ function SkynetIADS:getAbstracRadarElements()
1521
+ local abstractRadarElements = {}
1522
+ local ewRadars = self:getEarlyWarningRadars()
1523
+ local samSites = self:getSAMSites()
1524
+
1525
+ for i = 1, #ewRadars do
1526
+ local ewRadar = ewRadars[i]
1527
+ table.insert(abstractRadarElements, ewRadar)
1528
+ end
1529
+
1530
+ for i = 1, #samSites do
1531
+ local samSite = samSites[i]
1532
+ table.insert(abstractRadarElements, samSite)
1533
+ end
1534
+ return abstractRadarElements
1535
+ end
1536
+
1537
+
1538
+ function SkynetIADS:addRadarsToCommandCenters()
1539
+
1540
+ --we clear any existing radars that may have been added earlier
1541
+ local comCenters = self:getCommandCenters()
1542
+ for i = 1, #comCenters do
1543
+ local comCenter = comCenters[i]
1544
+ comCenter:clearChildRadars()
1545
+ end
1546
+
1547
+ -- then we add child radars to the command centers
1548
+ local abstractRadarElements = self:getAbstracRadarElements()
1549
+ for i = 1, #abstractRadarElements do
1550
+ local abstractRadar = abstractRadarElements[i]
1551
+ self:addSingleRadarToCommandCenters(abstractRadar)
1552
+ end
1553
+ end
1554
+
1555
+ function SkynetIADS:addSingleRadarToCommandCenters(abstractRadarElement)
1556
+ local comCenters = self:getCommandCenters()
1557
+ for i = 1, #comCenters do
1558
+ local comCenter = comCenters[i]
1559
+ comCenter:addChildRadar(abstractRadarElement)
1560
+ end
1561
+ end
1562
+
1563
+ -- this method rebuilds the radar coverage of the IADS, a complete rebuild is only required the first time the IADS is activated
1564
+ -- during runtime it is sufficient to call buildRadarCoverageForSAMSite or buildRadarCoverageForEarlyWarningRadar method that just updates the IADS for one unit, this saves script execution time
1565
+ function SkynetIADS:buildRadarCoverage()
1566
+
1567
+ --to build the basic radar coverage we use all SAM sites. Checks if SAM site has power or a connection node is done when using the SAM site later on
1568
+ local samSites = self:getSAMSites()
1569
+
1570
+ --first we clear all child and parent radars that may have been added previously
1571
+ for i = 1, #samSites do
1572
+ local samSite = samSites[i]
1573
+ samSite:clearChildRadars()
1574
+ samSite:clearParentRadars()
1575
+ end
1576
+
1577
+ local ewRadars = self:getEarlyWarningRadars()
1578
+
1579
+ for i = 1, #ewRadars do
1580
+ local ewRadar = ewRadars[i]
1581
+ ewRadar:clearChildRadars()
1582
+ end
1583
+
1584
+ --then we rebuild the radar coverage
1585
+ local abstractRadarElements = self:getAbstracRadarElements()
1586
+ for i = 1, #abstractRadarElements do
1587
+ local abstract = abstractRadarElements[i]
1588
+ self:buildRadarCoverageForAbstractRadarElement(abstract)
1589
+ end
1590
+
1591
+ self:addRadarsToCommandCenters()
1592
+
1593
+ --we call this once on all sam sites, to make sure autonomous sites go live when IADS activates
1594
+ for i = 1, #samSites do
1595
+ local samSite = samSites[i]
1596
+ samSite:informChildrenOfStateChange()
1597
+ end
1598
+
1599
+ end
1600
+
1601
+ function SkynetIADS:buildRadarCoverageForAbstractRadarElement(abstractRadarElement)
1602
+ local abstractRadarElements = self:getAbstracRadarElements()
1603
+ for i = 1, #abstractRadarElements do
1604
+ local aElementToCompare = abstractRadarElements[i]
1605
+ if aElementToCompare ~= abstractRadarElement then
1606
+ if abstractRadarElement:isInRadarDetectionRangeOf(aElementToCompare) then
1607
+ self:buildRadarAssociation(aElementToCompare, abstractRadarElement)
1608
+ end
1609
+ if aElementToCompare:isInRadarDetectionRangeOf(abstractRadarElement) then
1610
+ self:buildRadarAssociation(abstractRadarElement, aElementToCompare)
1611
+ end
1612
+ end
1613
+ end
1614
+ end
1615
+
1616
+ function SkynetIADS:buildRadarAssociation(parent, child)
1617
+ --chilren should only be SAM sites not EW radars
1618
+ if ( getmetatable(child) == SkynetIADSSamSite ) then
1619
+ parent:addChildRadar(child)
1620
+ end
1621
+ --Only SAM Sites should have parent Radars, not EW Radars
1622
+ if ( getmetatable(child) == SkynetIADSSamSite ) then
1623
+ child:addParentRadar(parent)
1624
+ end
1625
+ end
1626
+
1627
+ function SkynetIADS:buildRadarCoverageForSAMSite(samSite)
1628
+ self:buildRadarCoverageForAbstractRadarElement(samSite)
1629
+ self:addSingleRadarToCommandCenters(samSite)
1630
+ end
1631
+
1632
+ function SkynetIADS:buildRadarCoverageForEarlyWarningRadar(ewRadar)
1633
+ self:buildRadarCoverageForAbstractRadarElement(ewRadar)
1634
+ self:addSingleRadarToCommandCenters(ewRadar)
1635
+ end
1636
+
1637
+ function SkynetIADS:mergeContact(contact)
1638
+ local existingContact = false
1639
+ for i = 1, #self.contacts do
1640
+ local iadsContact = self.contacts[i]
1641
+ if iadsContact:getName() == contact:getName() then
1642
+ iadsContact:refresh()
1643
+ --these contacts are used in the logger we set a kown harm state of a contact coming from a SAM site. So the logger will show them als HARMs
1644
+ contact:setHARMState(iadsContact:getHARMState())
1645
+ local radars = contact:getAbstractRadarElementsDetected()
1646
+ for j = 1, #radars do
1647
+ local radar = radars[j]
1648
+ iadsContact:addAbstractRadarElementDetected(radar)
1649
+ end
1650
+ existingContact = true
1651
+ end
1652
+ end
1653
+ if existingContact == false then
1654
+ table.insert(self.contacts, contact)
1655
+ end
1656
+ end
1657
+
1658
+
1659
+ function SkynetIADS:getContacts()
1660
+ return self.contacts
1661
+ end
1662
+
1663
+ function SkynetIADS:getDebugSettings()
1664
+ return self.logger.debugOutput
1665
+ end
1666
+
1667
+ function SkynetIADS:printOutput(output, typeWarning)
1668
+ self.logger:printOutput(output, typeWarning)
1669
+ end
1670
+
1671
+ function SkynetIADS:printOutputToLog(output)
1672
+ self.logger:printOutputToLog(output)
1673
+ end
1674
+
1675
+ -- will start going through the Early Warning Radars and SAM sites to check what targets they have detected
1676
+ function SkynetIADS.activate(self)
1677
+ mist.removeFunction(self.ewRadarScanMistTaskID)
1678
+ self.ewRadarScanMistTaskID = mist.scheduleFunction(SkynetIADS.evaluateContacts, {self}, 1, self.contactUpdateInterval)
1679
+ self:buildRadarCoverage()
1680
+ end
1681
+
1682
+ function SkynetIADS:setupSAMSitesAndThenActivate(setupTime)
1683
+ self:activate()
1684
+ self.logger:printOutputToLog("DEPRECATED: setupSAMSitesAndThenActivate, no longer needed since using enableEmission instead of AI on / off allows for the Ground units to setup with their radars turned off")
1685
+ end
1686
+
1687
+ function SkynetIADS:deactivate()
1688
+ mist.removeFunction(self.ewRadarScanMistTaskID)
1689
+ mist.removeFunction(self.samSetupMistTaskID)
1690
+ self:deativateSAMSites()
1691
+ self:deactivateEarlyWarningRadars()
1692
+ self:deactivateCommandCenters()
1693
+ end
1694
+
1695
+ function SkynetIADS:deactivateCommandCenters()
1696
+ for i = 1, #self.commandCenters do
1697
+ local comCenter = self.commandCenters[i]
1698
+ comCenter:cleanUp()
1699
+ end
1700
+ end
1701
+
1702
+ function SkynetIADS:deativateSAMSites()
1703
+ for i = 1, #self.samSites do
1704
+ local samSite = self.samSites[i]
1705
+ samSite:cleanUp()
1706
+ end
1707
+ end
1708
+
1709
+ function SkynetIADS:deactivateEarlyWarningRadars()
1710
+ for i = 1, #self.earlyWarningRadars do
1711
+ local ewRadar = self.earlyWarningRadars[i]
1712
+ ewRadar:cleanUp()
1713
+ end
1714
+ end
1715
+
1716
+ function SkynetIADS:addRadioMenu()
1717
+ self.radioMenu = missionCommands.addSubMenu('SKYNET IADS '..self:getCoalitionString())
1718
+ local displayIADSStatus = missionCommands.addCommand('show IADS Status', self.radioMenu, SkynetIADS.updateDisplay, {self = self, value = true, option = 'IADSStatus'})
1719
+ local displayIADSStatus = missionCommands.addCommand('hide IADS Status', self.radioMenu, SkynetIADS.updateDisplay, {self = self, value = false, option = 'IADSStatus'})
1720
+ local displayIADSStatus = missionCommands.addCommand('show contacts', self.radioMenu, SkynetIADS.updateDisplay, {self = self, value = true, option = 'contacts'})
1721
+ local displayIADSStatus = missionCommands.addCommand('hide contacts', self.radioMenu, SkynetIADS.updateDisplay, {self = self, value = false, option = 'contacts'})
1722
+ end
1723
+
1724
+ function SkynetIADS:removeRadioMenu()
1725
+ missionCommands.removeItem(self.radioMenu)
1726
+ end
1727
+
1728
+ function SkynetIADS.updateDisplay(params)
1729
+ local option = params.option
1730
+ local self = params.self
1731
+ local value = params.value
1732
+ if option == 'IADSStatus' then
1733
+ self:getDebugSettings()[option] = value
1734
+ elseif option == 'contacts' then
1735
+ self:getDebugSettings()[option] = value
1736
+ end
1737
+ end
1738
+
1739
+ function SkynetIADS:getCoalitionString()
1740
+ local coalitionStr = "RED"
1741
+ if self.coalitionID == coalition.side.BLUE then
1742
+ coalitionStr = "BLUE"
1743
+ elseif self.coalitionID == coalition.side.NEUTRAL then
1744
+ coalitionStr = "NEUTRAL"
1745
+ end
1746
+
1747
+ if self.name then
1748
+ coalitionStr = "COALITION: "..coalitionStr.." | NAME: "..self.name
1749
+ end
1750
+
1751
+ return coalitionStr
1752
+ end
1753
+
1754
+ function SkynetIADS:getMooseConnector()
1755
+ if self.mooseConnector == nil then
1756
+ self.mooseConnector = SkynetMooseA2ADispatcherConnector:create(self)
1757
+ end
1758
+ return self.mooseConnector
1759
+ end
1760
+
1761
+ function SkynetIADS:addMooseSetGroup(mooseSetGroup)
1762
+ self:getMooseConnector():addMooseSetGroup(mooseSetGroup)
1763
+ end
1764
+
1765
+ end
1766
+ do
1767
+
1768
+ SkynetMooseA2ADispatcherConnector = {}
1769
+
1770
+ function SkynetMooseA2ADispatcherConnector:create(iads)
1771
+ local instance = {}
1772
+ setmetatable(instance, self)
1773
+ self.__index = self
1774
+ instance.iadsCollection = {}
1775
+ instance.mooseGroups = {}
1776
+ instance.ewRadarGroupNames = {}
1777
+ instance.samSiteGroupNames = {}
1778
+ table.insert(instance.iadsCollection, iads)
1779
+ return instance
1780
+ end
1781
+
1782
+ function SkynetMooseA2ADispatcherConnector:addIADS(iads)
1783
+ table.insert(self.iadsCollection, iads)
1784
+ end
1785
+
1786
+ function SkynetMooseA2ADispatcherConnector:addMooseSetGroup(mooseSetGroup)
1787
+ table.insert(self.mooseGroups, mooseSetGroup)
1788
+ self:update()
1789
+ end
1790
+
1791
+ function SkynetMooseA2ADispatcherConnector:getEarlyWarningRadarGroupNames()
1792
+ self.ewRadarGroupNames = {}
1793
+ for i = 1, #self.iadsCollection do
1794
+ local ewRadars = self.iadsCollection[i]:getUsableEarlyWarningRadars()
1795
+ for j = 1, #ewRadars do
1796
+ local ewRadar = ewRadars[j]
1797
+ table.insert(self.ewRadarGroupNames, ewRadar:getDCSRepresentation():getGroup():getName())
1798
+ end
1799
+ end
1800
+ return self.ewRadarGroupNames
1801
+ end
1802
+
1803
+ function SkynetMooseA2ADispatcherConnector:getSAMSiteGroupNames()
1804
+ self.samSiteGroupNames = {}
1805
+ for i = 1, #self.iadsCollection do
1806
+ local samSites = self.iadsCollection[i]:getUsableSAMSites()
1807
+ for j = 1, #samSites do
1808
+ local samSite = samSites[j]
1809
+ table.insert(self.samSiteGroupNames, samSite:getDCSName())
1810
+ end
1811
+ end
1812
+ return self.samSiteGroupNames
1813
+ end
1814
+
1815
+ function SkynetMooseA2ADispatcherConnector:update()
1816
+
1817
+ --mooseGroup elements are type of:
1818
+ --https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Core.Set.html##(SET_GROUP)
1819
+
1820
+ --remove previously set group names:
1821
+ for i = 1, #self.mooseGroups do
1822
+ local mooseGroup = self.mooseGroups[i]
1823
+ mooseGroup:RemoveGroupsByName(self.ewRadarGroupNames)
1824
+ mooseGroup:RemoveGroupsByName(self.samSiteGroupNames)
1825
+ end
1826
+
1827
+ --add group names of IADS radars that are currently usable by the IADS:
1828
+ for i = 1, #self.mooseGroups do
1829
+ local mooseGroup = self.mooseGroups[i]
1830
+ mooseGroup:AddGroupsByName(self:getEarlyWarningRadarGroupNames())
1831
+ mooseGroup:AddGroupsByName(self:getSAMSiteGroupNames())
1832
+ end
1833
+ end
1834
+
1835
+ end
1836
+ do
1837
+
1838
+
1839
+ SkynetIADSTableDelegator = {}
1840
+
1841
+ function SkynetIADSTableDelegator:create()
1842
+ local instance = {}
1843
+ local forwarder = {}
1844
+ forwarder.__index = function(tbl, name)
1845
+ tbl[name] = function(self, ...)
1846
+ for i = 1, #self do
1847
+ self[i][name](self[i], ...)
1848
+ end
1849
+ return self
1850
+ end
1851
+ return tbl[name]
1852
+ end
1853
+ setmetatable(instance, forwarder)
1854
+ instance.__index = forwarder
1855
+ return instance
1856
+ end
1857
+
1858
+ end
1859
+ do
1860
+
1861
+ SkynetIADSAbstractDCSObjectWrapper = {}
1862
+
1863
+ function SkynetIADSAbstractDCSObjectWrapper:create(dcsRepresentation)
1864
+ local instance = {}
1865
+ setmetatable(instance, self)
1866
+ self.__index = self
1867
+ instance.dcsName = ""
1868
+ instance.typeName = ""
1869
+ instance:setDCSRepresentation(dcsRepresentation)
1870
+ if getmetatable(dcsRepresentation) ~= Group then
1871
+ instance.typeName = dcsRepresentation:getTypeName()
1872
+ end
1873
+ return instance
1874
+ end
1875
+
1876
+ function SkynetIADSAbstractDCSObjectWrapper:setDCSRepresentation(representation)
1877
+ self.dcsRepresentation = representation
1878
+ if self.dcsRepresentation then
1879
+ self.dcsName = self:getDCSRepresentation():getName()
1880
+ end
1881
+ end
1882
+
1883
+ function SkynetIADSAbstractDCSObjectWrapper:getDCSRepresentation()
1884
+ return self.dcsRepresentation
1885
+ end
1886
+
1887
+ function SkynetIADSAbstractDCSObjectWrapper:getName()
1888
+ return self.dcsName
1889
+ end
1890
+
1891
+ function SkynetIADSAbstractDCSObjectWrapper:getTypeName()
1892
+ return self.typeName
1893
+ end
1894
+
1895
+ function SkynetIADSAbstractDCSObjectWrapper:getPosition()
1896
+ return self.dcsRepresentation:getPosition()
1897
+ end
1898
+
1899
+ function SkynetIADSAbstractDCSObjectWrapper:isExist()
1900
+ if self.dcsRepresentation then
1901
+ return self.dcsRepresentation:isExist()
1902
+ else
1903
+ return false
1904
+ end
1905
+ end
1906
+
1907
+ function SkynetIADSAbstractDCSObjectWrapper:insertToTableIfNotAlreadyAdded(tbl, object)
1908
+ local isAdded = false
1909
+ for i = 1, #tbl do
1910
+ local child = tbl[i]
1911
+ if child == object then
1912
+ isAdded = true
1913
+ end
1914
+ end
1915
+ if isAdded == false then
1916
+ table.insert(tbl, object)
1917
+ end
1918
+ return not isAdded
1919
+ end
1920
+
1921
+ -- helper code for class inheritance
1922
+ function inheritsFrom( baseClass )
1923
+
1924
+ local new_class = {}
1925
+ local class_mt = { __index = new_class }
1926
+
1927
+ function new_class:create()
1928
+ local newinst = {}
1929
+ setmetatable( newinst, class_mt )
1930
+ return newinst
1931
+ end
1932
+
1933
+ if nil ~= baseClass then
1934
+ setmetatable( new_class, { __index = baseClass } )
1935
+ end
1936
+
1937
+ -- Implementation of additional OO properties starts here --
1938
+
1939
+ -- Return the class object of the instance
1940
+ function new_class:class()
1941
+ return new_class
1942
+ end
1943
+
1944
+ -- Return the super class object of the instance
1945
+ function new_class:superClass()
1946
+ return baseClass
1947
+ end
1948
+
1949
+ -- Return true if the caller is an instance of theClass
1950
+ function new_class:isa( theClass )
1951
+ local b_isa = false
1952
+
1953
+ local cur_class = new_class
1954
+
1955
+ while ( nil ~= cur_class ) and ( false == b_isa ) do
1956
+ if cur_class == theClass then
1957
+ b_isa = true
1958
+ else
1959
+ cur_class = cur_class:superClass()
1960
+ end
1961
+ end
1962
+
1963
+ return b_isa
1964
+ end
1965
+
1966
+ return new_class
1967
+ end
1968
+
1969
+
1970
+ end
1971
+
1972
+ do
1973
+
1974
+ SkynetIADSAbstractElement = {}
1975
+ SkynetIADSAbstractElement = inheritsFrom(SkynetIADSAbstractDCSObjectWrapper)
1976
+
1977
+ function SkynetIADSAbstractElement:create(dcsRepresentation, iads)
1978
+ local instance = self:superClass():create(dcsRepresentation)
1979
+ setmetatable(instance, self)
1980
+ self.__index = self
1981
+ instance.connectionNodes = {}
1982
+ instance.powerSources = {}
1983
+ instance.iads = iads
1984
+ instance.natoName = "UNKNOWN"
1985
+ world.addEventHandler(instance)
1986
+ return instance
1987
+ end
1988
+
1989
+ function SkynetIADSAbstractElement:removeEventHandlers()
1990
+ world.removeEventHandler(self)
1991
+ end
1992
+
1993
+ function SkynetIADSAbstractElement:cleanUp()
1994
+ self:removeEventHandlers()
1995
+ end
1996
+
1997
+ function SkynetIADSAbstractElement:isDestroyed()
1998
+ return self:getDCSRepresentation():isExist() == false
1999
+ end
2000
+
2001
+ function SkynetIADSAbstractElement:addPowerSource(powerSource)
2002
+ table.insert(self.powerSources, powerSource)
2003
+ self:informChildrenOfStateChange()
2004
+ return self
2005
+ end
2006
+
2007
+ function SkynetIADSAbstractElement:getPowerSources()
2008
+ return self.powerSources
2009
+ end
2010
+
2011
+ function SkynetIADSAbstractElement:addConnectionNode(connectionNode)
2012
+ table.insert(self.connectionNodes, connectionNode)
2013
+ self:informChildrenOfStateChange()
2014
+ return self
2015
+ end
2016
+
2017
+ function SkynetIADSAbstractElement:getConnectionNodes()
2018
+ return self.connectionNodes
2019
+ end
2020
+
2021
+ function SkynetIADSAbstractElement:hasActiveConnectionNode()
2022
+ local connectionNode = self:genericCheckOneObjectIsAlive(self.connectionNodes)
2023
+ if connectionNode == false and self.iads:getDebugSettings().samNoConnection then
2024
+ self.iads:printOutput(self:getDescription().." no connection to Command Center")
2025
+ end
2026
+ return connectionNode
2027
+ end
2028
+
2029
+ function SkynetIADSAbstractElement:hasWorkingPowerSource()
2030
+ local power = self:genericCheckOneObjectIsAlive(self.powerSources)
2031
+ if power == false and self.iads:getDebugSettings().hasNoPower then
2032
+ self.iads:printOutput(self:getDescription().." has no power")
2033
+ end
2034
+ return power
2035
+ end
2036
+
2037
+ function SkynetIADSAbstractElement:getDCSName()
2038
+ return self.dcsName
2039
+ end
2040
+
2041
+ -- generic function to theck if power plants, command centers, connection nodes are still alive
2042
+ function SkynetIADSAbstractElement:genericCheckOneObjectIsAlive(objects)
2043
+ local isAlive = (#objects == 0)
2044
+ for i = 1, #objects do
2045
+ local object = objects[i]
2046
+ --if we find one object that is not fully destroyed we assume the IADS is still working
2047
+ if object:isExist() then
2048
+ isAlive = true
2049
+ break
2050
+ end
2051
+ end
2052
+ return isAlive
2053
+ end
2054
+
2055
+ function SkynetIADSAbstractElement:getNatoName()
2056
+ return self.natoName
2057
+ end
2058
+
2059
+ function SkynetIADSAbstractElement:getDescription()
2060
+ return "IADS ELEMENT: "..self:getDCSName().." | Type: "..tostring(self:getNatoName())
2061
+ end
2062
+
2063
+ function SkynetIADSAbstractElement:onEvent(event)
2064
+ --if a unit is destroyed we check to see if its a power plant powering the unit or a connection node
2065
+ if event.id == world.event.S_EVENT_DEAD then
2066
+ if self:hasWorkingPowerSource() == false or self:isDestroyed() then
2067
+ self:goDark()
2068
+ self:informChildrenOfStateChange()
2069
+ end
2070
+ if self:hasActiveConnectionNode() == false then
2071
+ self:informChildrenOfStateChange()
2072
+ end
2073
+ end
2074
+ if event.id == world.event.S_EVENT_SHOT then
2075
+ self:weaponFired(event)
2076
+ end
2077
+ end
2078
+
2079
+ --placeholder method, can be implemented by subclasses
2080
+ function SkynetIADSAbstractElement:weaponFired(event)
2081
+
2082
+ end
2083
+
2084
+ --placeholder method, can be implemented by subclasses
2085
+ function SkynetIADSAbstractElement:goDark()
2086
+
2087
+ end
2088
+
2089
+ --placeholder method, can be implemented by subclasses
2090
+ function SkynetIADSAbstractElement:goAutonomous()
2091
+
2092
+ end
2093
+
2094
+ --placeholder method, can be implemented by subclasses
2095
+ function SkynetIADSAbstractElement:setToCorrectAutonomousState()
2096
+
2097
+ end
2098
+
2099
+ --placeholder method, can be implemented by subclasses
2100
+ function SkynetIADSAbstractElement:informChildrenOfStateChange()
2101
+
2102
+ end
2103
+
2104
+ end
2105
+ do
2106
+
2107
+ SkynetIADSAbstractRadarElement = {}
2108
+ SkynetIADSAbstractRadarElement = inheritsFrom(SkynetIADSAbstractElement)
2109
+
2110
+ SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DCS_AI = 1
2111
+ SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DARK = 2
2112
+
2113
+ SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_KILL_ZONE = 1
2114
+ SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_SEARCH_RANGE = 2
2115
+
2116
+ SkynetIADSAbstractRadarElement.HARM_TO_SAM_ASPECT = 15
2117
+ SkynetIADSAbstractRadarElement.HARM_LOOKAHEAD_NM = 20
2118
+
2119
+ function SkynetIADSAbstractRadarElement:create(dcsElementWithRadar, iads)
2120
+ local instance = self:superClass():create(dcsElementWithRadar, iads)
2121
+ setmetatable(instance, self)
2122
+ self.__index = self
2123
+ instance.aiState = false
2124
+ instance.harmScanID = nil
2125
+ instance.harmSilenceID = nil
2126
+ instance.lastJammerUpdate = 0
2127
+ instance.objectsIdentifiedAsHarms = {}
2128
+ instance.objectsIdentifiedAsHarmsMaxTargetAge = 60
2129
+ instance.launchers = {}
2130
+ instance.trackingRadars = {}
2131
+ instance.searchRadars = {}
2132
+ instance.parentRadars = {}
2133
+ instance.childRadars = {}
2134
+ instance.missilesInFlight = {}
2135
+ instance.pointDefences = {}
2136
+ instance.harmDecoys = {}
2137
+ instance.autonomousBehaviour = SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DCS_AI
2138
+ instance.goLiveRange = SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_KILL_ZONE
2139
+ instance.isAutonomous = true
2140
+ instance.harmDetectionChance = 0
2141
+ instance.minHarmShutdownTime = 0
2142
+ instance.maxHarmShutDownTime = 0
2143
+ instance.minHarmPresetShutdownTime = 30
2144
+ instance.maxHarmPresetShutdownTime = 180
2145
+ instance.harmShutdownTime = 0
2146
+ instance.firingRangePercent = 100
2147
+ instance.actAsEW = false
2148
+ instance.cachedTargets = {}
2149
+ instance.cachedTargetsMaxAge = 1
2150
+ instance.cachedTargetsCurrentAge = 0
2151
+ instance.goLiveTime = 0
2152
+ instance.engageAirWeapons = false
2153
+ instance.isAPointDefence = false
2154
+ instance.canEngageHARM = false
2155
+ instance.dataBaseSupportedTypesCanEngageHARM = false
2156
+ -- 5 seconds seems to be a good value for the sam site to find the target with its organic radar
2157
+ instance.noCacheActiveForSecondsAfterGoLive = 5
2158
+ return instance
2159
+ end
2160
+
2161
+ --TODO: this method could be updated to only return Radar weapons fired, this way a SAM firing an IR weapon could go dark faster in the goDark() method
2162
+ function SkynetIADSAbstractRadarElement:weaponFired(event)
2163
+ if event.id == world.event.S_EVENT_SHOT then
2164
+ local weapon = event.weapon
2165
+ local launcherFired = event.initiator
2166
+ for i = 1, #self.launchers do
2167
+ local launcher = self.launchers[i]
2168
+ if launcher:getDCSRepresentation() == launcherFired then
2169
+ table.insert(self.missilesInFlight, weapon)
2170
+ end
2171
+ end
2172
+ end
2173
+ end
2174
+
2175
+ function SkynetIADSAbstractRadarElement:setCachedTargetsMaxAge(maxAge)
2176
+ self.cachedTargetsMaxAge = maxAge
2177
+ end
2178
+
2179
+ function SkynetIADSAbstractRadarElement:cleanUp()
2180
+ for i = 1, #self.pointDefences do
2181
+ local pointDefence = self.pointDefences[i]
2182
+ pointDefence:cleanUp()
2183
+ end
2184
+ mist.removeFunction(self.harmScanID)
2185
+ mist.removeFunction(self.harmSilenceID)
2186
+ --call method from super class
2187
+ self:removeEventHandlers()
2188
+ end
2189
+
2190
+ function SkynetIADSAbstractRadarElement:setIsAPointDefence(state)
2191
+ if (state == true or state == false) then
2192
+ self.isAPointDefence = state
2193
+ end
2194
+ end
2195
+
2196
+ function SkynetIADSAbstractRadarElement:getIsAPointDefence()
2197
+ return self.isAPointDefence
2198
+ end
2199
+
2200
+ function SkynetIADSAbstractRadarElement:addPointDefence(pointDefence)
2201
+ table.insert(self.pointDefences, pointDefence)
2202
+ pointDefence:setIsAPointDefence(true)
2203
+ return self
2204
+ end
2205
+
2206
+ function SkynetIADSAbstractRadarElement:getPointDefences()
2207
+ return self.pointDefences
2208
+ end
2209
+
2210
+ function SkynetIADSAbstractRadarElement:addHARMDecoy(harmDecoy)
2211
+ table.insert(self.harmDecoys, harmDecoy)
2212
+ end
2213
+
2214
+ function SkynetIADSAbstractRadarElement:addParentRadar(parentRadar)
2215
+ self:insertToTableIfNotAlreadyAdded(self.parentRadars, parentRadar)
2216
+ self:informChildrenOfStateChange()
2217
+ end
2218
+
2219
+ function SkynetIADSAbstractRadarElement:getParentRadars()
2220
+ return self.parentRadars
2221
+ end
2222
+
2223
+ function SkynetIADSAbstractRadarElement:clearParentRadars()
2224
+ self.parentRadars = {}
2225
+ end
2226
+
2227
+ function SkynetIADSAbstractRadarElement:addChildRadar(childRadar)
2228
+ self:insertToTableIfNotAlreadyAdded(self.childRadars, childRadar)
2229
+ end
2230
+
2231
+ function SkynetIADSAbstractRadarElement:getChildRadars()
2232
+ return self.childRadars
2233
+ end
2234
+
2235
+ function SkynetIADSAbstractRadarElement:clearChildRadars()
2236
+ self.childRadars = {}
2237
+ end
2238
+
2239
+ --TODO: unit test this method
2240
+ function SkynetIADSAbstractRadarElement:getUsableChildRadars()
2241
+ local usableRadars = {}
2242
+ for i = 1, #self.childRadars do
2243
+ local childRadar = self.childRadars[i]
2244
+ if childRadar:hasWorkingPowerSource() and childRadar:hasActiveConnectionNode() then
2245
+ table.insert(usableRadars, childRadar)
2246
+ end
2247
+ end
2248
+ return usableRadars
2249
+ end
2250
+
2251
+ function SkynetIADSAbstractRadarElement:informChildrenOfStateChange()
2252
+ self:setToCorrectAutonomousState()
2253
+ local children = self:getChildRadars()
2254
+ for i = 1, #children do
2255
+ local childRadar = children[i]
2256
+ childRadar:setToCorrectAutonomousState()
2257
+ end
2258
+ self.iads:getMooseConnector():update()
2259
+ end
2260
+
2261
+ function SkynetIADSAbstractRadarElement:setToCorrectAutonomousState()
2262
+ local parents = self:getParentRadars()
2263
+ for i = 1, #parents do
2264
+ local parent = parents[i]
2265
+ --of one parent exists that still is connected to the IADS, the SAM site does not have to go autonomous
2266
+ --instead of isDestroyed() write method, hasWorkingSearchRadars()
2267
+ if self:hasActiveConnectionNode() and self.iads:isCommandCenterUsable() and parent:hasWorkingPowerSource() and parent:hasActiveConnectionNode() and parent:getActAsEW() == true and parent:isDestroyed() == false then
2268
+ self:resetAutonomousState()
2269
+ return
2270
+ end
2271
+ end
2272
+ self:goAutonomous()
2273
+ end
2274
+
2275
+
2276
+ function SkynetIADSAbstractRadarElement:setAutonomousBehaviour(mode)
2277
+ if mode ~= nil then
2278
+ self.autonomousBehaviour = mode
2279
+ end
2280
+ return self
2281
+ end
2282
+
2283
+ function SkynetIADSAbstractRadarElement:getAutonomousBehaviour()
2284
+ return self.autonomousBehaviour
2285
+ end
2286
+
2287
+ function SkynetIADSAbstractRadarElement:resetAutonomousState()
2288
+ self.isAutonomous = false
2289
+ self:goDark()
2290
+ end
2291
+
2292
+ function SkynetIADSAbstractRadarElement:goAutonomous()
2293
+ self.isAutonomous = true
2294
+ if self.autonomousBehaviour == SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DARK then
2295
+ self:goDark()
2296
+ else
2297
+ self:goLive()
2298
+ end
2299
+ end
2300
+
2301
+ function SkynetIADSAbstractRadarElement:getAutonomousState()
2302
+ return self.isAutonomous
2303
+ end
2304
+
2305
+ function SkynetIADSAbstractRadarElement:pointDefencesHaveRemainingAmmo(minNumberOfMissiles)
2306
+ local remainingMissiles = 0
2307
+ for i = 1, #self.pointDefences do
2308
+ local pointDefence = self.pointDefences[i]
2309
+ remainingMissiles = remainingMissiles + pointDefence:getRemainingNumberOfMissiles()
2310
+ end
2311
+ return self:hasRequiredNumberOfMissiles(minNumberOfMissiles, remainingMissiles)
2312
+ end
2313
+
2314
+ function SkynetIADSAbstractRadarElement:hasRequiredNumberOfMissiles(minNumberOfMissiles, remainingMissiles)
2315
+ local returnValue = false
2316
+ if ( remainingMissiles > 0 and remainingMissiles >= minNumberOfMissiles ) then
2317
+ returnValue = true
2318
+ end
2319
+ return returnValue
2320
+ end
2321
+
2322
+ function SkynetIADSAbstractRadarElement:hasRemainingAmmoToEngageMissiles(minNumberOfMissiles)
2323
+ local remainingMissiles = self:getRemainingNumberOfMissiles()
2324
+ return self:hasRequiredNumberOfMissiles(minNumberOfMissiles, remainingMissiles)
2325
+ end
2326
+
2327
+ -- this method needs to be refactored so that it works for ew radars that don't have launchers, or that it is only called by sam sites
2328
+ function SkynetIADSAbstractRadarElement:hasEnoughLaunchersToEngageMissiles(minNumberOfLaunchers)
2329
+ local launchers = self:getLaunchers()
2330
+ if(launchers ~= nil) then
2331
+ launchers = #self:getLaunchers()
2332
+ else
2333
+ launchers = 0
2334
+ end
2335
+ return self:hasRequiredNumberOfMissiles(minNumberOfLaunchers, launchers)
2336
+ end
2337
+
2338
+ function SkynetIADSAbstractRadarElement:pointDefencesHaveEnoughLaunchers(minNumberOfLaunchers)
2339
+ local numOfLaunchers = 0
2340
+ for i = 1, #self.pointDefences do
2341
+ local pointDefence = self.pointDefences[i]
2342
+ numOfLaunchers = numOfLaunchers + #pointDefence:getLaunchers()
2343
+ end
2344
+ return self:hasRequiredNumberOfMissiles(minNumberOfLaunchers, numOfLaunchers)
2345
+ end
2346
+
2347
+ function SkynetIADSAbstractRadarElement:setIgnoreHARMSWhilePointDefencesHaveAmmo(state)
2348
+ self.iads:printOutputToLog("DEPRECATED: setIgnoreHARMSWhilePointDefencesHaveAmmo SAM Site will stay live automaticall as long as itself or it's point defences can defend against a HARM")
2349
+ return self
2350
+ end
2351
+
2352
+ function SkynetIADSAbstractRadarElement:hasMissilesInFlight()
2353
+ return #self.missilesInFlight > 0
2354
+ end
2355
+
2356
+ function SkynetIADSAbstractRadarElement:getNumberOfMissilesInFlight()
2357
+ return #self.missilesInFlight
2358
+ end
2359
+
2360
+ -- DCS does not send an event, when a missile is destroyed, so this method needs to be polled so that the missiles in flight are current, polling is done in the HARM Search call: evaluateIfTargetsContainHARMs
2361
+ function SkynetIADSAbstractRadarElement:updateMissilesInFlight()
2362
+ local missilesInFlight = {}
2363
+ for i = 1, #self.missilesInFlight do
2364
+ local missile = self.missilesInFlight[i]
2365
+ if missile:isExist() then
2366
+ table.insert(missilesInFlight, missile)
2367
+ end
2368
+ end
2369
+ self.missilesInFlight = missilesInFlight
2370
+ self:goDarkIfOutOfAmmo()
2371
+ end
2372
+
2373
+ function SkynetIADSAbstractRadarElement:goDarkIfOutOfAmmo()
2374
+ if self:hasRemainingAmmo() == false and self:getActAsEW() == false then
2375
+ self:goDark()
2376
+ end
2377
+ end
2378
+
2379
+ function SkynetIADSAbstractRadarElement:getActAsEW()
2380
+ return self.actAsEW
2381
+ end
2382
+
2383
+ function SkynetIADSAbstractRadarElement:setActAsEW(ewState)
2384
+ if ewState == true or ewState == false then
2385
+ local stateChange = false
2386
+ if ewState ~= self.actAsEW then
2387
+ stateChange = true
2388
+ end
2389
+ self.actAsEW = ewState
2390
+ if stateChange then
2391
+ self:informChildrenOfStateChange()
2392
+ end
2393
+ end
2394
+ if self.actAsEW == true then
2395
+ self:goLive()
2396
+ else
2397
+ self:goDark()
2398
+ end
2399
+ return self
2400
+ end
2401
+
2402
+ function SkynetIADSAbstractRadarElement:getUnitsToAnalyse()
2403
+ local units = {}
2404
+ table.insert(units, self:getDCSRepresentation())
2405
+ if getmetatable(self:getDCSRepresentation()) == Group then
2406
+ units = self:getDCSRepresentation():getUnits()
2407
+ end
2408
+ return units
2409
+ end
2410
+
2411
+ function SkynetIADSAbstractRadarElement:getRemainingNumberOfMissiles()
2412
+ local remainingNumberOfMissiles = 0
2413
+ for i = 1, #self.launchers do
2414
+ local launcher = self.launchers[i]
2415
+ remainingNumberOfMissiles = remainingNumberOfMissiles + launcher:getRemainingNumberOfMissiles()
2416
+ end
2417
+ return remainingNumberOfMissiles
2418
+ end
2419
+
2420
+ function SkynetIADSAbstractRadarElement:getInitialNumberOfMissiles()
2421
+ local initalNumberOfMissiles = 0
2422
+ for i = 1, #self.launchers do
2423
+ local launcher = self.launchers[i]
2424
+ initalNumberOfMissiles = launcher:getInitialNumberOfMissiles() + initalNumberOfMissiles
2425
+ end
2426
+ return initalNumberOfMissiles
2427
+ end
2428
+
2429
+ function SkynetIADSAbstractRadarElement:getRemainingNumberOfShells()
2430
+ local remainingNumberOfShells = 0
2431
+ for i = 1, #self.launchers do
2432
+ local launcher = self.launchers[i]
2433
+ remainingNumberOfShells = remainingNumberOfShells + launcher:getRemainingNumberOfShells()
2434
+ end
2435
+ return remainingNumberOfShells
2436
+ end
2437
+
2438
+ function SkynetIADSAbstractRadarElement:getInitialNumberOfShells()
2439
+ local initialNumberOfShells = 0
2440
+ for i = 1, #self.launchers do
2441
+ local launcher = self.launchers[i]
2442
+ initialNumberOfShells = initialNumberOfShells + launcher:getInitialNumberOfShells()
2443
+ end
2444
+ return initialNumberOfShells
2445
+ end
2446
+
2447
+ function SkynetIADSAbstractRadarElement:hasRemainingAmmo()
2448
+ --the launcher check is due to ew radars they have no launcher and no ammo and therefore are never out of ammo
2449
+ return ( #self.launchers == 0 ) or ((self:getRemainingNumberOfMissiles() > 0 ) or ( self:getRemainingNumberOfShells() > 0 ) )
2450
+ end
2451
+
2452
+ function SkynetIADSAbstractRadarElement:getHARMDetectionChance()
2453
+ return self.harmDetectionChance
2454
+ end
2455
+
2456
+ function SkynetIADSAbstractRadarElement:setHARMDetectionChance(chance)
2457
+ if chance and chance >= 0 and chance <= 100 then
2458
+ self.harmDetectionChance = chance
2459
+ end
2460
+ return self
2461
+ end
2462
+
2463
+ function SkynetIADSAbstractRadarElement:setupElements()
2464
+ local numUnits = #self:getUnitsToAnalyse()
2465
+ for typeName, dataType in pairs(SkynetIADS.database) do
2466
+ local hasSearchRadar = false
2467
+ local hasTrackingRadar = false
2468
+ local hasLauncher = false
2469
+ self.searchRadars = {}
2470
+ self.trackingRadars = {}
2471
+ self.launchers = {}
2472
+ for entry, unitData in pairs(dataType) do
2473
+ if entry == 'searchRadar' then
2474
+ self:analyseAndAddUnit(SkynetIADSSAMSearchRadar, self.searchRadars, unitData)
2475
+ hasSearchRadar = true
2476
+ end
2477
+ if entry == 'launchers' then
2478
+ self:analyseAndAddUnit(SkynetIADSSAMLauncher, self.launchers, unitData)
2479
+ hasLauncher = true
2480
+ end
2481
+ if entry == 'trackingRadar' then
2482
+ self:analyseAndAddUnit(SkynetIADSSAMTrackingRadar, self.trackingRadars, unitData)
2483
+ hasTrackingRadar = true
2484
+ end
2485
+ end
2486
+
2487
+ --this check ensures a unit or group has all required elements for the specific sam or ew type:
2488
+ if (hasLauncher and hasSearchRadar and hasTrackingRadar and #self.launchers > 0 and #self.searchRadars > 0 and #self.trackingRadars > 0 )
2489
+ or (hasSearchRadar and hasLauncher and #self.searchRadars > 0 and #self.launchers > 0) then
2490
+ self:setHARMDetectionChance(dataType['harm_detection_chance'])
2491
+ self.dataBaseSupportedTypesCanEngageHARM = dataType['can_engage_harm']
2492
+ self:setCanEngageHARM(self.dataBaseSupportedTypesCanEngageHARM)
2493
+ local natoName = dataType['name']['NATO']
2494
+ self:buildNatoName(natoName)
2495
+ break
2496
+ end
2497
+ end
2498
+ end
2499
+
2500
+ function SkynetIADSAbstractRadarElement:setCanEngageHARM(canEngage)
2501
+ if canEngage == true or canEngage == false then
2502
+ self.canEngageHARM = canEngage
2503
+ if ( canEngage == true and self:getCanEngageAirWeapons() == false ) then
2504
+ self:setCanEngageAirWeapons(true)
2505
+ end
2506
+ end
2507
+ return self
2508
+ end
2509
+
2510
+ function SkynetIADSAbstractRadarElement:getCanEngageHARM()
2511
+ return self.canEngageHARM
2512
+ end
2513
+
2514
+ function SkynetIADSAbstractRadarElement:setCanEngageAirWeapons(engageAirWeapons)
2515
+ if self:isDestroyed() == false then
2516
+ local controller = self:getDCSRepresentation():getController()
2517
+ if ( engageAirWeapons == true ) then
2518
+ controller:setOption(AI.Option.Ground.id.ENGAGE_AIR_WEAPONS, true)
2519
+ --its important that we set var to true here, to prevent recursion in setCanEngageHARM
2520
+ self.engageAirWeapons = true
2521
+ --we set the original value we got when loading info about the SAM site
2522
+ self:setCanEngageHARM(self.dataBaseSupportedTypesCanEngageHARM)
2523
+ else
2524
+ controller:setOption(AI.Option.Ground.id.ENGAGE_AIR_WEAPONS, false)
2525
+ self:setCanEngageHARM(false)
2526
+ self.engageAirWeapons = false
2527
+ end
2528
+ end
2529
+ return self
2530
+ end
2531
+
2532
+ function SkynetIADSAbstractRadarElement:getCanEngageAirWeapons()
2533
+ return self.engageAirWeapons
2534
+ end
2535
+
2536
+ function SkynetIADSAbstractRadarElement:buildNatoName(natoName)
2537
+ --we shorten the SA-XX names and don't return their code names eg goa, gainful..
2538
+ local pos = natoName:find(" ")
2539
+ local prefix = natoName:sub(1, 2)
2540
+ if string.lower(prefix) == 'sa' and pos ~= nil then
2541
+ self.natoName = natoName:sub(1, (pos-1))
2542
+ else
2543
+ self.natoName = natoName
2544
+ end
2545
+ end
2546
+
2547
+ function SkynetIADSAbstractRadarElement:analyseAndAddUnit(class, tableToAdd, unitData)
2548
+ local units = self:getUnitsToAnalyse()
2549
+ for i = 1, #units do
2550
+ local unit = units[i]
2551
+ self:buildSingleUnit(unit, class, tableToAdd, unitData)
2552
+ end
2553
+ end
2554
+
2555
+ function SkynetIADSAbstractRadarElement:buildSingleUnit(unit, class, tableToAdd, unitData)
2556
+ local unitTypeName = unit:getTypeName()
2557
+ for unitName, unitPerformanceData in pairs(unitData) do
2558
+ if unitName == unitTypeName then
2559
+ samElement = class:create(unit)
2560
+ samElement:setupRangeData()
2561
+ table.insert(tableToAdd, samElement)
2562
+ end
2563
+ end
2564
+ end
2565
+
2566
+ function SkynetIADSAbstractRadarElement:getController()
2567
+ local dcsRepresentation = self:getDCSRepresentation()
2568
+ if dcsRepresentation:isExist() then
2569
+ return dcsRepresentation:getController()
2570
+ else
2571
+ return nil
2572
+ end
2573
+ end
2574
+
2575
+ function SkynetIADSAbstractRadarElement:getLaunchers()
2576
+ return self.launchers
2577
+ end
2578
+
2579
+ function SkynetIADSAbstractRadarElement:getSearchRadars()
2580
+ return self.searchRadars
2581
+ end
2582
+
2583
+ function SkynetIADSAbstractRadarElement:getTrackingRadars()
2584
+ return self.trackingRadars
2585
+ end
2586
+
2587
+ function SkynetIADSAbstractRadarElement:getRadars()
2588
+ local radarUnits = {}
2589
+ for i = 1, #self.searchRadars do
2590
+ table.insert(radarUnits, self.searchRadars[i])
2591
+ end
2592
+ for i = 1, #self.trackingRadars do
2593
+ table.insert(radarUnits, self.trackingRadars[i])
2594
+ end
2595
+ return radarUnits
2596
+ end
2597
+
2598
+ function SkynetIADSAbstractRadarElement:setGoLiveRangeInPercent(percent)
2599
+ if percent ~= nil then
2600
+ self.firingRangePercent = percent
2601
+ for i = 1, #self.launchers do
2602
+ local launcher = self.launchers[i]
2603
+ launcher:setFiringRangePercent(self.firingRangePercent)
2604
+ end
2605
+ for i = 1, #self.searchRadars do
2606
+ local radar = self.searchRadars[i]
2607
+ radar:setFiringRangePercent(self.firingRangePercent)
2608
+ end
2609
+ end
2610
+ return self
2611
+ end
2612
+
2613
+ function SkynetIADSAbstractRadarElement:getGoLiveRangeInPercent()
2614
+ return self.firingRangePercent
2615
+ end
2616
+
2617
+ function SkynetIADSAbstractRadarElement:setEngagementZone(engagementZone)
2618
+ if engagementZone == SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_KILL_ZONE then
2619
+ self.goLiveRange = engagementZone
2620
+ elseif engagementZone == SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_SEARCH_RANGE then
2621
+ self.goLiveRange = engagementZone
2622
+ end
2623
+ return self
2624
+ end
2625
+
2626
+ function SkynetIADSAbstractRadarElement:getEngagementZone()
2627
+ return self.goLiveRange
2628
+ end
2629
+
2630
+ function SkynetIADSAbstractRadarElement:goLive()
2631
+ if ( self.aiState == false and self:hasWorkingPowerSource() and self.harmSilenceID == nil)
2632
+ and (self:hasRemainingAmmo() == true )
2633
+ then
2634
+ if self:isDestroyed() == false then
2635
+ local cont = self:getController()
2636
+ cont:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.RED)
2637
+ cont:setOption(AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE)
2638
+ self:getDCSRepresentation():enableEmission(true)
2639
+ self.goLiveTime = timer.getTime()
2640
+ self.aiState = true
2641
+ end
2642
+ self:pointDefencesStopActingAsEW()
2643
+ if self.iads:getDebugSettings().radarWentLive then
2644
+ self.iads:printOutputToLog("GOING LIVE: "..self:getDescription())
2645
+ end
2646
+ self:scanForHarms()
2647
+ end
2648
+ end
2649
+
2650
+ function SkynetIADSAbstractRadarElement:pointDefencesStopActingAsEW()
2651
+ for i = 1, #self.pointDefences do
2652
+ local pointDefence = self.pointDefences[i]
2653
+ pointDefence:setActAsEW(false)
2654
+ end
2655
+ end
2656
+
2657
+
2658
+ function SkynetIADSAbstractRadarElement:goDark()
2659
+ if (self:hasWorkingPowerSource() == false) or ( self.aiState == true )
2660
+ and (self.harmSilenceID ~= nil or ( self.harmSilenceID == nil and #self:getDetectedTargets() == 0 and self:hasMissilesInFlight() == false) or ( self.harmSilenceID == nil and #self:getDetectedTargets() > 0 and self:hasMissilesInFlight() == false and self:hasRemainingAmmo() == false ) )
2661
+ then
2662
+ if self:isDestroyed() == false then
2663
+ self:getDCSRepresentation():enableEmission(false)
2664
+ end
2665
+ -- point defence will only go live if the Radar Emitting site it is protecting goes dark and this is due to a it defending against a HARM
2666
+ if (self.harmSilenceID ~= nil) then
2667
+ self:pointDefencesGoLive()
2668
+ end
2669
+ self.aiState = false
2670
+ self:stopScanningForHARMs()
2671
+ if self.iads:getDebugSettings().radarWentDark then
2672
+ self.iads:printOutputToLog("GOING DARK: "..self:getDescription())
2673
+ end
2674
+ end
2675
+ end
2676
+
2677
+ function SkynetIADSAbstractRadarElement:pointDefencesGoLive()
2678
+ local setActive = false
2679
+ for i = 1, #self.pointDefences do
2680
+ local pointDefence = self.pointDefences[i]
2681
+ if ( pointDefence:getActAsEW() == false ) then
2682
+ setActive = true
2683
+ pointDefence:setActAsEW(true)
2684
+ end
2685
+ end
2686
+ return setActive
2687
+ end
2688
+
2689
+ function SkynetIADSAbstractRadarElement:isActive()
2690
+ return self.aiState
2691
+ end
2692
+
2693
+ function SkynetIADSAbstractRadarElement:isTargetInRange(target)
2694
+
2695
+ local isSearchRadarInRange = false
2696
+ local isTrackingRadarInRange = false
2697
+ local isLauncherInRange = false
2698
+
2699
+ local isSearchRadarInRange = ( #self.searchRadars == 0 )
2700
+ for i = 1, #self.searchRadars do
2701
+ local searchRadar = self.searchRadars[i]
2702
+ if searchRadar:isInRange(target) then
2703
+ isSearchRadarInRange = true
2704
+ break
2705
+ end
2706
+ end
2707
+
2708
+ if self.goLiveRange == SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_KILL_ZONE then
2709
+
2710
+ isLauncherInRange = ( #self.launchers == 0 )
2711
+ for i = 1, #self.launchers do
2712
+ local launcher = self.launchers[i]
2713
+ if launcher:isInRange(target) then
2714
+ isLauncherInRange = true
2715
+ break
2716
+ end
2717
+ end
2718
+
2719
+ isTrackingRadarInRange = ( #self.trackingRadars == 0 )
2720
+ for i = 1, #self.trackingRadars do
2721
+ local trackingRadar = self.trackingRadars[i]
2722
+ if trackingRadar:isInRange(target) then
2723
+ isTrackingRadarInRange = true
2724
+ break
2725
+ end
2726
+ end
2727
+ else
2728
+ isLauncherInRange = true
2729
+ isTrackingRadarInRange = true
2730
+ end
2731
+ return (isSearchRadarInRange and isTrackingRadarInRange and isLauncherInRange )
2732
+ end
2733
+
2734
+ function SkynetIADSAbstractRadarElement:isInRadarDetectionRangeOf(abstractRadarElement)
2735
+ local radars = self:getRadars()
2736
+ local abstractRadarElementRadars = abstractRadarElement:getRadars()
2737
+ for i = 1, #radars do
2738
+ local radar = radars[i]
2739
+ for j = 1, #abstractRadarElementRadars do
2740
+ local abstractRadarElementRadar = abstractRadarElementRadars[j]
2741
+ if abstractRadarElementRadar:isExist() and radar:isExist() then
2742
+ local distance = self:getDistanceToUnit(radar:getDCSRepresentation():getPosition().p, abstractRadarElementRadar:getDCSRepresentation():getPosition().p)
2743
+ if abstractRadarElementRadar:getMaxRangeFindingTarget() >= distance then
2744
+ return true
2745
+ end
2746
+ end
2747
+ end
2748
+ end
2749
+ return false
2750
+ end
2751
+
2752
+ function SkynetIADSAbstractRadarElement:getDistanceToUnit(unitPosA, unitPosB)
2753
+ return mist.utils.round(mist.utils.get2DDist(unitPosA, unitPosB, 0))
2754
+ end
2755
+
2756
+ function SkynetIADSAbstractRadarElement:hasWorkingRadar()
2757
+ local radars = self:getRadars()
2758
+ for i = 1, #radars do
2759
+ local radar = radars[i]
2760
+ if radar:isRadarWorking() == true then
2761
+ return true
2762
+ end
2763
+ end
2764
+ return false
2765
+ end
2766
+
2767
+ function SkynetIADSAbstractRadarElement:jam(successProbability)
2768
+ if self:isDestroyed() == false then
2769
+ local controller = self:getController()
2770
+ local probability = math.random(1, 100)
2771
+ if self.iads:getDebugSettings().jammerProbability then
2772
+ self.iads:printOutputToLog("JAMMER: "..self:getDescription()..": Probability: "..successProbability)
2773
+ end
2774
+ if successProbability > probability then
2775
+ controller:setOption(AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD)
2776
+ if self.iads:getDebugSettings().jammerProbability then
2777
+ self.iads:printOutputToLog("JAMMER: "..self:getDescription()..": jammed, setting to weapon hold")
2778
+ end
2779
+ else
2780
+ controller:setOption(AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE)
2781
+ if self.iads:getDebugSettings().jammerProbability then
2782
+ self.iads:printOutputToLog("JAMMER: "..self:getDescription()..": jammed, setting to weapon free")
2783
+ end
2784
+ end
2785
+ self.lastJammerUpdate = timer:getTime()
2786
+ end
2787
+ end
2788
+
2789
+ function SkynetIADSAbstractRadarElement:scanForHarms()
2790
+ self:stopScanningForHARMs()
2791
+ self.harmScanID = mist.scheduleFunction(SkynetIADSAbstractRadarElement.evaluateIfTargetsContainHARMs, {self}, 1, 2)
2792
+ end
2793
+
2794
+ function SkynetIADSAbstractRadarElement:isScanningForHARMs()
2795
+ return self.harmScanID ~= nil
2796
+ end
2797
+
2798
+ function SkynetIADSAbstractRadarElement:isDefendingHARM()
2799
+ return self.harmSilenceID ~= nil
2800
+ end
2801
+
2802
+ function SkynetIADSAbstractRadarElement:stopScanningForHARMs()
2803
+ mist.removeFunction(self.harmScanID)
2804
+ self.harmScanID = nil
2805
+ end
2806
+
2807
+ function SkynetIADSAbstractRadarElement:goSilentToEvadeHARM(timeToImpact)
2808
+ self:finishHarmDefence(self)
2809
+ if ( timeToImpact == nil ) then
2810
+ timeToImpact = 0
2811
+ end
2812
+
2813
+ self.minHarmShutdownTime = self:calculateMinimalShutdownTimeInSeconds(timeToImpact)
2814
+ self.maxHarmShutDownTime = self:calculateMaximalShutdownTimeInSeconds(self.minHarmShutdownTime)
2815
+
2816
+ self.harmShutdownTime = self:calculateHARMShutdownTime()
2817
+ if self.iads:getDebugSettings().harmDefence then
2818
+ self.iads:printOutputToLog("HARM DEFENCE SHUTTING DOWN: "..self:getDCSName().." | FOR: "..self.harmShutdownTime.." seconds | TTI: "..timeToImpact)
2819
+ end
2820
+ self.harmSilenceID = mist.scheduleFunction(SkynetIADSAbstractRadarElement.finishHarmDefence, {self}, timer.getTime() + self.harmShutdownTime, 1)
2821
+ self:goDark()
2822
+ end
2823
+
2824
+ function SkynetIADSAbstractRadarElement:getHARMShutdownTime()
2825
+ return self.harmShutdownTime
2826
+ end
2827
+
2828
+ function SkynetIADSAbstractRadarElement:calculateHARMShutdownTime()
2829
+ local shutDownTime = math.random(self.minHarmShutdownTime, self.maxHarmShutDownTime)
2830
+ return shutDownTime
2831
+ end
2832
+
2833
+ function SkynetIADSAbstractRadarElement.finishHarmDefence(self)
2834
+ mist.removeFunction(self.harmSilenceID)
2835
+ self.harmSilenceID = nil
2836
+ self.harmShutdownTime = 0
2837
+
2838
+ if ( self:getAutonomousState() == true ) then
2839
+ self:goAutonomous()
2840
+ end
2841
+ end
2842
+
2843
+ function SkynetIADSAbstractRadarElement:getDetectedTargets()
2844
+ if ( timer.getTime() - self.cachedTargetsCurrentAge > self.cachedTargetsMaxAge ) or ( timer.getTime() - self.goLiveTime < self.noCacheActiveForSecondsAfterGoLive ) then
2845
+ self.cachedTargets = {}
2846
+ self.cachedTargetsCurrentAge = timer.getTime()
2847
+ if self:hasWorkingPowerSource() and self:isDestroyed() == false then
2848
+ local targets = self:getController():getDetectedTargets(Controller.Detection.RADAR)
2849
+ for i = 1, #targets do
2850
+ local target = targets[i]
2851
+ -- there are cases when a destroyed object is still visible as a target to the radar, don't add it, will cause errors everywhere the dcs object is accessed
2852
+ if target.object then
2853
+ local iadsTarget = SkynetIADSContact:create(target, self)
2854
+ iadsTarget:refresh()
2855
+ if self:isTargetInRange(iadsTarget) then
2856
+ table.insert(self.cachedTargets, iadsTarget)
2857
+ end
2858
+ end
2859
+ end
2860
+ end
2861
+ end
2862
+ return self.cachedTargets
2863
+ end
2864
+
2865
+ function SkynetIADSAbstractRadarElement:getSecondsToImpact(distanceNM, speedKT)
2866
+ local tti = 0
2867
+ if speedKT > 0 then
2868
+ tti = mist.utils.round((distanceNM / speedKT) * 3600, 0)
2869
+ if tti < 0 then
2870
+ tti = 0
2871
+ end
2872
+ end
2873
+ return tti
2874
+ end
2875
+
2876
+ function SkynetIADSAbstractRadarElement:getDistanceInMetersToContact(radarUnit, point)
2877
+ return mist.utils.round(mist.utils.get3DDist(radarUnit:getPosition().p, point), 0)
2878
+ end
2879
+
2880
+ function SkynetIADSAbstractRadarElement:calculateMinimalShutdownTimeInSeconds(timeToImpact)
2881
+ return timeToImpact + self.minHarmPresetShutdownTime
2882
+ end
2883
+
2884
+ function SkynetIADSAbstractRadarElement:calculateMaximalShutdownTimeInSeconds(minShutdownTime)
2885
+ return minShutdownTime + mist.random(1, self.maxHarmPresetShutdownTime)
2886
+ end
2887
+
2888
+ function SkynetIADSAbstractRadarElement:calculateImpactPoint(target, distanceInMeters)
2889
+ -- distance needs to be incremented by a certain value for ip calculation to work, check why presumably due to rounding errors in the previous distance calculation
2890
+ return land.getIP(target:getPosition().p, target:getPosition().x, distanceInMeters + 50)
2891
+ end
2892
+
2893
+ function SkynetIADSAbstractRadarElement:shallReactToHARM()
2894
+ return self.harmDetectionChance >= math.random(1, 100)
2895
+ end
2896
+
2897
+ -- will only check for missiles, if DCS ads AAA than can engage HARMs then this code must be updated:
2898
+ function SkynetIADSAbstractRadarElement:shallIgnoreHARMShutdown()
2899
+ local numOfHarms = self:getNumberOfObjectsItentifiedAsHARMS()
2900
+ --[[
2901
+ self.iads:printOutputToLog("Self enough launchers: "..tostring(self:hasEnoughLaunchersToEngageMissiles(numOfHarms)))
2902
+ self.iads:printOutputToLog("Self enough missiles: "..tostring(self:hasRemainingAmmoToEngageMissiles(numOfHarms)))
2903
+ self.iads:printOutputToLog("PD enough missiles: "..tostring(self:pointDefencesHaveRemainingAmmo(numOfHarms)))
2904
+ self.iads:printOutputToLog("PD enough launchers: "..tostring(self:pointDefencesHaveEnoughLaunchers(numOfHarms)))
2905
+ --]]
2906
+ return ( ((self:hasEnoughLaunchersToEngageMissiles(numOfHarms) and self:hasRemainingAmmoToEngageMissiles(numOfHarms) and self:getCanEngageHARM()) or (self:pointDefencesHaveRemainingAmmo(numOfHarms) and self:pointDefencesHaveEnoughLaunchers(numOfHarms))))
2907
+ end
2908
+
2909
+ function SkynetIADSAbstractRadarElement:informOfHARM(harmContact)
2910
+ local radars = self:getRadars()
2911
+ for j = 1, #radars do
2912
+ local radar = radars[j]
2913
+ local distanceNM = mist.utils.metersToNM(self:getDistanceInMetersToContact(radar, harmContact:getPosition().p))
2914
+ local harmToSAMHeading = mist.utils.toDegree(mist.utils.getHeadingPoints(harmContact:getPosition().p, radar:getPosition().p))
2915
+ local harmToSAMAspect = self:calculateAspectInDegrees(harmContact:getMagneticHeading(), harmToSAMHeading)
2916
+ local speedKT = harmContact:getGroundSpeedInKnots(0)
2917
+ local secondsToImpact = self:getSecondsToImpact(distanceNM, speedKT)
2918
+ --TODO: use tti instead of distanceNM?
2919
+ -- when iterating through the radars, store shortest tti and work with that value??
2920
+ if ( harmToSAMAspect < SkynetIADSAbstractRadarElement.HARM_TO_SAM_ASPECT and distanceNM < SkynetIADSAbstractRadarElement.HARM_LOOKAHEAD_NM ) then
2921
+ self:addObjectIdentifiedAsHARM(harmContact)
2922
+ if ( #self:getPointDefences() > 0 and self:pointDefencesGoLive() == true and self.iads:getDebugSettings().harmDefence ) then
2923
+ self.iads:printOutputToLog("POINT DEFENCES GOING LIVE FOR: "..self:getDCSName().." | TTI: "..secondsToImpact)
2924
+ end
2925
+ --self.iads:printOutputToLog("Ignore HARM shutdown: "..tostring(self:shallIgnoreHARMShutdown()))
2926
+ if ( self:getIsAPointDefence() == false and ( self:isDefendingHARM() == false or ( self:getHARMShutdownTime() < secondsToImpact ) ) and self:shallIgnoreHARMShutdown() == false) then
2927
+ self:goSilentToEvadeHARM(secondsToImpact)
2928
+ break
2929
+ end
2930
+ end
2931
+ end
2932
+ end
2933
+
2934
+ function SkynetIADSAbstractElement:addObjectIdentifiedAsHARM(harmContact)
2935
+ self:insertToTableIfNotAlreadyAdded(self.objectsIdentifiedAsHarms, harmContact)
2936
+ end
2937
+
2938
+ function SkynetIADSAbstractRadarElement:calculateAspectInDegrees(harmHeading, harmToSAMHeading)
2939
+ local aspect = harmHeading - harmToSAMHeading
2940
+ if ( aspect < 0 ) then
2941
+ aspect = -1 * aspect
2942
+ end
2943
+ if aspect > 180 then
2944
+ aspect = 360 - aspect
2945
+ end
2946
+ return mist.utils.round(aspect)
2947
+ end
2948
+
2949
+ function SkynetIADSAbstractRadarElement:getNumberOfObjectsItentifiedAsHARMS()
2950
+ return #self.objectsIdentifiedAsHarms
2951
+ end
2952
+
2953
+ function SkynetIADSAbstractRadarElement:cleanUpOldObjectsIdentifiedAsHARMS()
2954
+ local newHARMS = {}
2955
+ for i = 1, #self.objectsIdentifiedAsHarms do
2956
+ local harmContact = self.objectsIdentifiedAsHarms[i]
2957
+ if harmContact:getAge() < self.objectsIdentifiedAsHarmsMaxTargetAge then
2958
+ table.insert(newHARMS, harmContact)
2959
+ end
2960
+ end
2961
+ --stop point defences acting as ew (always on), will occur if activated via evaluateIfTargetsContainHARMs()
2962
+ --if in this iteration all harms where cleared we turn of the point defence. But in any other cases we dont turn of point defences, that interferes with other parts of the iads
2963
+ -- when setting up the iads (letting pds go to read state)
2964
+ if (#newHARMS == 0 and self:getNumberOfObjectsItentifiedAsHARMS() > 0 ) then
2965
+ self:pointDefencesStopActingAsEW()
2966
+ end
2967
+ self.objectsIdentifiedAsHarms = newHARMS
2968
+ end
2969
+
2970
+
2971
+ function SkynetIADSAbstractRadarElement.evaluateIfTargetsContainHARMs(self)
2972
+
2973
+ --if an emitter dies the SAM site being jammed will revert back to normal operation:
2974
+ if self.lastJammerUpdate > 0 and ( timer:getTime() - self.lastJammerUpdate ) > 10 then
2975
+ self:jam(0)
2976
+ self.lastJammerUpdate = 0
2977
+ end
2978
+
2979
+ --we use the regular interval of this method to update to other states:
2980
+ self:updateMissilesInFlight()
2981
+ self:cleanUpOldObjectsIdentifiedAsHARMS()
2982
+ end
2983
+
2984
+ end
2985
+ do
2986
+ --this class is currently used for AWACS and Ships, at a latter date a separate class for ships could be created, currently not needed
2987
+ SkynetIADSAWACSRadar = {}
2988
+ SkynetIADSAWACSRadar = inheritsFrom(SkynetIADSAbstractRadarElement)
2989
+
2990
+ function SkynetIADSAWACSRadar:create(radarUnit, iads)
2991
+ local instance = self:superClass():create(radarUnit, iads)
2992
+ setmetatable(instance, self)
2993
+ self.__index = self
2994
+ instance.lastUpdatePosition = nil
2995
+ instance.natoName = radarUnit:getTypeName()
2996
+ return instance
2997
+ end
2998
+
2999
+ function SkynetIADSAWACSRadar:setupElements()
3000
+ local unit = self:getDCSRepresentation()
3001
+ local radar = SkynetIADSSAMSearchRadar:create(unit)
3002
+ radar:setupRangeData()
3003
+ table.insert(self.searchRadars, radar)
3004
+ end
3005
+
3006
+
3007
+ -- AWACs will not scan for HARMS
3008
+ function SkynetIADSAWACSRadar:scanForHarms()
3009
+
3010
+ end
3011
+
3012
+ function SkynetIADSAWACSRadar:getMaxAllowedMovementForAutonomousUpdateInNM()
3013
+ --local radarRange = mist.utils.metersToNM(self.searchRadars[1]:getMaxRangeFindingTarget())
3014
+ --return mist.utils.round(radarRange / 10)
3015
+ --fixed to 10 nm miles to better fit small SAM sites
3016
+ return 10
3017
+ end
3018
+
3019
+ function SkynetIADSAWACSRadar:isUpdateOfAutonomousStateOfSAMSitesRequired()
3020
+ local isUpdateRequired = self:getDistanceTraveledSinceLastUpdate() > self:getMaxAllowedMovementForAutonomousUpdateInNM()
3021
+ if isUpdateRequired then
3022
+ self.lastUpdatePosition = nil
3023
+ end
3024
+ return isUpdateRequired
3025
+ end
3026
+
3027
+ function SkynetIADSAWACSRadar:getDistanceTraveledSinceLastUpdate()
3028
+ local currentPosition = nil
3029
+ if self.lastUpdatePosition == nil and self:getDCSRepresentation():isExist() then
3030
+ self.lastUpdatePosition = self:getDCSRepresentation():getPosition().p
3031
+ end
3032
+ if self:getDCSRepresentation():isExist() then
3033
+ currentPosition = self:getDCSRepresentation():getPosition().p
3034
+ end
3035
+ return mist.utils.round(mist.utils.metersToNM(self:getDistanceToUnit(self.lastUpdatePosition, currentPosition)))
3036
+ end
3037
+
3038
+ end
3039
+
3040
+ do
3041
+ SkynetIADSCommandCenter = {}
3042
+ SkynetIADSCommandCenter = inheritsFrom(SkynetIADSAbstractRadarElement)
3043
+
3044
+ function SkynetIADSCommandCenter:create(commandCenter, iads)
3045
+ local instance = self:superClass():create(commandCenter, iads)
3046
+ setmetatable(instance, self)
3047
+ self.__index = self
3048
+ instance.natoName = "COMMAND CENTER"
3049
+ return instance
3050
+ end
3051
+
3052
+ function SkynetIADSCommandCenter:goDark()
3053
+
3054
+ end
3055
+
3056
+ function SkynetIADSCommandCenter:goLive()
3057
+
3058
+ end
3059
+
3060
+ end
3061
+ do
3062
+
3063
+ SkynetIADSContact = {}
3064
+ SkynetIADSContact = inheritsFrom(SkynetIADSAbstractDCSObjectWrapper)
3065
+
3066
+ SkynetIADSContact.CLIMB = "CLIMB"
3067
+ SkynetIADSContact.DESCEND = "DESCEND"
3068
+
3069
+ SkynetIADSContact.HARM = "HARM"
3070
+ SkynetIADSContact.NOT_HARM = "NOT_HARM"
3071
+ SkynetIADSContact.HARM_UNKNOWN = "HARM_UNKNOWN"
3072
+
3073
+ function SkynetIADSContact:create(dcsRadarTarget, abstractRadarElementDetected)
3074
+ local instance = self:superClass():create(dcsRadarTarget.object)
3075
+ setmetatable(instance, self)
3076
+ self.__index = self
3077
+ instance.abstractRadarElementsDetected = {}
3078
+ table.insert(instance.abstractRadarElementsDetected, abstractRadarElementDetected)
3079
+ instance.firstContactTime = timer.getAbsTime()
3080
+ instance.lastTimeSeen = 0
3081
+ instance.dcsRadarTarget = dcsRadarTarget
3082
+ instance.position = instance:getDCSRepresentation():getPosition()
3083
+ instance.numOfTimesRefreshed = 0
3084
+ instance.speed = 0
3085
+ instance.harmState = SkynetIADSContact.HARM_UNKNOWN
3086
+ instance.simpleAltitudeProfile = {}
3087
+ return instance
3088
+ end
3089
+
3090
+ function SkynetIADSContact:setHARMState(state)
3091
+ self.harmState = state
3092
+ end
3093
+
3094
+ function SkynetIADSContact:getHARMState()
3095
+ return self.harmState
3096
+ end
3097
+
3098
+ function SkynetIADSContact:isIdentifiedAsHARM()
3099
+ return self.harmState == SkynetIADSContact.HARM
3100
+ end
3101
+
3102
+ function SkynetIADSContact:isHARMStateUnknown()
3103
+ return self.harmState == SkynetIADSContact.HARM_UNKNOWN
3104
+ end
3105
+
3106
+ function SkynetIADSContact:getMagneticHeading()
3107
+ if ( self:isExist() ) then
3108
+ return mist.utils.round(mist.utils.toDegree(mist.getHeading(self:getDCSRepresentation())))
3109
+ else
3110
+ return -1
3111
+ end
3112
+ end
3113
+
3114
+ function SkynetIADSContact:getAbstractRadarElementsDetected()
3115
+ return self.abstractRadarElementsDetected
3116
+ end
3117
+
3118
+ function SkynetIADSContact:addAbstractRadarElementDetected(radar)
3119
+ self:insertToTableIfNotAlreadyAdded(self.abstractRadarElementsDetected, radar)
3120
+ end
3121
+
3122
+ function SkynetIADSContact:isTypeKnown()
3123
+ return self.dcsRadarTarget.type
3124
+ end
3125
+
3126
+ function SkynetIADSContact:isDistanceKnown()
3127
+ return self.dcsRadarTarget.distance
3128
+ end
3129
+
3130
+ function SkynetIADSContact:getTypeName()
3131
+ if self:isIdentifiedAsHARM() then
3132
+ return SkynetIADSContact.HARM
3133
+ end
3134
+ local category = self:getDCSRepresentation():getCategory()
3135
+ if category == Object.Category.UNIT then
3136
+ return self.typeName
3137
+ end
3138
+ return "UNKNOWN"
3139
+ end
3140
+
3141
+ function SkynetIADSContact:getPosition()
3142
+ return self.position
3143
+ end
3144
+
3145
+ function SkynetIADSContact:getGroundSpeedInKnots(decimals)
3146
+ if decimals == nil then
3147
+ decimals = 2
3148
+ end
3149
+ return mist.utils.round(self.speed, decimals)
3150
+ end
3151
+
3152
+ function SkynetIADSContact:getHeightInFeetMSL()
3153
+ if self:isExist() then
3154
+ return mist.utils.round(mist.utils.metersToFeet(self:getDCSRepresentation():getPosition().p.y), 0)
3155
+ else
3156
+ return 0
3157
+ end
3158
+ end
3159
+
3160
+ function SkynetIADSContact:getDesc()
3161
+ if self:isExist() then
3162
+ return self:getDCSRepresentation():getDesc()
3163
+ else
3164
+ return {}
3165
+ end
3166
+ end
3167
+
3168
+ function SkynetIADSContact:getNumberOfTimesHitByRadar()
3169
+ return self.numOfTimesRefreshed
3170
+ end
3171
+
3172
+ function SkynetIADSContact:refresh()
3173
+ if self:isExist() then
3174
+ local timeDelta = (timer.getAbsTime() - self.lastTimeSeen)
3175
+ if timeDelta > 0 then
3176
+ self.numOfTimesRefreshed = self.numOfTimesRefreshed + 1
3177
+ local distance = mist.utils.metersToNM(mist.utils.get2DDist(self.position.p, self:getDCSRepresentation():getPosition().p))
3178
+ local hours = timeDelta / 3600
3179
+ self.speed = (distance / hours)
3180
+ self:updateSimpleAltitudeProfile()
3181
+ self.position = self:getDCSRepresentation():getPosition()
3182
+ end
3183
+ end
3184
+ self.lastTimeSeen = timer.getAbsTime()
3185
+ end
3186
+
3187
+ function SkynetIADSContact:updateSimpleAltitudeProfile()
3188
+ local currentAltitude = self:getDCSRepresentation():getPosition().p.y
3189
+
3190
+ local previousPath = ""
3191
+ if #self.simpleAltitudeProfile > 0 then
3192
+ previousPath = self.simpleAltitudeProfile[#self.simpleAltitudeProfile]
3193
+ end
3194
+
3195
+ if self.position.p.y > currentAltitude and previousPath ~= SkynetIADSContact.DESCEND then
3196
+ table.insert(self.simpleAltitudeProfile, SkynetIADSContact.DESCEND)
3197
+ elseif self.position.p.y < currentAltitude and previousPath ~= SkynetIADSContact.CLIMB then
3198
+ table.insert(self.simpleAltitudeProfile, SkynetIADSContact.CLIMB)
3199
+ end
3200
+ end
3201
+
3202
+ function SkynetIADSContact:getSimpleAltitudeProfile()
3203
+ return self.simpleAltitudeProfile
3204
+ end
3205
+
3206
+ function SkynetIADSContact:getAge()
3207
+ return mist.utils.round(timer.getAbsTime() - self.lastTimeSeen)
3208
+ end
3209
+
3210
+ end
3211
+
3212
+ do
3213
+
3214
+ SkynetIADSEWRadar = {}
3215
+ SkynetIADSEWRadar = inheritsFrom(SkynetIADSAbstractRadarElement)
3216
+
3217
+ function SkynetIADSEWRadar:create(radarUnit, iads)
3218
+ local instance = self:superClass():create(radarUnit, iads)
3219
+ setmetatable(instance, self)
3220
+ self.__index = self
3221
+ instance.autonomousBehaviour = SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DARK
3222
+ return instance
3223
+ end
3224
+
3225
+ function SkynetIADSEWRadar:setupElements()
3226
+ local unit = self:getDCSRepresentation()
3227
+ local unitType = unit:getTypeName()
3228
+ for typeName, dataType in pairs(SkynetIADS.database) do
3229
+ for entry, unitData in pairs(dataType) do
3230
+ if entry == 'searchRadar' then
3231
+ --buildSingleUnit checks to make sure the EW radar is defined in the Skynet database. If it is not, self.searchRadars will be 0 so no ew radar will be added
3232
+ self:buildSingleUnit(unit, SkynetIADSSAMSearchRadar, self.searchRadars, unitData)
3233
+ if #self.searchRadars > 0 then
3234
+ local harmDetection = dataType['harm_detection_chance']
3235
+ self:setHARMDetectionChance(harmDetection)
3236
+ if unitData[unitType]['name'] then
3237
+ local natoName = unitData[unitType]['name']['NATO']
3238
+ self:buildNatoName(natoName)
3239
+ end
3240
+ return
3241
+ end
3242
+ end
3243
+ end
3244
+ end
3245
+ end
3246
+
3247
+ --an Early Warning Radar has simplified check to determine if its autonomous or not
3248
+ function SkynetIADSEWRadar:setToCorrectAutonomousState()
3249
+ if self:hasActiveConnectionNode() and self:hasWorkingPowerSource() and self.iads:isCommandCenterUsable() then
3250
+ self:resetAutonomousState()
3251
+ self:goLive()
3252
+ end
3253
+ if self:hasActiveConnectionNode() == false or self.iads:isCommandCenterUsable() == false then
3254
+ self:goAutonomous()
3255
+ end
3256
+ end
3257
+
3258
+ end
3259
+ do
3260
+
3261
+ SkynetIADSJammer = {}
3262
+ SkynetIADSJammer.__index = SkynetIADSJammer
3263
+
3264
+ function SkynetIADSJammer:create(emitter, iads)
3265
+ local jammer = {}
3266
+ setmetatable(jammer, SkynetIADSJammer)
3267
+ jammer.radioMenu = nil
3268
+ jammer.emitter = emitter
3269
+ jammer.jammerTaskID = nil
3270
+ jammer.iads = {iads}
3271
+ jammer.maximumEffectiveDistanceNM = 200
3272
+ --jammer probability settings are stored here, visualisation, see: https://docs.google.com/spreadsheets/d/16rnaU49ZpOczPEsdGJ6nfD0SLPxYLEYKmmo4i2Vfoe0/edit#gid=0
3273
+ jammer.jammerTable = {
3274
+ ['SA-2'] = {
3275
+ ['function'] = function(distanceNauticalMiles) return ( 1.4 ^ distanceNauticalMiles ) + 90 end,
3276
+ ['canjam'] = true,
3277
+ },
3278
+ ['SA-3'] = {
3279
+ ['function'] = function(distanceNauticalMiles) return ( 1.4 ^ distanceNauticalMiles ) + 80 end,
3280
+ ['canjam'] = true,
3281
+ },
3282
+ ['SA-6'] = {
3283
+ ['function'] = function(distanceNauticalMiles) return ( 1.4 ^ distanceNauticalMiles ) + 23 end,
3284
+ ['canjam'] = true,
3285
+ },
3286
+ ['SA-8'] = {
3287
+ ['function'] = function(distanceNauticalMiles) return ( 1.35 ^ distanceNauticalMiles ) + 30 end,
3288
+ ['canjam'] = true,
3289
+ },
3290
+ ['SA-10'] = {
3291
+ ['function'] = function(distanceNauticalMiles) return ( 1.07 ^ (distanceNauticalMiles / 1.13) ) + 5 end,
3292
+ ['canjam'] = true,
3293
+ },
3294
+ ['SA-11'] = {
3295
+ ['function'] = function(distanceNauticalMiles) return ( 1.25 ^ distanceNauticalMiles ) + 15 end,
3296
+ ['canjam'] = true,
3297
+ },
3298
+ ['SA-15'] = {
3299
+ ['function'] = function(distanceNauticalMiles) return ( 1.15 ^ distanceNauticalMiles ) + 5 end,
3300
+ ['canjam'] = true,
3301
+ },
3302
+ }
3303
+ return jammer
3304
+ end
3305
+
3306
+ function SkynetIADSJammer:masterArmOn()
3307
+ self:masterArmSafe()
3308
+ self.jammerTaskID = mist.scheduleFunction(SkynetIADSJammer.runCycle, {self}, 1, 10)
3309
+ end
3310
+
3311
+ function SkynetIADSJammer:addFunction(natoName, jammerFunction)
3312
+ self.jammerTable[natoName] = {
3313
+ ['function'] = jammerFunction,
3314
+ ['canjam'] = true
3315
+ }
3316
+ end
3317
+
3318
+ function SkynetIADSJammer:setMaximumEffectiveDistance(distance)
3319
+ self.maximumEffectiveDistanceNM = distance
3320
+ end
3321
+
3322
+ function SkynetIADSJammer:disableFor(natoName)
3323
+ self.jammerTable[natoName]['canjam'] = false
3324
+ end
3325
+
3326
+ function SkynetIADSJammer:isKnownRadarEmitter(natoName)
3327
+ local isActive = false
3328
+ for unitName, unit in pairs(self.jammerTable) do
3329
+ if unitName == natoName and unit['canjam'] == true then
3330
+ isActive = true
3331
+ end
3332
+ end
3333
+ return isActive
3334
+ end
3335
+
3336
+ function SkynetIADSJammer:addIADS(iads)
3337
+ table.insert(self.iads, iads)
3338
+ end
3339
+
3340
+ function SkynetIADSJammer:getSuccessProbability(distanceNauticalMiles, natoName)
3341
+ local probability = 0
3342
+ local jammerSettings = self.jammerTable[natoName]
3343
+ if jammerSettings ~= nil then
3344
+ probability = jammerSettings['function'](distanceNauticalMiles)
3345
+ end
3346
+ return probability
3347
+ end
3348
+
3349
+ function SkynetIADSJammer:getDistanceNMToRadarUnit(radarUnit)
3350
+ return mist.utils.metersToNM(mist.utils.get3DDist(self.emitter:getPosition().p, radarUnit:getPosition().p))
3351
+ end
3352
+
3353
+ function SkynetIADSJammer.runCycle(self)
3354
+
3355
+ if self.emitter:isExist() == false then
3356
+ self:masterArmSafe()
3357
+ return
3358
+ end
3359
+
3360
+ for i = 1, #self.iads do
3361
+ local iads = self.iads[i]
3362
+ local samSites = iads:getActiveSAMSites()
3363
+ for j = 1, #samSites do
3364
+ local samSite = samSites[j]
3365
+ local radars = samSite:getRadars()
3366
+ local hasLOS = false
3367
+ local distance = 0
3368
+ local natoName = samSite:getNatoName()
3369
+ for l = 1, #radars do
3370
+ local radar = radars[l]
3371
+ distance = self:getDistanceNMToRadarUnit(radar)
3372
+ -- I try to emulate the system as it would work in real life, so a jammer can only jam a SAM site if has line of sight to at least one radar in the group
3373
+ if self:isKnownRadarEmitter(natoName) and self:hasLineOfSightToRadar(radar) and distance <= self.maximumEffectiveDistanceNM then
3374
+ if iads:getDebugSettings().jammerProbability then
3375
+ iads:printOutput("JAMMER: Distance: "..distance)
3376
+ end
3377
+ samSite:jam(self:getSuccessProbability(distance, natoName))
3378
+ end
3379
+ end
3380
+ end
3381
+ end
3382
+ end
3383
+
3384
+ function SkynetIADSJammer:hasLineOfSightToRadar(radar)
3385
+ local radarPos = radar:getPosition().p
3386
+ --lift the radar 30 meters off the ground, some 3d models are dug in to the ground, creating issues in calculating LOS
3387
+ radarPos.y = radarPos.y + 30
3388
+ return land.isVisible(radarPos, self.emitter:getPosition().p)
3389
+ end
3390
+
3391
+ function SkynetIADSJammer:masterArmSafe()
3392
+ mist.removeFunction(self.jammerTaskID)
3393
+ end
3394
+
3395
+ --TODO: Remove Menu when emitter dies:
3396
+ function SkynetIADSJammer:addRadioMenu()
3397
+ self.radioMenu = missionCommands.addSubMenu('Jammer: '..self.emitter:getName())
3398
+ missionCommands.addCommand('Master Arm On', self.radioMenu, SkynetIADSJammer.updateMasterArm, {self = self, option = 'masterArmOn'})
3399
+ missionCommands.addCommand('Master Arm Safe', self.radioMenu, SkynetIADSJammer.updateMasterArm, {self = self, option = 'masterArmSafe'})
3400
+ end
3401
+
3402
+ function SkynetIADSJammer.updateMasterArm(params)
3403
+ local option = params.option
3404
+ local self = params.self
3405
+ if option == 'masterArmOn' then
3406
+ self:masterArmOn()
3407
+ elseif option == 'masterArmSafe' then
3408
+ self:masterArmSafe()
3409
+ end
3410
+ end
3411
+
3412
+ function SkynetIADSJammer:removeRadioMenu()
3413
+ missionCommands.removeItem(self.radioMenu)
3414
+ end
3415
+
3416
+ end
3417
+ do
3418
+
3419
+ SkynetIADSSAMSearchRadar = {}
3420
+ SkynetIADSSAMSearchRadar = inheritsFrom(SkynetIADSAbstractDCSObjectWrapper)
3421
+
3422
+ function SkynetIADSSAMSearchRadar:create(unit)
3423
+ local instance = self:superClass():create(unit)
3424
+ setmetatable(instance, self)
3425
+ self.__index = self
3426
+ instance.firingRangePercent = 100
3427
+ instance.maximumRange = 0
3428
+ instance.initialNumberOfMissiles = 0
3429
+ instance.remainingNumberOfMissiles = 0
3430
+ instance.initialNumberOfShells = 0
3431
+ instance.remainingNumberOfShells = 0
3432
+ instance.triedSensors = 0
3433
+ return instance
3434
+ end
3435
+
3436
+ --override in subclasses to match different datastructure of getSensors()
3437
+ function SkynetIADSSAMSearchRadar:setupRangeData()
3438
+ if self:isExist() then
3439
+ local data = self:getDCSRepresentation():getSensors()
3440
+ if data == nil then
3441
+ --this is to prevent infinite calls between launcher and search radar
3442
+ self.triedSensors = self.triedSensors + 1
3443
+ --the SA-13 does not have any sensor data, but is has launcher data, so we use the stuff from the launcher for the radar range.
3444
+ SkynetIADSSAMLauncher.setupRangeData(self)
3445
+ return
3446
+ end
3447
+ for i = 1, #data do
3448
+ local subEntries = data[i]
3449
+ for j = 1, #subEntries do
3450
+ local sensorInformation = subEntries[j]
3451
+ -- some sam sites have IR and passive EWR detection, we are just interested in the radar data
3452
+ -- investigate if upperHemisphere and headOn is ok, I guess it will work for most detection cases
3453
+ if sensorInformation.type == Unit.SensorType.RADAR and sensorInformation['detectionDistanceAir'] then
3454
+ local upperHemisphere = sensorInformation['detectionDistanceAir']['upperHemisphere']['headOn']
3455
+ local lowerHemisphere = sensorInformation['detectionDistanceAir']['lowerHemisphere']['headOn']
3456
+ self.maximumRange = upperHemisphere
3457
+ if lowerHemisphere > upperHemisphere then
3458
+ self.maximumRange = lowerHemisphere
3459
+ end
3460
+ end
3461
+ end
3462
+ end
3463
+ end
3464
+ end
3465
+
3466
+ function SkynetIADSSAMSearchRadar:getMaxRangeFindingTarget()
3467
+ return self.maximumRange
3468
+ end
3469
+
3470
+ function SkynetIADSSAMSearchRadar:isRadarWorking()
3471
+ -- the ammo check is for the SA-13 which does not return any sensor data:
3472
+ return (self:isExist() == true and ( self:getDCSRepresentation():getSensors() ~= nil or self:getDCSRepresentation():getAmmo() ~= nil ) )
3473
+ end
3474
+
3475
+ function SkynetIADSSAMSearchRadar:setFiringRangePercent(percent)
3476
+ self.firingRangePercent = percent
3477
+ end
3478
+
3479
+ function SkynetIADSSAMSearchRadar:getDistance(target)
3480
+ return mist.utils.get2DDist(target:getPosition().p, self:getDCSRepresentation():getPosition().p)
3481
+ end
3482
+
3483
+ function SkynetIADSSAMSearchRadar:getHeight(target)
3484
+ local radarElevation = self:getDCSRepresentation():getPosition().p.y
3485
+ local targetElevation = target:getPosition().p.y
3486
+ return math.abs(targetElevation - radarElevation)
3487
+ end
3488
+
3489
+ function SkynetIADSSAMSearchRadar:isInHorizontalRange(target)
3490
+ return (self:getMaxRangeFindingTarget() / 100 * self.firingRangePercent) >= self:getDistance(target)
3491
+ end
3492
+
3493
+ function SkynetIADSSAMSearchRadar:isInRange(target)
3494
+ if self:isExist() == false then
3495
+ return false
3496
+ end
3497
+ return self:isInHorizontalRange(target)
3498
+ end
3499
+
3500
+ end
3501
+
3502
+ do
3503
+
3504
+ SkynetIADSSamSite = {}
3505
+ SkynetIADSSamSite = inheritsFrom(SkynetIADSAbstractRadarElement)
3506
+
3507
+ function SkynetIADSSamSite:create(samGroup, iads)
3508
+ local sam = self:superClass():create(samGroup, iads)
3509
+ setmetatable(sam, self)
3510
+ self.__index = self
3511
+ sam.targetsInRange = false
3512
+ sam.goLiveConstraints = {}
3513
+ return sam
3514
+ end
3515
+
3516
+ function SkynetIADSSamSite:addGoLiveConstraint(constraintName, constraint)
3517
+ self.goLiveConstraints[constraintName] = constraint
3518
+ end
3519
+
3520
+ function SkynetIADSAbstractRadarElement:areGoLiveConstraintsSatisfied(contact)
3521
+ for constraintName, constraint in pairs(self.goLiveConstraints) do
3522
+ if ( constraint(contact) ~= true ) then
3523
+ return false
3524
+ end
3525
+ end
3526
+ return true
3527
+ end
3528
+
3529
+ function SkynetIADSAbstractRadarElement:removeGoLiveConstraint(constraintName)
3530
+ local constraints = {}
3531
+ for cName, constraint in pairs(self.goLiveConstraints) do
3532
+ if cName ~= constraintName then
3533
+ constraints[cName] = constraint
3534
+ end
3535
+ end
3536
+ self.goLiveConstraints = constraints
3537
+ end
3538
+
3539
+ function SkynetIADSAbstractRadarElement:getGoLiveConstraints()
3540
+ return self.goLiveConstraints
3541
+ end
3542
+
3543
+ function SkynetIADSSamSite:isDestroyed()
3544
+ local isDestroyed = true
3545
+ for i = 1, #self.launchers do
3546
+ local launcher = self.launchers[i]
3547
+ if launcher:isExist() == true then
3548
+ isDestroyed = false
3549
+ end
3550
+ end
3551
+ local radars = self:getRadars()
3552
+ for i = 1, #radars do
3553
+ local radar = radars[i]
3554
+ if radar:isExist() == true then
3555
+ isDestroyed = false
3556
+ end
3557
+ end
3558
+ return isDestroyed
3559
+ end
3560
+
3561
+ function SkynetIADSSamSite:targetCycleUpdateStart()
3562
+ self.targetsInRange = false
3563
+ end
3564
+
3565
+ function SkynetIADSSamSite:targetCycleUpdateEnd()
3566
+ if self.targetsInRange == false and self.actAsEW == false and self:getAutonomousState() == false and self:getAutonomousBehaviour() == SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DCS_AI then
3567
+ self:goDark()
3568
+ end
3569
+ end
3570
+
3571
+ function SkynetIADSSamSite:informOfContact(contact)
3572
+ -- we make sure isTargetInRange (expensive call) is only triggered if no previous calls to this method resulted in targets in range
3573
+ if ( self.targetsInRange == false and self:areGoLiveConstraintsSatisfied(contact) == true and self:isTargetInRange(contact) and ( contact:isIdentifiedAsHARM() == false or ( contact:isIdentifiedAsHARM() == true and self:getCanEngageHARM() == true ) ) ) then
3574
+ self:goLive()
3575
+ self.targetsInRange = true
3576
+ end
3577
+ end
3578
+
3579
+ end
3580
+ do
3581
+
3582
+ SkynetIADSSAMTrackingRadar = {}
3583
+ SkynetIADSSAMTrackingRadar = inheritsFrom(SkynetIADSSAMSearchRadar)
3584
+
3585
+ function SkynetIADSSAMTrackingRadar:create(unit)
3586
+ local instance = self:superClass():create(unit)
3587
+ setmetatable(instance, self)
3588
+ self.__index = self
3589
+ return instance
3590
+ end
3591
+
3592
+ end
3593
+ do
3594
+
3595
+ SkynetIADSSAMLauncher = {}
3596
+ SkynetIADSSAMLauncher = inheritsFrom(SkynetIADSSAMSearchRadar)
3597
+
3598
+ function SkynetIADSSAMLauncher:create(unit)
3599
+ local instance = self:superClass():create(unit)
3600
+ setmetatable(instance, self)
3601
+ self.__index = self
3602
+ instance.maximumFiringAltitude = 0
3603
+ return instance
3604
+ end
3605
+
3606
+ function SkynetIADSSAMLauncher:setupRangeData()
3607
+ self.remainingNumberOfMissiles = 0
3608
+ self.remainingNumberOfShells = 0
3609
+ if self:isExist() then
3610
+ local data = self:getDCSRepresentation():getAmmo()
3611
+ local initialNumberOfMissiles = 0
3612
+ local initialNumberOfShells = 0
3613
+ --data becomes nil, when all missiles are fired
3614
+ if data then
3615
+ for i = 1, #data do
3616
+ local ammo = data[i]
3617
+ --we ignore checks on radar guidance types, since we are not interested in how exactly the missile is guided by the SAM site.
3618
+ if ammo.desc.category == Weapon.Category.MISSILE then
3619
+ --TODO: see what the difference is between Max and Min values, SA-3 has higher Min value than Max?, most likely it has to do with the box parameters supplied by launcher
3620
+ --to simplyfy we just use the larger value, sam sites need a few seconds of tracking time to fire, by that time contact has most likely closed in on the SAM site.
3621
+ local altMin = ammo.desc.rangeMaxAltMin
3622
+ local altMax = ammo.desc.rangeMaxAltMax
3623
+ self.maximumRange = altMin
3624
+ if altMin < altMax then
3625
+ self.maximumRange = altMax
3626
+ end
3627
+ self.maximumFiringAltitude = ammo.desc.altMax
3628
+ self.remainingNumberOfMissiles = self.remainingNumberOfMissiles + ammo.count
3629
+ initialNumberOfMissiles = self.remainingNumberOfMissiles
3630
+ end
3631
+ if ammo.desc.category == Weapon.Category.SHELL then
3632
+ self.remainingNumberOfShells = self.remainingNumberOfShells + ammo.count
3633
+ initialNumberOfShells = self.remainingNumberOfShells
3634
+ end
3635
+ --if no distance was detected we run the code for the search radar. This happens when all in one units are passed like the shilka
3636
+ if self.maximumRange == 0 then
3637
+ --this is to prevent infinite calls between launcher and search radar
3638
+ if self.triedSensors <= 2 then
3639
+ SkynetIADSSAMSearchRadar.setupRangeData(self)
3640
+ end
3641
+ end
3642
+ end
3643
+ -- conditions here are because setupRangeData() is called multiple times in the code to update ammo status, we set initial values only the first time the method is called
3644
+ if self.initialNumberOfMissiles == 0 then
3645
+ self.initialNumberOfMissiles = initialNumberOfMissiles
3646
+ end
3647
+ if self.initialNumberOfShells == 0 then
3648
+ self.initialNumberOfShells = initialNumberOfShells
3649
+ end
3650
+ end
3651
+ end
3652
+ end
3653
+
3654
+ function SkynetIADSSAMLauncher:getInitialNumberOfShells()
3655
+ return self.initialNumberOfShells
3656
+ end
3657
+
3658
+ function SkynetIADSSAMLauncher:getRemainingNumberOfShells()
3659
+ self:setupRangeData()
3660
+ return self.remainingNumberOfShells
3661
+ end
3662
+
3663
+ function SkynetIADSSAMLauncher:getInitialNumberOfMissiles()
3664
+ return self.initialNumberOfMissiles
3665
+ end
3666
+
3667
+ function SkynetIADSSAMLauncher:getRemainingNumberOfMissiles()
3668
+ self:setupRangeData()
3669
+ return self.remainingNumberOfMissiles
3670
+ end
3671
+
3672
+ function SkynetIADSSAMLauncher:getRange()
3673
+ return self.maximumRange
3674
+ end
3675
+
3676
+ function SkynetIADSSAMLauncher:getMaximumFiringAltitude()
3677
+ return self.maximumFiringAltitude
3678
+ end
3679
+
3680
+ function SkynetIADSSAMLauncher:isWithinFiringHeight(target)
3681
+ -- if no max firing height is set (radar quided AAA) then we use the vertical range, bit of a hack but probably ok for AAA
3682
+ if self:getMaximumFiringAltitude() > 0 then
3683
+ return self:getMaximumFiringAltitude() >= self:getHeight(target)
3684
+ else
3685
+ return self:getRange() >= self:getHeight(target)
3686
+ end
3687
+ end
3688
+
3689
+ function SkynetIADSSAMLauncher:isInRange(target)
3690
+ if self:isExist() == false then
3691
+ return false
3692
+ end
3693
+ return self:isWithinFiringHeight(target) and self:isInHorizontalRange(target)
3694
+ end
3695
+
3696
+ end
3697
+
3698
+ --[[
3699
+ SA-2 Launcher:
3700
+ {
3701
+ count=1,
3702
+ desc={
3703
+ Nmax=17,
3704
+ RCS=0.39669999480247,
3705
+ _origin="",
3706
+ altMax=25000,
3707
+ altMin=100,
3708
+ box={
3709
+ max={x=4.7303376197815, y=0.84564626216888, z=0.84564626216888},
3710
+ min={x=-5.8387970924377, y=-0.84564626216888, z=-0.84564626216888}
3711
+ },
3712
+ category=1,
3713
+ displayName="SA2V755",
3714
+ fuseDist=20,
3715
+ guidance=4,
3716
+ life=2,
3717
+ missileCategory=2,
3718
+ rangeMaxAltMax=30000,
3719
+ rangeMaxAltMin=40000,
3720
+ rangeMin=7000,
3721
+ typeName="SA2V755",
3722
+ warhead={caliber=500, explosiveMass=196, mass=196, type=1}
3723
+ }
3724
+ }
3725
+ }
3726
+ --]]
3727
+ do
3728
+
3729
+ SkynetIADSHARMDetection = {}
3730
+ SkynetIADSHARMDetection.__index = SkynetIADSHARMDetection
3731
+
3732
+ SkynetIADSHARMDetection.HARM_THRESHOLD_SPEED_KTS = 800
3733
+
3734
+ function SkynetIADSHARMDetection:create(iads)
3735
+ local harmDetection = {}
3736
+ setmetatable(harmDetection, self)
3737
+ harmDetection.contacts = {}
3738
+ harmDetection.iads = iads
3739
+ harmDetection.contactRadarsEvaluated = {}
3740
+ return harmDetection
3741
+ end
3742
+
3743
+ function SkynetIADSHARMDetection:setContacts(contacts)
3744
+ self.contacts = contacts
3745
+ end
3746
+
3747
+ function SkynetIADSHARMDetection:evaluateContacts()
3748
+ self:cleanAgedContacts()
3749
+ for i = 1, #self.contacts do
3750
+ local contact = self.contacts[i]
3751
+ local groundSpeed = contact:getGroundSpeedInKnots(0)
3752
+ --if a contact has only been hit by a radar once it's speed is 0
3753
+ if groundSpeed == 0 then
3754
+ return
3755
+ end
3756
+ local simpleAltitudeProfile = contact:getSimpleAltitudeProfile()
3757
+ local newRadarsToEvaluate = self:getNewRadarsThatHaveDetectedContact(contact)
3758
+ --self.iads:printOutputToLog(contact:getName().." new Radars to evaluate: "..#newRadarsToEvaluate)
3759
+ --self.iads:printOutputToLog(contact:getName().." ground speed: "..groundSpeed)
3760
+ if ( #newRadarsToEvaluate > 0 and contact:isIdentifiedAsHARM() == false and ( groundSpeed > SkynetIADSHARMDetection.HARM_THRESHOLD_SPEED_KTS and #simpleAltitudeProfile <= 2 ) ) then
3761
+ local detectionProbability = self:getDetectionProbability(newRadarsToEvaluate)
3762
+ --self.iads:printOutputToLog("DETECTION PROB: "..detectionProbability)
3763
+ if ( self:shallReactToHARM(detectionProbability) ) then
3764
+ contact:setHARMState(SkynetIADSContact.HARM)
3765
+ if (self.iads:getDebugSettings().harmDefence ) then
3766
+ self.iads:printOutputToLog("HARM IDENTIFIED: "..contact:getTypeName().." | DETECTION PROBABILITY WAS: "..detectionProbability.."%")
3767
+ end
3768
+ else
3769
+ contact:setHARMState(SkynetIADSContact.NOT_HARM)
3770
+ if (self.iads:getDebugSettings().harmDefence ) then
3771
+ self.iads:printOutputToLog("HARM NOT IDENTIFIED: "..contact:getTypeName().." | DETECTION PROBABILITY WAS: "..detectionProbability.."%")
3772
+ end
3773
+ end
3774
+ end
3775
+
3776
+ if ( #simpleAltitudeProfile > 2 and contact:isIdentifiedAsHARM() ) then
3777
+ contact:setHARMState(SkynetIADSContact.HARM_UNKNOWN)
3778
+ if (self.iads:getDebugSettings().harmDefence ) then
3779
+ self.iads:printOutputToLog("CORRECTING HARM STATE: CONTACT IS NOT A HARM: "..contact:getName())
3780
+ end
3781
+ end
3782
+
3783
+ if ( contact:isIdentifiedAsHARM() ) then
3784
+ self:informRadarsOfHARM(contact)
3785
+ end
3786
+ end
3787
+ end
3788
+
3789
+ function SkynetIADSHARMDetection:cleanAgedContacts()
3790
+ local activeContactRadars = {}
3791
+ for contact, radars in pairs (self.contactRadarsEvaluated) do
3792
+ if contact:getAge() < 32 then
3793
+ activeContactRadars[contact] = radars
3794
+ end
3795
+ end
3796
+ self.contactRadarsEvaluated = activeContactRadars
3797
+ end
3798
+
3799
+ function SkynetIADSHARMDetection:getNewRadarsThatHaveDetectedContact(contact)
3800
+ local newRadars = contact:getAbstractRadarElementsDetected()
3801
+ local radars = self.contactRadarsEvaluated[contact]
3802
+ if radars then
3803
+ newRadars = {}
3804
+ local contactRadars = contact:getAbstractRadarElementsDetected()
3805
+ for i = 1, #contactRadars do
3806
+ local contactRadar = contactRadars[i]
3807
+ local newRadar = self:isElementInTable(radars, contactRadar)
3808
+ if newRadar ~= nil then
3809
+ table.insert(newRadars, newRadar)
3810
+ end
3811
+ end
3812
+ end
3813
+ self.contactRadarsEvaluated[contact] = contact:getAbstractRadarElementsDetected()
3814
+ return newRadars
3815
+ end
3816
+
3817
+ function SkynetIADSHARMDetection:isElementInTable(tbl, element)
3818
+ for i = 1, #tbl do
3819
+ tblElement = tbl[i]
3820
+ if tblElement == element then
3821
+ return nil
3822
+ end
3823
+ end
3824
+ return element
3825
+ end
3826
+
3827
+ function SkynetIADSHARMDetection:informRadarsOfHARM(contact)
3828
+ local samSites = self.iads:getUsableSAMSites()
3829
+ self:updateRadarsOfSites(samSites, contact)
3830
+
3831
+ local ewRadars = self.iads:getUsableEarlyWarningRadars()
3832
+ self:updateRadarsOfSites(ewRadars, contact)
3833
+ end
3834
+
3835
+ function SkynetIADSHARMDetection:updateRadarsOfSites(sites, contact)
3836
+ for i = 1, #sites do
3837
+ local site = sites[i]
3838
+ site:informOfHARM(contact)
3839
+ end
3840
+ end
3841
+
3842
+ function SkynetIADSHARMDetection:shallReactToHARM(chance)
3843
+ return chance >= math.random(1, 100)
3844
+ end
3845
+
3846
+ function SkynetIADSHARMDetection:getDetectionProbability(radars)
3847
+ local detectionChance = 0
3848
+ local missChance = 100
3849
+ local detection = 0
3850
+ for i = 1, #radars do
3851
+ detection = radars[i]:getHARMDetectionChance()
3852
+ if ( detectionChance == 0 ) then
3853
+ detectionChance = detection
3854
+ else
3855
+ detectionChance = detectionChance + (detection * (missChance / 100))
3856
+ end
3857
+ missChance = 100 - detection
3858
+ end
3859
+ return detectionChance
3860
+ end
3861
+
3862
+ end
3863
+
3864
+