diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/Battle of Gori/.indexes/properties.index b/.metadata/.plugins/org.eclipse.core.resources/.projects/Battle of Gori/.indexes/properties.index
new file mode 100644
index 0000000..151a9d4
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/Battle of Gori/.indexes/properties.index differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/Battle of Gori/.location b/.metadata/.plugins/org.eclipse.core.resources/.projects/Battle of Gori/.location
new file mode 100644
index 0000000..d5f5292
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/Battle of Gori/.location differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/Battle of Gori/org.eclipse.dltk.core/state.dat b/.metadata/.plugins/org.eclipse.core.resources/.projects/Battle of Gori/org.eclipse.dltk.core/state.dat
new file mode 100644
index 0000000..d4ec1f2
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/Battle of Gori/org.eclipse.dltk.core/state.dat differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Afgainistan/.indexes/properties.index b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Afgainistan/.indexes/properties.index
new file mode 100644
index 0000000..151a9d4
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Afgainistan/.indexes/properties.index differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Afgainistan/.location b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Afgainistan/.location
new file mode 100644
index 0000000..91c823e
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Afgainistan/.location differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Afgainistan/org.eclipse.dltk.core/state.dat b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Afgainistan/org.eclipse.dltk.core/state.dat
new file mode 100644
index 0000000..b6bb50c
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Afgainistan/org.eclipse.dltk.core/state.dat differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Caucasus/.indexes/properties.index b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Caucasus/.indexes/properties.index
new file mode 100644
index 0000000..151a9d4
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Caucasus/.indexes/properties.index differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Caucasus/.location b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Caucasus/.location
new file mode 100644
index 0000000..ade34eb
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Caucasus/.location differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Caucasus/org.eclipse.dltk.core/state.dat b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Caucasus/org.eclipse.dltk.core/state.dat
new file mode 100644
index 0000000..67e0396
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Caucasus/org.eclipse.dltk.core/state.dat differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Falklands/.indexes/properties.index b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Falklands/.indexes/properties.index
new file mode 100644
index 0000000..151a9d4
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Falklands/.indexes/properties.index differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Falklands/.location b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Falklands/.location
new file mode 100644
index 0000000..171e489
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Falklands/.location differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Falklands/org.eclipse.dltk.core/state.dat b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Falklands/org.eclipse.dltk.core/state.dat
new file mode 100644
index 0000000..aed318a
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Falklands/org.eclipse.dltk.core/state.dat differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Marianas/.indexes/properties.index b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Marianas/.indexes/properties.index
new file mode 100644
index 0000000..151a9d4
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Marianas/.indexes/properties.index differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Marianas/.location b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Marianas/.location
new file mode 100644
index 0000000..5b9c1d0
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Marianas/.location differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Marianas/org.eclipse.dltk.core/state.dat b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Marianas/org.eclipse.dltk.core/state.dat
new file mode 100644
index 0000000..6eff08a
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Marianas/org.eclipse.dltk.core/state.dat differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Nevada/.indexes/properties.index b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Nevada/.indexes/properties.index
new file mode 100644
index 0000000..151a9d4
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Nevada/.indexes/properties.index differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Nevada/.location b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Nevada/.location
new file mode 100644
index 0000000..3000806
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Nevada/.location differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Nevada/org.eclipse.dltk.core/state.dat b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Nevada/org.eclipse.dltk.core/state.dat
new file mode 100644
index 0000000..f9d352e
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Nevada/org.eclipse.dltk.core/state.dat differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Normandy/.indexes/properties.index b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Normandy/.indexes/properties.index
new file mode 100644
index 0000000..151a9d4
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Normandy/.indexes/properties.index differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Normandy/.location b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Normandy/.location
new file mode 100644
index 0000000..d4cd151
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Normandy/.location differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Normandy/org.eclipse.dltk.core/state.dat b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Normandy/org.eclipse.dltk.core/state.dat
new file mode 100644
index 0000000..afc328e
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Normandy/org.eclipse.dltk.core/state.dat differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Persian_Gulf/.indexes/properties.index b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Persian_Gulf/.indexes/properties.index
new file mode 100644
index 0000000..151a9d4
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Persian_Gulf/.indexes/properties.index differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Persian_Gulf/.location b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Persian_Gulf/.location
new file mode 100644
index 0000000..8489a54
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Persian_Gulf/.location differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Persian_Gulf/org.eclipse.dltk.core/state.dat b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Persian_Gulf/org.eclipse.dltk.core/state.dat
new file mode 100644
index 0000000..ec92671
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Persian_Gulf/org.eclipse.dltk.core/state.dat differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Sinai/.indexes/properties.index b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Sinai/.indexes/properties.index
new file mode 100644
index 0000000..151a9d4
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Sinai/.indexes/properties.index differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Sinai/.location b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Sinai/.location
new file mode 100644
index 0000000..630f412
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Sinai/.location differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Sinai/org.eclipse.dltk.core/state.dat b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Sinai/org.eclipse.dltk.core/state.dat
new file mode 100644
index 0000000..bf91ce7
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Sinai/org.eclipse.dltk.core/state.dat differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Syria/.indexes/properties.index b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Syria/.indexes/properties.index
new file mode 100644
index 0000000..151a9d4
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Syria/.indexes/properties.index differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Syria/.location b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Syria/.location
new file mode 100644
index 0000000..d86dc99
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Syria/.location differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Syria/org.eclipse.dltk.core/state.dat b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Syria/org.eclipse.dltk.core/state.dat
new file mode 100644
index 0000000..5e66f12
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/DCS_Syria/org.eclipse.dltk.core/state.dat differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Framework/.indexes/properties.index b/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Framework/.indexes/properties.index
new file mode 100644
index 0000000..151a9d4
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Framework/.indexes/properties.index differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Framework/.location b/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Framework/.location
new file mode 100644
index 0000000..15fbfa5
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Framework/.location differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Framework/org.eclipse.dltk.core/state.dat b/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Framework/org.eclipse.dltk.core/state.dat
new file mode 100644
index 0000000..6247530
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Framework/org.eclipse.dltk.core/state.dat differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Missions/.indexes/properties.index b/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Missions/.indexes/properties.index
new file mode 100644
index 0000000..151a9d4
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Missions/.indexes/properties.index differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Missions/.location b/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Missions/.location
new file mode 100644
index 0000000..eb89dba
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Missions/.location differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Missions/org.eclipse.dltk.core/state.dat b/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Missions/org.eclipse.dltk.core/state.dat
new file mode 100644
index 0000000..b00029e
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.projects/Moose_Missions/org.eclipse.dltk.core/state.dat differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.index b/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.index
new file mode 100644
index 0000000..898f8c4
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.root/.indexes/properties.index differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.root/1.tree b/.metadata/.plugins/org.eclipse.core.resources/.root/1.tree
deleted file mode 100644
index 984bfda..0000000
Binary files a/.metadata/.plugins/org.eclipse.core.resources/.root/1.tree and /dev/null differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.root/3.tree b/.metadata/.plugins/org.eclipse.core.resources/.root/3.tree
new file mode 100644
index 0000000..2be290f
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.core.resources/.root/3.tree differ
diff --git a/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources b/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources
index c6f84fb..35d19ed 100644
Binary files a/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources and b/.metadata/.plugins/org.eclipse.core.resources/.safetable/org.eclipse.core.resources differ
diff --git a/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ldt.prefs b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ldt.prefs
new file mode 100644
index 0000000..08f2473
--- /dev/null
+++ b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ldt.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+org.eclipse.dltk.core.codeComplete.visibilityCheck=enabled
diff --git a/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ldt.ui.prefs b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ldt.ui.prefs
new file mode 100644
index 0000000..238fe2b
--- /dev/null
+++ b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ldt.ui.prefs
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+sourceHoverBackgroundColor=255,255,225
+sourceHoverBackgroundColor.SystemDefault=true
+useAnnotationsPrefPage=true
+useQuickDiffPrefPage=true
diff --git a/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.team.ui.prefs b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.team.ui.prefs
new file mode 100644
index 0000000..56cd496
--- /dev/null
+++ b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.team.ui.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+org.eclipse.team.ui.first_time=false
diff --git a/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.editors.prefs b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.editors.prefs
new file mode 100644
index 0000000..61f3bb8
--- /dev/null
+++ b/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.editors.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+overviewRuler_migration=migrated_3.1
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/1484524933.index b/.metadata/.plugins/org.eclipse.dltk.core/1484524933.index
new file mode 100644
index 0000000..c94dbd3
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/1484524933.index differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/1502499027.index b/.metadata/.plugins/org.eclipse.dltk.core/1502499027.index
new file mode 100644
index 0000000..569a221
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/1502499027.index differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/1505386533.index b/.metadata/.plugins/org.eclipse.dltk.core/1505386533.index
new file mode 100644
index 0000000..3d91a7c
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/1505386533.index differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/1747788129.index b/.metadata/.plugins/org.eclipse.dltk.core/1747788129.index
new file mode 100644
index 0000000..dc0a8ee
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/1747788129.index differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/1939285624.index b/.metadata/.plugins/org.eclipse.dltk.core/1939285624.index
new file mode 100644
index 0000000..569a221
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/1939285624.index differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/2538043997.index b/.metadata/.plugins/org.eclipse.dltk.core/2538043997.index
new file mode 100644
index 0000000..a01b55e
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/2538043997.index differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/2768864900.index b/.metadata/.plugins/org.eclipse.dltk.core/2768864900.index
new file mode 100644
index 0000000..c94dbd3
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/2768864900.index differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/329329841.index b/.metadata/.plugins/org.eclipse.dltk.core/329329841.index
new file mode 100644
index 0000000..c94dbd3
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/329329841.index differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/3688405372.index b/.metadata/.plugins/org.eclipse.dltk.core/3688405372.index
new file mode 100644
index 0000000..c94dbd3
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/3688405372.index differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/4554018.index b/.metadata/.plugins/org.eclipse.dltk.core/4554018.index
new file mode 100644
index 0000000..c94dbd3
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/4554018.index differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/567655800.index b/.metadata/.plugins/org.eclipse.dltk.core/567655800.index
new file mode 100644
index 0000000..c94dbd3
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/567655800.index differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/640583989.index b/.metadata/.plugins/org.eclipse.dltk.core/640583989.index
new file mode 100644
index 0000000..c94dbd3
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/640583989.index differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/998229636.index b/.metadata/.plugins/org.eclipse.dltk.core/998229636.index
new file mode 100644
index 0000000..c94dbd3
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/998229636.index differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/Containers.dat b/.metadata/.plugins/org.eclipse.dltk.core/Containers.dat
index 9c82273..c4e995e 100644
Binary files a/.metadata/.plugins/org.eclipse.dltk.core/Containers.dat and b/.metadata/.plugins/org.eclipse.dltk.core/Containers.dat differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/cache/2838196607/2210315311/5.idx b/.metadata/.plugins/org.eclipse.dltk.core/cache/2838196607/2210315311/5.idx
new file mode 100644
index 0000000..6bccb0b
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/cache/2838196607/2210315311/5.idx differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/cache/2838196607/2846345860/7.idx b/.metadata/.plugins/org.eclipse.dltk.core/cache/2838196607/2846345860/7.idx
new file mode 100644
index 0000000..1377e93
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/cache/2838196607/2846345860/7.idx differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/cache/2838196607/336605292/3.idx b/.metadata/.plugins/org.eclipse.dltk.core/cache/2838196607/336605292/3.idx
new file mode 100644
index 0000000..9bfe373
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/cache/2838196607/336605292/3.idx differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/cache/2838196607/3672975892/1.idx b/.metadata/.plugins/org.eclipse.dltk.core/cache/2838196607/3672975892/1.idx
new file mode 100644
index 0000000..9e4db47
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/cache/2838196607/3672975892/1.idx differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/cache/index b/.metadata/.plugins/org.eclipse.dltk.core/cache/index
new file mode 100644
index 0000000..c967b01
--- /dev/null
+++ b/.metadata/.plugins/org.eclipse.dltk.core/cache/index
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/customTimeStamps b/.metadata/.plugins/org.eclipse.dltk.core/customTimeStamps
new file mode 100644
index 0000000..593f470
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/customTimeStamps differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/externalLibsTimeStamps b/.metadata/.plugins/org.eclipse.dltk.core/externalLibsTimeStamps
new file mode 100644
index 0000000..b77c9f3
Binary files /dev/null and b/.metadata/.plugins/org.eclipse.dltk.core/externalLibsTimeStamps differ
diff --git a/.metadata/.plugins/org.eclipse.dltk.core/savedIndexNames.txt b/.metadata/.plugins/org.eclipse.dltk.core/savedIndexNames.txt
new file mode 100644
index 0000000..e328cf6
--- /dev/null
+++ b/.metadata/.plugins/org.eclipse.dltk.core/savedIndexNames.txt
@@ -0,0 +1,2 @@
+C:\DCS_MissionDev\.metadata\.plugins\org.eclipse.dltk.core\2538043997.index
+C:\DCS_MissionDev\.metadata\.plugins\org.eclipse.dltk.core\1505386533.index
diff --git a/.metadata/.plugins/org.eclipse.dltk.ui/dialog_settings.xml b/.metadata/.plugins/org.eclipse.dltk.ui/dialog_settings.xml
index a7b2b49..a6308c8 100644
--- a/.metadata/.plugins/org.eclipse.dltk.ui/dialog_settings.xml
+++ b/.metadata/.plugins/org.eclipse.dltk.ui/dialog_settings.xml
@@ -7,4 +7,6 @@
+
diff --git a/.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi b/.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi
index 7e6da53..57db667 100644
--- a/.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi
+++ b/.metadata/.plugins/org.eclipse.e4.workbench/workbench.xmi
@@ -1,9 +1,9 @@
-
+
activeSchemeId:org.eclipse.ui.defaultAcceleratorConfiguration
ModelMigrationProcessor.001
-
+
@@ -39,7 +39,7 @@
persp.perspSC:org.eclipse.debug.ui.DebugPerspective
persp.actionSet:org.eclipse.debug.ui.launchActionSet
-
+
active
@@ -66,7 +66,7 @@
-
+
@@ -200,10 +200,16 @@
-
-
+
+
org.eclipse.e4.primaryDataStack
EditorStack
+
+
+ Editor
+ org.eclipse.ldt.ui.editor
+ removeOnHide
+
@@ -353,7 +359,7 @@
Draggable
-
+
toolbarSeparator
@@ -381,7 +387,7 @@
Draggable
-
+
toolbarSeparator
diff --git a/.metadata/.plugins/org.eclipse.ui.ide/dialog_settings.xml b/.metadata/.plugins/org.eclipse.ui.ide/dialog_settings.xml
index 5ca0b77..e4f5727 100644
--- a/.metadata/.plugins/org.eclipse.ui.ide/dialog_settings.xml
+++ b/.metadata/.plugins/org.eclipse.ui.ide/dialog_settings.xml
@@ -1,3 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.metadata/.plugins/org.eclipse.ui.workbench/dialog_settings.xml b/.metadata/.plugins/org.eclipse.ui.workbench/dialog_settings.xml
index 851c296..2e27e17 100644
--- a/.metadata/.plugins/org.eclipse.ui.workbench/dialog_settings.xml
+++ b/.metadata/.plugins/org.eclipse.ui.workbench/dialog_settings.xml
@@ -1,5 +1,9 @@
+
diff --git a/.metadata/version.ini b/.metadata/version.ini
index 55b108f..a4e9450 100644
--- a/.metadata/version.ini
+++ b/.metadata/version.ini
@@ -1,3 +1,3 @@
-#Sat Nov 16 11:11:18 CST 2024
+#Tue Sep 30 15:30:00 CDT 2025
org.eclipse.core.runtime=2
org.eclipse.platform=4.8.0.v20180308-0630
diff --git a/DCS_Kola/F99th-Operation_Polar_Shield_1.0.0.miz b/DCS_Kola/F99th-Operation_Polar_Shield_1.0.0.miz
new file mode 100644
index 0000000..c1bf30e
Binary files /dev/null and b/DCS_Kola/F99th-Operation_Polar_Shield_1.0.0.miz differ
diff --git a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.0.miz b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.0.miz
new file mode 100644
index 0000000..7398d33
Binary files /dev/null and b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.0.miz differ
diff --git a/DCS_Kola/Operation_Polar_Shield/Moose_CaptureZones.lua b/DCS_Kola/Operation_Polar_Shield/Moose_CaptureZones.lua
new file mode 100644
index 0000000..877de92
--- /dev/null
+++ b/DCS_Kola/Operation_Polar_Shield/Moose_CaptureZones.lua
@@ -0,0 +1,239 @@
+-- Setup Capture Missions & Zones
+
+
+-- Setup BLUE Missions
+do -- Missions
+
+ US_Mission_Capture_Airfields = MISSION:New( US_CC, "Capture the Airfields", "Primary",
+ "Capture the Air Bases marked on your F10 map.\n" ..
+ "Destroy enemy ground forces in the surrounding area, " ..
+ "then occupy each capture zone with a platoon.\n " ..
+ "Your orders are to hold position until all capture zones are taken.\n" ..
+ "Use the map (F10) for a clear indication of the location of each capture zone.\n" ..
+ "Note that heavy resistance can be expected at the airbases!\n"
+ , coalition.side.BLUE)
+
+ US_Score = SCORING:New( "Capture Airfields" )
+
+ US_Mission_Capture_Airfields:AddScoring( US_Score )
+
+ US_Mission_Capture_Airfields:Start()
+
+end
+
+
+
+-- Red Airbases (from TADC configuration)
+env.info("[DEBUG] Initializing Capture Zone: Kilpyavr")
+CaptureZone_Kilpyavr = ZONE:New( "Capture Kilpyavr" )
+ZoneCapture_Kilpyavr = ZONE_CAPTURE_COALITION:New( CaptureZone_Kilpyavr, coalition.side.RED )
+-- SetMarkReadOnly method not available in this MOOSE version - feature disabled
+ZoneCapture_Kilpyavr:__Guard( 1 )
+ZoneCapture_Kilpyavr:Start( 30, 30 )
+env.info("[DEBUG] Kilpyavr zone initialization complete")
+
+env.info("[DEBUG] Initializing Capture Zone: Severomorsk-1")
+CaptureZone_Severomorsk_1 = ZONE:New( "Capture Severomorsk-1" )
+ZoneCapture_Severomorsk_1 = ZONE_CAPTURE_COALITION:New( CaptureZone_Severomorsk_1, coalition.side.RED )
+-- SetMarkReadOnly method not available in this MOOSE version - feature disabled
+ZoneCapture_Severomorsk_1:__Guard( 1 )
+ZoneCapture_Severomorsk_1:Start( 30, 30 )
+env.info("[DEBUG] Severomorsk-1 zone initialization complete")
+
+env.info("[DEBUG] Initializing Capture Zone: Severomorsk-3")
+CaptureZone_Severomorsk_3 = ZONE:New( "Capture Severomorsk-3" )
+ZoneCapture_Severomorsk_3 = ZONE_CAPTURE_COALITION:New( CaptureZone_Severomorsk_3, coalition.side.RED )
+-- SetMarkReadOnly method not available in this MOOSE version - feature disabled
+ZoneCapture_Severomorsk_3:__Guard( 1 )
+ZoneCapture_Severomorsk_3:Start( 30, 30 )
+env.info("[DEBUG] Severomorsk-3 zone initialization complete")
+
+env.info("[DEBUG] Initializing Capture Zone: Murmansk International")
+CaptureZone_Murmansk_International = ZONE:New( "Capture Murmansk International" )
+ZoneCapture_Murmansk_International = ZONE_CAPTURE_COALITION:New( CaptureZone_Murmansk_International, coalition.side.RED )
+-- SetMarkReadOnly method not available in this MOOSE version - feature disabled
+ZoneCapture_Murmansk_International:__Guard( 1 )
+ZoneCapture_Murmansk_International:Start( 30, 30 )
+env.info("[DEBUG] Murmansk International zone initialization complete")
+
+env.info("[DEBUG] Initializing Capture Zone: Monchegorsk")
+CaptureZone_Monchegorsk = ZONE:New( "Capture Monchegorsk" )
+ZoneCapture_Monchegorsk = ZONE_CAPTURE_COALITION:New( CaptureZone_Monchegorsk, coalition.side.RED )
+-- SetMarkReadOnly method not available in this MOOSE version - feature disabled
+ZoneCapture_Monchegorsk:__Guard( 1 )
+ZoneCapture_Monchegorsk:Start( 30, 30 )
+env.info("[DEBUG] Monchegorsk zone initialization complete")
+
+env.info("[DEBUG] Initializing Capture Zone: Olenya")
+CaptureZone_Olenya = ZONE:New( "Capture Olenya" )
+ZoneCapture_Olenya = ZONE_CAPTURE_COALITION:New( CaptureZone_Olenya, coalition.side.RED )
+-- SetMarkReadOnly method not available in this MOOSE version - feature disabled
+ZoneCapture_Olenya:__Guard( 1 )
+ZoneCapture_Olenya:Start( 30, 30 )
+env.info("[DEBUG] Olenya zone initialization complete")
+
+env.info("[DEBUG] Initializing Capture Zone: Afrikanda")
+CaptureZone_Afrikanda = ZONE:New( "Capture Afrikanda" )
+ZoneCapture_Afrikanda = ZONE_CAPTURE_COALITION:New( CaptureZone_Afrikanda, coalition.side.RED )
+-- SetMarkReadOnly method not available in this MOOSE version - feature disabled
+ZoneCapture_Afrikanda:__Guard( 1 )
+ZoneCapture_Afrikanda:Start( 30, 30 )
+env.info("[DEBUG] Afrikanda zone initialization complete")
+
+env.info("[DEBUG] Initializing Capture Zone: The Mountain")
+CaptureZone_The_Mountain = ZONE:New( "Capture The Mountain" )
+ZoneCapture_The_Mountain = ZONE_CAPTURE_COALITION:New( CaptureZone_The_Mountain, coalition.side.RED )
+-- SetMarkReadOnly method not available in this MOOSE version - feature disabled
+ZoneCapture_The_Mountain:__Guard( 1 )
+ZoneCapture_The_Mountain:Start( 30, 30 )
+env.info("[DEBUG] The Mountain zone initialization complete")
+
+env.info("[DEBUG] Initializing Capture Zone: The River")
+CaptureZone_The_River = ZONE:New( "Capture The River" )
+ZoneCapture_The_River = ZONE_CAPTURE_COALITION:New( CaptureZone_The_River, coalition.side.RED )
+-- SetMarkReadOnly method not available in this MOOSE version - feature disabled
+ZoneCapture_The_River:__Guard( 1 )
+ZoneCapture_The_River:Start( 30, 30 )
+env.info("[DEBUG] The River zone initialization complete")
+
+env.info("[DEBUG] Initializing Capture Zone: The Gulf")
+CaptureZone_The_Gulf = ZONE:New( "Capture The Gulf" )
+ZoneCapture_The_Gulf = ZONE_CAPTURE_COALITION:New( CaptureZone_The_Gulf, coalition.side.RED )
+-- SetMarkReadOnly method not available in this MOOSE version - feature disabled
+ZoneCapture_The_Gulf:__Guard( 1 )
+ZoneCapture_The_Gulf:Start( 30, 30 )
+env.info("[DEBUG] The Gulf zone initialization complete")
+
+
+
+-- Event handler functions - define them separately for each zone
+local function OnEnterGuarded(ZoneCapture, From, Event, To)
+ if From ~= To then
+ local Coalition = ZoneCapture:GetCoalition()
+ if Coalition == coalition.side.BLUE then
+ ZoneCapture:Smoke( SMOKECOLOR.Blue )
+ US_CC:MessageTypeToCoalition( string.format( "%s is under protection of the USA", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
+ RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of the USA", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
+ else
+ ZoneCapture:Smoke( SMOKECOLOR.Red )
+ RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of Russia", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
+ US_CC:MessageTypeToCoalition( string.format( "%s is under protection of Russia", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
+ end
+ end
+end
+
+local function OnEnterEmpty(ZoneCapture)
+ ZoneCapture:Smoke( SMOKECOLOR.Green )
+ US_CC:MessageTypeToCoalition( string.format( "%s is unprotected, and can be captured!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
+ RU_CC:MessageTypeToCoalition( string.format( "%s is unprotected, and can be captured!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
+end
+
+local function OnEnterAttacked(ZoneCapture)
+ ZoneCapture:Smoke( SMOKECOLOR.White )
+ local Coalition = ZoneCapture:GetCoalition()
+ if Coalition == coalition.side.BLUE then
+ US_CC:MessageTypeToCoalition( string.format( "%s is under attack by Russia", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
+ RU_CC:MessageTypeToCoalition( string.format( "We are attacking %s", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
+ else
+ RU_CC:MessageTypeToCoalition( string.format( "%s is under attack by the USA", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
+ US_CC:MessageTypeToCoalition( string.format( "We are attacking %s", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
+ end
+end
+
+local function OnEnterCaptured(ZoneCapture)
+ local Coalition = ZoneCapture:GetCoalition()
+ if Coalition == coalition.side.BLUE then
+ RU_CC:MessageTypeToCoalition( string.format( "%s is captured by the USA, we lost it!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
+ US_CC:MessageTypeToCoalition( string.format( "We captured %s, Excellent job!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
+ else
+ US_CC:MessageTypeToCoalition( string.format( "%s is captured by Russia, we lost it!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
+ RU_CC:MessageTypeToCoalition( string.format( "We captured %s, Excellent job!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information )
+ end
+
+ ZoneCapture:AddScore( "Captured", "Zone captured: Extra points granted.", 200 )
+ ZoneCapture:__Guard( 30 )
+end
+
+-- Apply event handlers to all zone capture objects
+local zoneCaptureObjects = {
+ ZoneCapture_Kilpyavr,
+ ZoneCapture_Severomorsk_1,
+ ZoneCapture_Severomorsk_3,
+ ZoneCapture_Murmansk_International,
+ ZoneCapture_Monchegorsk,
+ ZoneCapture_Olenya,
+ ZoneCapture_Afrikanda,
+ ZoneCapture_The_Mountain,
+ ZoneCapture_The_River,
+ ZoneCapture_The_Gulf
+}
+
+-- Set up event handlers for each zone with proper MOOSE methods and debugging
+local zoneNames = {
+ "Kilpyavr", "Severomorsk-1", "Severomorsk-3", "Murmansk International",
+ "Monchegorsk", "Olenya", "Afrikanda", "The Mountain", "The River", "The Gulf"
+}
+
+for i, zoneCapture in ipairs(zoneCaptureObjects) do
+ if zoneCapture then
+ local zoneName = zoneNames[i] or ("Zone " .. i)
+
+ -- Proper MOOSE event handlers for ZONE_CAPTURE_COALITION
+ zoneCapture.OnEnterGuarded = OnEnterGuarded
+ zoneCapture.OnEnterEmpty = OnEnterEmpty
+ zoneCapture.OnEnterAttacked = OnEnterAttacked
+ zoneCapture.OnEnterCaptured = OnEnterCaptured
+
+ -- Debug: Check if the underlying zone exists
+ local success, zone = pcall(function() return zoneCapture:GetZone() end)
+ if success and zone then
+ env.info("✓ Zone 'Capture " .. zoneName .. "' successfully created and linked")
+
+ -- Try to make zone borders visible with different approach
+ local drawSuccess, drawError = pcall(function()
+ zone:DrawZone(-1, {1, 0, 0}, 0.5, {1, 0, 0}, 0.2, 2, true)
+ end)
+
+ if not drawSuccess then
+ env.info("⚠ Zone 'Capture " .. zoneName .. "' border drawing failed: " .. tostring(drawError))
+ -- Alternative: Try simpler zone marking
+ pcall(function()
+ zone:SmokeZone(SMOKECOLOR.Red, 30)
+ end)
+ else
+ env.info("✓ Zone 'Capture " .. zoneName .. "' border drawn successfully")
+ end
+ else
+ env.info("✗ ERROR: Zone 'Capture " .. zoneName .. "' not found in mission editor!")
+ env.info(" Make sure you have a trigger zone named exactly: 'Capture " .. zoneName .. "'")
+ end
+ else
+ env.info("✗ ERROR: Zone capture object " .. i .. " (" .. (zoneNames[i] or "Unknown") .. ") is nil!")
+ end
+end
+
+-- Additional specific check for Olenya
+env.info("=== OLENYA SPECIFIC DEBUG ===")
+if ZoneCapture_Olenya then
+ env.info("✓ ZoneCapture_Olenya object exists")
+ local success, result = pcall(function() return ZoneCapture_Olenya:GetZoneName() end)
+ if success then
+ env.info("✓ Zone name: " .. tostring(result))
+ else
+ env.info("✗ Could not get zone name: " .. tostring(result))
+ end
+
+ local success2, zone = pcall(function() return ZoneCapture_Olenya:GetZone() end)
+ if success2 and zone then
+ env.info("✓ Underlying zone object exists")
+ local coord = zone:GetCoordinate()
+ if coord then
+ env.info("✓ Zone coordinate: " .. coord:ToStringLLDMS())
+ end
+ else
+ env.info("✗ Underlying zone object missing: " .. tostring(zone))
+ end
+else
+ env.info("✗ ZoneCapture_Olenya object is nil!")
+end
+
diff --git a/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield.lua b/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield.lua
new file mode 100644
index 0000000..06257d5
--- /dev/null
+++ b/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield.lua
@@ -0,0 +1,295 @@
+
+-- Operation Polar Shield Mission Script using MOOSE
+
+
+-- Set Spawn Limits - These limits can be adjusted to change the number of ground units that will spawn for each type.
+-- These are max units, not groups. For example, the manpad group in the mission editor is 2 units. So if MAX_RU_MANPADS = 10, then 5 groups of manpads will spawn.
+-- So you have to knnow how many units are in each group to set these limits effectively.
+
+MAX_RU_MANPADS = 10 -- Each group has 2 units, so 10 = 5 groups of 2.
+MAX_RU_AAA = 25 -- Each group has 1 units, so 25 = 25 groups of 1.
+MAX_RU_TANK_T90 = 10 -- The rest of these groups have 1 unit each.
+MAX_RU_TANK_T55 = 10
+MAX_RU_IFV = 35
+MAX_RU_IFV_Technicals = 45
+MAX_RU_SA08 = 5
+MAX_RU_SA19 = 5
+MAX_RU_SA15 = 5
+-- MAX_RU_ARTY = 10 -- Disabled lower regardless of this setting. Will fix later.
+
+
+-- Build Command Center and Mission for Blue Coalition
+local blueHQ = GROUP:FindByName("BLUEHQ")
+if blueHQ then
+ US_CC = COMMANDCENTER:New(blueHQ, "USA HQ")
+ US_Mission = MISSION:New(US_CC, "Operation Polar Hammer", "Primary", "", coalition.side.BLUE)
+ US_Score = SCORING:New("Operation Polar Hammer")
+ --US_Mission:AddScoring(US_Score)
+ --US_Mission:Start()
+ env.info("Blue Coalition Command Center and Mission started successfully")
+else
+ env.info("ERROR: BLUEHQ group not found! Blue mission will not start.")
+end
+
+--Build Command Center and Mission Red
+local redHQ = GROUP:FindByName("REDHQ")
+if redHQ then
+ RU_CC = COMMANDCENTER:New(redHQ, "Russia HQ")
+ RU_Mission = MISSION:New(RU_CC, "Operation Polar Shield", "Primary", "Destroy the City of Ushuaia and its supporting FARPS", coalition.side.RED)
+ --RU_Score = SCORING:New("Operation Polar Shield")
+ --RU_Mission:AddScoring(RU_Score)
+ RU_Mission:Start()
+ env.info("Red Coalition Command Center and Mission started successfully")
+else
+ env.info("ERROR: REDHQ group not found! Red mission will not start.")
+end
+
+-- Table of Zones to spread red ground forces randomly around.
+RandomSpawnZoneTable = {
+ ZONE:New("sp-1"), ZONE:New("sp-2"), ZONE:New("sp-3"), ZONE:New("sp-4"), ZONE:New("sp-5"),
+ ZONE:New("sp-6"), ZONE:New("sp-7"), ZONE:New("sp-8"), ZONE:New("sp-9"), ZONE:New("sp-10"),
+ ZONE:New("sp-11"), ZONE:New("sp-12"), ZONE:New("sp-13"), ZONE:New("sp-14"), ZONE:New("sp-15"),
+ ZONE:New("sp-16"), ZONE:New("sp-17"), ZONE:New("sp-18"), ZONE:New("sp-19"), ZONE:New("sp-20"),
+ ZONE:New("sp-21"), ZONE:New("sp-22"), ZONE:New("sp-23"), ZONE:New("sp-24"), ZONE:New("sp-25"),
+ ZONE:New("sp-26"), ZONE:New("sp-27"), ZONE:New("sp-28"), ZONE:New("sp-29"), ZONE:New("sp-30"),
+ ZONE:New("sp-31"), ZONE:New("sp-32"), ZONE:New("sp-33"), ZONE:New("sp-34"), ZONE:New("sp-35"),
+ ZONE:New("sp-36"), ZONE:New("sp-37"), ZONE:New("sp-38"), ZONE:New("sp-39"), ZONE:New("sp-40"),
+ ZONE:New("sp-41"), ZONE:New("sp-42"), ZONE:New("sp-43"), ZONE:New("sp-44"), ZONE:New("sp-45"),
+ ZONE:New("sp-46"), ZONE:New("sp-47"), ZONE:New("sp-48"), ZONE:New("sp-49"), ZONE:New("sp-50"),
+ ZONE:New("sp-51"), ZONE:New("sp-52"), ZONE:New("sp-53"), ZONE:New("sp-54"), ZONE:New("sp-55"),
+ ZONE:New("sp-56"), ZONE:New("sp-57"), ZONE:New("sp-58"), ZONE:New("sp-59"), ZONE:New("sp-60"),
+ ZONE:New("sp-61"), ZONE:New("sp-62"), ZONE:New("sp-63"), ZONE:New("sp-64"), ZONE:New("sp-65"),
+ ZONE:New("sp-66"), ZONE:New("sp-67"), ZONE:New("sp-68"), ZONE:New("sp-69"), ZONE:New("sp-70"),
+ ZONE:New("sp-71"), ZONE:New("sp-72"), ZONE:New("sp-73"), ZONE:New("sp-74"), ZONE:New("sp-75"),
+ ZONE:New("sp-76"), ZONE:New("sp-77"), ZONE:New("sp-78"), ZONE:New("sp-79"), ZONE:New("sp-80"),
+ ZONE:New("sp-81"), ZONE:New("sp-82"), ZONE:New("sp-83"), ZONE:New("sp-84"), ZONE:New("sp-85"),
+ ZONE:New("sp-86"), ZONE:New("sp-87"), ZONE:New("sp-88"), ZONE:New("sp-89"), ZONE:New("sp-90"),
+ ZONE:New("sp-91"), ZONE:New("sp-92"), ZONE:New("sp-93"), ZONE:New("sp-94"), ZONE:New("sp-95"),
+ ZONE:New("sp-96"), ZONE:New("sp-97"), ZONE:New("sp-98"), ZONE:New("sp-99"), ZONE:New("sp-100"),
+ ZONE:New("sp-101"), ZONE:New("sp-102"), ZONE:New("sp-103"), ZONE:New("sp-104"), ZONE:New("sp-105"),
+ ZONE:New("sp-106"), ZONE:New("sp-107"), ZONE:New("sp-108"), ZONE:New("sp-109"), ZONE:New("sp-110"),
+ ZONE:New("sp-111"), ZONE:New("sp-112"), ZONE:New("sp-113"), ZONE:New("sp-114"), ZONE:New("sp-115"),
+ ZONE:New("sp-116"), ZONE:New("sp-117"), ZONE:New("sp-118"), ZONE:New("sp-119"), ZONE:New("sp-120"),
+ ZONE:New("sp-121"), ZONE:New("sp-122"), ZONE:New("sp-123"), ZONE:New("sp-124"), ZONE:New("sp-125"),
+ ZONE:New("sp-126"), ZONE:New("sp-127"), ZONE:New("sp-128"), ZONE:New("sp-129"), ZONE:New("sp-130"),
+ ZONE:New("sp-131"), ZONE:New("sp-132"), ZONE:New("sp-133"), ZONE:New("sp-134"), ZONE:New("sp-135"),
+ ZONE:New("sp-136"), ZONE:New("sp-137"), ZONE:New("sp-138"), ZONE:New("sp-139"), ZONE:New("sp-140"),
+ ZONE:New("sp-141"), ZONE:New("sp-142"), ZONE:New("sp-143"), ZONE:New("sp-144"), ZONE:New("sp-145"),
+ ZONE:New("sp-146"), ZONE:New("sp-147"), ZONE:New("sp-148"), ZONE:New("sp-149"), ZONE:New("sp-150"),
+ ZONE:New("sp-151"), ZONE:New("sp-152"), ZONE:New("sp-153"), ZONE:New("sp-154"), ZONE:New("sp-155"),
+ ZONE:New("sp-156"), ZONE:New("sp-157"), ZONE:New("sp-158"), ZONE:New("sp-159"), ZONE:New("sp-160"),
+ ZONE:New("sp-161"), ZONE:New("sp-162"), ZONE:New("sp-163"), ZONE:New("sp-164"), ZONE:New("sp-165"),
+ ZONE:New("sp-166"), ZONE:New("sp-167"), ZONE:New("sp-168"), ZONE:New("sp-169"), ZONE:New("sp-170"),
+ ZONE:New("sp-171"), ZONE:New("sp-172"), ZONE:New("sp-173"), ZONE:New("sp-174"), ZONE:New("sp-175"),
+ ZONE:New("sp-176"), ZONE:New("sp-177"), ZONE:New("sp-178"), ZONE:New("sp-179"), ZONE:New("sp-180"),
+ ZONE:New("sp-181"), ZONE:New("sp-182"), ZONE:New("sp-183"), ZONE:New("sp-184"), ZONE:New("sp-185"),
+ ZONE:New("sp-186"), ZONE:New("sp-187"), ZONE:New("sp-188"), ZONE:New("sp-189"), ZONE:New("sp-190"),
+ ZONE:New("sp-191"), ZONE:New("sp-192"), ZONE:New("sp-193"), ZONE:New("sp-194"), ZONE:New("sp-195"),
+ ZONE:New("sp-196"), ZONE:New("sp-197"), ZONE:New("sp-198"), ZONE:New("sp-199"), ZONE:New("sp-200"),
+ ZONE:New("sp-201"), ZONE:New("sp-202"), ZONE:New("sp-203"), ZONE:New("sp-204"), ZONE:New("sp-205"),
+ ZONE:New("sp-206"), ZONE:New("sp-207"), ZONE:New("sp-208"), ZONE:New("sp-209"), ZONE:New("sp-210"),
+ ZONE:New("sp-211"), ZONE:New("sp-212"), ZONE:New("sp-213"), ZONE:New("sp-214"), ZONE:New("sp-215"),
+ ZONE:New("sp-216"), ZONE:New("sp-217"), ZONE:New("sp-218"), ZONE:New("sp-219"), ZONE:New("sp-220"),
+ ZONE:New("sp-221"), ZONE:New("sp-222"), ZONE:New("sp-223"), ZONE:New("sp-224"), ZONE:New("sp-225"),
+ ZONE:New("sp-226"), ZONE:New("sp-227"), ZONE:New("sp-228"), ZONE:New("sp-229"), ZONE:New("sp-230"),
+ ZONE:New("sp-231"), ZONE:New("sp-232"), ZONE:New("sp-233"), ZONE:New("sp-234"), ZONE:New("sp-235"),
+ ZONE:New("sp-236"), ZONE:New("sp-237"), ZONE:New("sp-238"), ZONE:New("sp-239"), ZONE:New("sp-240"),
+ ZONE:New("sp-241"), ZONE:New("sp-242"), ZONE:New("sp-243"), ZONE:New("sp-244"), ZONE:New("sp-245"),
+ ZONE:New("sp-246"), ZONE:New("sp-247"), ZONE:New("sp-248"), ZONE:New("sp-249"), ZONE:New("sp-250"),
+ ZONE:New("sp-251"), ZONE:New("sp-252"), ZONE:New("sp-253"), ZONE:New("sp-254"), ZONE:New("sp-255"),
+ ZONE:New("sp-256"), ZONE:New("sp-257"), ZONE:New("sp-258"), ZONE:New("sp-259"), ZONE:New("sp-260"),
+ ZONE:New("sp-261"), ZONE:New("sp-262"), ZONE:New("sp-263"), ZONE:New("sp-264"), ZONE:New("sp-265"),
+ ZONE:New("sp-266"), ZONE:New("sp-267"), ZONE:New("sp-268"), ZONE:New("sp-269"), ZONE:New("sp-270"),
+ ZONE:New("sp-271"), ZONE:New("sp-272"), ZONE:New("sp-273"), ZONE:New("sp-274"), ZONE:New("sp-275"),
+ ZONE:New("sp-276"), ZONE:New("sp-277"), ZONE:New("sp-278"), ZONE:New("sp-279"), ZONE:New("sp-280"),
+ ZONE:New("sp-281"), ZONE:New("sp-282"), ZONE:New("sp-283"), ZONE:New("sp-284"), ZONE:New("sp-285"),
+ ZONE:New("sp-286"), ZONE:New("sp-287"), ZONE:New("sp-288"), ZONE:New("sp-289"), ZONE:New("sp-290"),
+ ZONE:New("sp-291"), ZONE:New("sp-292"), ZONE:New("sp-293"), ZONE:New("sp-294"), ZONE:New("sp-295"),
+ ZONE:New("sp-296"), ZONE:New("sp-297"), ZONE:New("sp-298"), ZONE:New("sp-299"), ZONE:New("sp-300"),
+ ZONE:New("sp-301"), ZONE:New("sp-302"), ZONE:New("sp-303"), ZONE:New("sp-304"), ZONE:New("sp-305"),
+ ZONE:New("sp-306"), ZONE:New("sp-307"), ZONE:New("sp-308"), ZONE:New("sp-309"), ZONE:New("sp-310"),
+ ZONE:New("sp-311"), ZONE:New("sp-312"), ZONE:New("sp-313"), ZONE:New("sp-314"), ZONE:New("sp-315"),
+ ZONE:New("sp-316"), ZONE:New("sp-317"), ZONE:New("sp-318"), ZONE:New("sp-319"), ZONE:New("sp-320"),
+ ZONE:New("sp-321"), ZONE:New("sp-322"), ZONE:New("sp-323"), ZONE:New("sp-324"), ZONE:New("sp-325"),
+ ZONE:New("sp-326"), ZONE:New("sp-327"), ZONE:New("sp-328"), ZONE:New("sp-329"), ZONE:New("sp-330"),
+ ZONE:New("sp-331"), ZONE:New("sp-332"), ZONE:New("sp-333"), ZONE:New("sp-334"), ZONE:New("sp-335"),
+ ZONE:New("sp-336"), ZONE:New("sp-337"), ZONE:New("sp-338"), ZONE:New("sp-339"), ZONE:New("sp-340"),
+ ZONE:New("sp-341"), ZONE:New("sp-342"), ZONE:New("sp-343"), ZONE:New("sp-344"), ZONE:New("sp-345"),
+ ZONE:New("sp-346"), ZONE:New("sp-347"), ZONE:New("sp-348"), ZONE:New("sp-349"), ZONE:New("sp-350"),
+ ZONE:New("sp-351"), ZONE:New("sp-352"), ZONE:New("sp-353"), ZONE:New("sp-354"), ZONE:New("sp-355"),
+ ZONE:New("sp-356"), ZONE:New("sp-357"), ZONE:New("sp-358"), ZONE:New("sp-359"), ZONE:New("sp-360"),
+ ZONE:New("sp-361"), ZONE:New("sp-362"), ZONE:New("sp-363"), ZONE:New("sp-364"), ZONE:New("sp-365"),
+ ZONE:New("sp-366"), ZONE:New("sp-367"), ZONE:New("sp-368"), ZONE:New("sp-369"), ZONE:New("sp-370"),
+ ZONE:New("sp-371"), ZONE:New("sp-372"), ZONE:New("sp-373"), ZONE:New("sp-374"), ZONE:New("sp-375"),
+ ZONE:New("sp-376"), ZONE:New("sp-377"), ZONE:New("sp-378"), ZONE:New("sp-379"), ZONE:New("sp-380"),
+ ZONE:New("sp-381"), ZONE:New("sp-382"), ZONE:New("sp-383"), ZONE:New("sp-384"), ZONE:New("sp-385"),
+ ZONE:New("sp-386"), ZONE:New("sp-387"), ZONE:New("sp-388"), ZONE:New("sp-389"), ZONE:New("sp-390"),
+ ZONE:New("sp-391"), ZONE:New("sp-392"), ZONE:New("sp-393"), ZONE:New("sp-394"), ZONE:New("sp-395"),
+ ZONE:New("sp-396"), ZONE:New("sp-397"), ZONE:New("sp-398"), ZONE:New("sp-399"), ZONE:New("sp-400"),
+ ZONE:New("sp-401"), ZONE:New("sp-402"), ZONE:New("sp-403"), ZONE:New("sp-404"), ZONE:New("sp-405"),
+ ZONE:New("sp-406"), ZONE:New("sp-407"), ZONE:New("sp-408"), ZONE:New("sp-409"), ZONE:New("sp-410"),
+ ZONE:New("sp-411"), ZONE:New("sp-412"), ZONE:New("sp-413"), ZONE:New("sp-414"), ZONE:New("sp-415"),
+ ZONE:New("sp-416"), ZONE:New("sp-417"), ZONE:New("sp-418"), ZONE:New("sp-419"), ZONE:New("sp-420"),
+ ZONE:New("sp-421"), ZONE:New("sp-422"), ZONE:New("sp-423"), ZONE:New("sp-424"), ZONE:New("sp-425"),
+ ZONE:New("sp-426"), ZONE:New("sp-427"), ZONE:New("sp-428"), ZONE:New("sp-429"), ZONE:New("sp-430"),
+ ZONE:New("sp-431"), ZONE:New("sp-432"), ZONE:New("sp-433"), ZONE:New("sp-434"), ZONE:New("sp-435"),
+ ZONE:New("sp-436"), ZONE:New("sp-437"), ZONE:New("sp-438"), ZONE:New("sp-439"), ZONE:New("sp-440"),
+ ZONE:New("sp-441"), ZONE:New("sp-442"), ZONE:New("sp-443"), ZONE:New("sp-444"), ZONE:New("sp-445"),
+ ZONE:New("sp-446"), ZONE:New("sp-447"), ZONE:New("sp-448"), ZONE:New("sp-449"), ZONE:New("sp-450"),
+ ZONE:New("sp-451"), ZONE:New("sp-452"), ZONE:New("sp-453"), ZONE:New("sp-454"), ZONE:New("sp-455"),
+ ZONE:New("sp-456"), ZONE:New("sp-457"), ZONE:New("sp-458"), ZONE:New("sp-459"), ZONE:New("sp-460"),
+ ZONE:New("sp-461"), ZONE:New("sp-462"), ZONE:New("sp-463"), ZONE:New("sp-464"), ZONE:New("sp-465"),
+ ZONE:New("sp-466"), ZONE:New("sp-467"), ZONE:New("sp-468"), ZONE:New("sp-469"), ZONE:New("sp-470"),
+ ZONE:New("sp-471"), ZONE:New("sp-472"), ZONE:New("sp-473"), ZONE:New("sp-474"), ZONE:New("sp-475"),
+ ZONE:New("sp-476"), ZONE:New("sp-477"), ZONE:New("sp-478"), ZONE:New("sp-479"), ZONE:New("sp-480"),
+ ZONE:New("sp-481"), ZONE:New("sp-482"), ZONE:New("sp-483"), ZONE:New("sp-484"), ZONE:New("sp-485"),
+ ZONE:New("sp-486"), ZONE:New("sp-487"), ZONE:New("sp-488"), ZONE:New("sp-489"), ZONE:New("sp-490"),
+ ZONE:New("sp-491"), ZONE:New("sp-492"), ZONE:New("sp-493"), ZONE:New("sp-494"), ZONE:New("sp-495"),
+ ZONE:New("sp-496"), ZONE:New("sp-497"), ZONE:New("sp-498"), ZONE:New("sp-499"), ZONE:New("sp-500"),
+ ZONE:New("sp-501"), ZONE:New("sp-502"), ZONE:New("sp-503"), ZONE:New("sp-504"), ZONE:New("sp-505"),
+ ZONE:New("sp-506"), ZONE:New("sp-507"), ZONE:New("sp-508"), ZONE:New("sp-509"), ZONE:New("sp-510"),
+ ZONE:New("sp-511"), ZONE:New("sp-512"), ZONE:New("sp-513"), ZONE:New("sp-514"), ZONE:New("sp-515"),
+ ZONE:New("sp-516"), ZONE:New("sp-517"), ZONE:New("sp-518"), ZONE:New("sp-519"), ZONE:New("sp-520"),
+ ZONE:New("sp-521"), ZONE:New("sp-522"), ZONE:New("sp-523"), ZONE:New("sp-524"), ZONE:New("sp-525"),
+ ZONE:New("sp-526"), ZONE:New("sp-527"), ZONE:New("sp-528"), ZONE:New("sp-529"), ZONE:New("sp-530"),
+ ZONE:New("sp-531"), ZONE:New("sp-532"), ZONE:New("sp-533"), ZONE:New("sp-534"), ZONE:New("sp-535"),
+ ZONE:New("sp-536"), ZONE:New("sp-537"), ZONE:New("sp-538"), ZONE:New("sp-539"), ZONE:New("sp-540"),
+ ZONE:New("sp-541"), ZONE:New("sp-542"), ZONE:New("sp-543"), ZONE:New("sp-544"), ZONE:New("sp-545"),
+ ZONE:New("sp-546"), ZONE:New("sp-547"), ZONE:New("sp-548"), ZONE:New("sp-549"), ZONE:New("sp-550"),
+ ZONE:New("sp-551"), ZONE:New("sp-552"), ZONE:New("sp-553"), ZONE:New("sp-554"), ZONE:New("sp-555"),
+ ZONE:New("sp-556"), ZONE:New("sp-557"), ZONE:New("sp-558"), ZONE:New("sp-559"), ZONE:New("sp-560"),
+ ZONE:New("sp-561"), ZONE:New("sp-562"), ZONE:New("sp-563"), ZONE:New("sp-564"), ZONE:New("sp-565"),
+ ZONE:New("sp-566"), ZONE:New("sp-567"), ZONE:New("sp-568"), ZONE:New("sp-569"), ZONE:New("sp-570"),
+ ZONE:New("sp-571"), ZONE:New("sp-572"), ZONE:New("sp-573"), ZONE:New("sp-574"), ZONE:New("sp-575"),
+ ZONE:New("sp-576"), ZONE:New("sp-577"), ZONE:New("sp-578"), ZONE:New("sp-579"), ZONE:New("sp-580"),
+ ZONE:New("sp-581"), ZONE:New("sp-582"), ZONE:New("sp-583"), ZONE:New("sp-584"), ZONE:New("sp-585"),
+ ZONE:New("sp-586"), ZONE:New("sp-587"), ZONE:New("sp-588"), ZONE:New("sp-589"), ZONE:New("sp-590"),
+ ZONE:New("sp-591"), ZONE:New("sp-592"), ZONE:New("sp-593"), ZONE:New("sp-594"), ZONE:New("sp-595"),
+ ZONE:New("sp-596"), ZONE:New("sp-597"), ZONE:New("sp-598"), ZONE:New("sp-599"), ZONE:New("sp-600"),
+ ZONE:New("sp-601"), ZONE:New("sp-602"), ZONE:New("sp-603"), ZONE:New("sp-604"), ZONE:New("sp-605"),
+ ZONE:New("sp-606"), ZONE:New("sp-607"), ZONE:New("sp-608"), ZONE:New("sp-609"), ZONE:New("sp-610"),
+ ZONE:New("sp-611"), ZONE:New("sp-612"), ZONE:New("sp-613"), ZONE:New("sp-614"), ZONE:New("sp-615"),
+ ZONE:New("sp-616"), ZONE:New("sp-617"), ZONE:New("sp-618"), ZONE:New("sp-619"), ZONE:New("sp-620"),
+ ZONE:New("sp-621"), ZONE:New("sp-622"), ZONE:New("sp-623"), ZONE:New("sp-624"), ZONE:New("sp-625"),
+ ZONE:New("sp-626"), ZONE:New("sp-627"), ZONE:New("sp-628"), ZONE:New("sp-629"), ZONE:New("sp-630"),
+ ZONE:New("sp-631"), ZONE:New("sp-632"), ZONE:New("sp-633"), ZONE:New("sp-634"), ZONE:New("sp-635"),
+ ZONE:New("sp-636"), ZONE:New("sp-637"), ZONE:New("sp-638"), ZONE:New("sp-639"), ZONE:New("sp-640"),
+ ZONE:New("sp-641"), ZONE:New("sp-642"), ZONE:New("sp-643"), ZONE:New("sp-644"), ZONE:New("sp-645"),
+ ZONE:New("sp-646"), ZONE:New("sp-647"), ZONE:New("sp-648"), ZONE:New("sp-649"), ZONE:New("sp-650"),
+ ZONE:New("sp-651"), ZONE:New("sp-652"), ZONE:New("sp-653"), ZONE:New("sp-654"), ZONE:New("sp-655"),
+ ZONE:New("sp-656"), ZONE:New("sp-657"), ZONE:New("sp-658"), ZONE:New("sp-659"), ZONE:New("sp-660"),
+ ZONE:New("sp-661"), ZONE:New("sp-662"), ZONE:New("sp-663"), ZONE:New("sp-664"), ZONE:New("sp-665"),
+ ZONE:New("sp-666"), ZONE:New("sp-667"), ZONE:New("sp-668"), ZONE:New("sp-669"), ZONE:New("sp-670"),
+ ZONE:New("sp-671"), ZONE:New("sp-672"), ZONE:New("sp-673"), ZONE:New("sp-674"), ZONE:New("sp-675"),
+ ZONE:New("sp-676"), ZONE:New("sp-677"), ZONE:New("sp-678"), ZONE:New("sp-679"), ZONE:New("sp-680"),
+ ZONE:New("sp-681"), ZONE:New("sp-682"), ZONE:New("sp-683"), ZONE:New("sp-684"), ZONE:New("sp-685"),
+ ZONE:New("sp-686"), ZONE:New("sp-687"), ZONE:New("sp-688"), ZONE:New("sp-689"), ZONE:New("sp-690"),
+ ZONE:New("sp-691"), ZONE:New("sp-692"), ZONE:New("sp-693"), ZONE:New("sp-694"), ZONE:New("sp-695"),
+ ZONE:New("sp-696"), ZONE:New("sp-697"), ZONE:New("sp-698"), ZONE:New("sp-699"), ZONE:New("sp-700")
+}
+
+
+-- Spawn Ground Units for Red Coalition
+
+env.info("=== GROUND UNIT SPAWN DEBUG ===")
+env.info("MAX_RU_MANPADS: " .. MAX_RU_MANPADS)
+env.info("RandomSpawnZoneTable size: " .. #RandomSpawnZoneTable)
+
+-- Check if template groups exist
+local templateGroups = {
+ "RU_MANPADS-1", "RU_AAA-1", "RU_TANK_T90", "RU_TANK_T55",
+ "RU_IFV-1", "RU_IFV-Technicals", "RU_SA-08", "RU_SA-19", "RU_SA-15"
+}
+
+for _, templateName in pairs(templateGroups) do
+ local template = GROUP:FindByName(templateName)
+ if template then
+ env.info("✓ Found template: " .. templateName)
+ else
+ env.info("✗ Missing template: " .. templateName)
+ end
+end
+
+-- Check spawn zones
+local validZones = 0
+for i, zone in pairs(RandomSpawnZoneTable) do
+ if zone then
+ validZones = validZones + 1
+ else
+ env.info("✗ Invalid zone at index " .. i)
+ end
+end
+env.info("Valid spawn zones: " .. validZones .. "/" .. #RandomSpawnZoneTable)
+
+-- MANPADS Systems
+env.info("Spawning MANPADS...")
+RandomSpawns_RU_MANPADS = SPAWN:New( "RU_MANPADS-1" )
+:InitLimit( MAX_RU_MANPADS, MAX_RU_MANPADS )
+:InitRandomizeZones( RandomSpawnZoneTable )
+:SpawnScheduled( .1, .5 )
+
+-- Anti-Aircraft Artillery
+env.info("Spawning AAA...")
+RandomSpawns_RU_AAA = SPAWN:New( "RU_AAA-1" )
+:InitLimit( MAX_RU_AAA, MAX_RU_AAA )
+:InitRandomizeZones( RandomSpawnZoneTable )
+:SpawnScheduled( .1, .5 )
+
+-- Main Battle Tanks
+env.info("Spawning T-90 tanks...")
+RandomSpawns_RU_TANK_T90 = SPAWN:New( "RU_TANK_T90" )
+:InitLimit( MAX_RU_TANK_T90, MAX_RU_TANK_T90 )
+:InitRandomizeZones( RandomSpawnZoneTable )
+:SpawnScheduled( .1, .5 )
+
+env.info("Spawning T-55 tanks...")
+RandomSpawns_RU_TANK_T55 = SPAWN:New( "RU_TANK_T55" )
+:InitLimit( MAX_RU_TANK_T55, MAX_RU_TANK_T55 )
+:InitRandomizeZones( RandomSpawnZoneTable )
+:SpawnScheduled( .1, .5 )
+
+-- Infantry Fighting Vehicles
+env.info("Spawning IFVs...")
+RandomSpawns_RU_IFV = SPAWN:New( "RU_IFV-1" )
+:InitLimit( MAX_RU_IFV, MAX_RU_IFV )
+:InitRandomizeZones( RandomSpawnZoneTable )
+:SpawnScheduled( .1, .5 )
+
+env.info("Spawning Technical vehicles...")
+RandomSpawns_RU_IFV_Technicals = SPAWN:New( "RU_IFV-Technicals" )
+:InitLimit( MAX_RU_IFV_Technicals, MAX_RU_IFV_Technicals )
+:InitRandomizeZones( RandomSpawnZoneTable )
+:SpawnScheduled( .1, .5 )
+
+-- Short Range SAM Systems
+env.info("Spawning SA-08 SAMs...")
+RandomSpawns_RU_SA08 = SPAWN:New( "RU_SA-08" )
+:InitLimit( MAX_RU_SA08, MAX_RU_SA08 )
+:InitRandomizeZones( RandomSpawnZoneTable )
+:SpawnScheduled( .1, .5 )
+
+-- Medium Range SAM Systems
+env.info("Spawning SA-19 SAMs...")
+RandomSpawns_RU_SA19 = SPAWN:New( "RU_SA-19" )
+:InitLimit( MAX_RU_SA19, MAX_RU_SA19 )
+:InitRandomizeZones( RandomSpawnZoneTable )
+:SpawnScheduled( .1, .5 )
+
+-- Long Range SAM Systems
+env.info("Spawning SA-15 SAMs...")
+RandomSpawns_RU_SA15 = SPAWN:New( "RU_SA-15" )
+:InitLimit( MAX_RU_SA15, MAX_RU_SA15 )
+:InitRandomizeZones( RandomSpawnZoneTable )
+:SpawnScheduled( .1, .5 )
+
+
+-- Artillery Systems
+--[[
+RandomSpawns_RU_ARTY = SPAWN:New( "RU_ARTY-1" )
+:InitLimit( MAX_RU_ARTY, MAX_RU_ARTY )
+:InitRandomizeTemplate( RandomSpawnZoneTable )
+:InitRandomizeZones( RandomSpawnZoneTable )
+:SpawnScheduled( .1, .5 )
+--]]
+
+env.info("Red Ground Forces Spawned")
diff --git a/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield_TADC.lua b/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield_TADC.lua
new file mode 100644
index 0000000..aca812a
--- /dev/null
+++ b/DCS_Kola/Operation_Polar_Shield/Moose_OperationPolarShield_TADC.lua
@@ -0,0 +1,1449 @@
+-- Operation Polar Shield - MOOSE Mission Script
+--
+-- KNOWN ISSUES WITH EASYGCICAP v0.1.30:
+-- 1. Internal MOOSE errors during intercept assignments (_AssignIntercept)
+-- 2. Inconsistent wing size requests (alternates between 1 and 2 aircraft)
+-- 3. These errors do NOT prevent CAP operations - system works despite them
+--
+-- VERIFICATION: Check F10 map for active CAP flights patrolling border zones
+-- The script successfully initializes all squadrons and patrol points.
+
+-- STATUS: OPERATIONAL ✅
+-- All squadrons added, all patrol points configured, RedCAP started successfully
+--
+-- NEW FEATURES:
+-- ✅ Randomized AI Patrol System - Aircraft pick random patrol areas within their zones
+-- ✅ Anti-Clustering Behavior - Each aircraft patrols different areas to prevent clustering
+-- ✅ Dynamic Patrol Rotation - Aircraft change patrol areas every AI_PATROL_TIME seconds
+-- ✅ Configurable Patrol Parameters - Adjust patrol timing and area sizes via config
+
+-- Optional: Reduce MOOSE error message boxes in game
+-- env.setErrorMessageBoxEnabled(false)
+
+--================================================================================================
+-- TACTICAL AIR DEFENSE CONTROLLER (TADC) CONFIGURATION
+-- ================================================================================================
+
+-- GCI Configuration - Define at top level for global access
+local GCI_Config = {
+ -- Threat Response Parameters
+ threatRatio = 1, -- Send 1.5x defenders per attacker
+ maxSimultaneousCAP = 12, -- Maximum total airborne aircraft
+ reservePercent = 0.25, -- Keep 25% of forces in reserve
+ responseDelay = 15, -- Seconds to assess threat before scrambling
+
+ -- Supply Management
+ supplyMode = "INFINITE", -- "FINITE" or "INFINITE" aircraft spawning
+ defaultSquadronSize = 25, -- Aircraft per squadron if not specified
+
+ -- Detection Parameters
+ useEWRDetection = true, -- Use realistic EWR-based detection (true) or omniscient detection (false)
+ ewrDetectionRadius = 30000, -- EWR detection radius in meters (30km)
+ ewrUpdateInterval = 10, -- EWR detection update interval in seconds
+ threatTimeout = 300, -- Remove threats not seen for 5 minutes
+ minThreatDistance = 50000, -- Minimum 50km to consider threat
+
+ -- Force Sizing Rules
+ fighterVsFighter = 1.0, -- Multiplier for fighter threats (reduced from 1.5)
+ fighterVsBomber = 1.2, -- Multiplier for bomber threats (reduced from 2.0)
+ fighterVsHelicopter = 0.8, -- Multiplier for helicopter threats
+
+ -- CAP Behavior Parameters
+ capOrbitRadius = 30000, -- CAP orbit radius in meters (30km)
+ capEngagementRange = 35000, -- Maximum engagement range in meters (35km)
+ capZoneConstraint = true, -- Keep CAP flights within their patrol zones
+
+ -- AI Patrol Parameters
+ AI_PATROL_TIME = 300, -- Time aircraft patrol one area before moving (5 minutes)
+ patrolAreaRadius = 15000, -- Radius of individual patrol areas in meters (15km)
+ minPatrolSeparation = 25000, -- Minimum distance between patrol areas (25km)
+
+ -- Squadron Operational Parameters
+ squadronCooldown = 1800, -- Seconds between squadron launches (30 minutes - increased from 15)
+ maxAircraftPerMission = 1, -- Maximum aircraft per squadron per mission (reduced from 2)
+
+ -- Spawn Positioning
+ spawnDistanceMin = 1000, -- Minimum spawn distance from airbase (meters)
+ spawnDistanceMax = 3000, -- Maximum spawn distance from airbase (meters)
+ takeoffDistance = 5000, -- Distance for ground spawn takeoff positioning (meters)
+
+ -- Mission Timing
+ minPatrolDuration = 7200, -- Minimum patrol duration in seconds (2 hours)
+ rtbDuration = 600, -- Time allowed for RTB in seconds (10 minutes)
+ missionCleanupTime = 1800, -- Time before old missions are cleaned up (30 minutes)
+ statusReportInterval = 300, -- Interval between status reports (5 minutes)
+
+ -- System Timing
+ capSetupDelay = 5, -- Delay before setting up CAP tasks (seconds)
+ mainLoopDelay = 5, -- Initial delay before starting main loop (seconds)
+ mainLoopInterval = 30, -- Main loop execution interval (seconds)
+
+ -- Debug Options
+ debugLevel = 2, -- 0=Silent, 1=Basic, 2=Verbose
+
+ -- Persistent CAP Configuration
+ enablePersistentCAP = true, -- Enable continuous standing patrols
+ persistentCAPCount = 2, -- Number of persistent CAP flights to maintain
+ persistentCAPInterval = 900, -- Check/maintain persistent CAP every 15 minutes
+ persistentCAPReserve = 0.3, -- Reserve 30% of maxSimultaneousCAP slots for threat response
+ persistentCAPPriority = { -- Priority order for persistent CAP squadrons
+ "FIGHTER_SWEEP_RED_Severomorsk-1", -- Primary intercept base
+ "FIGHTER_SWEEP_RED_Olenya", -- Northern coverage
+ "FIGHTER_SWEEP_RED_Murmansk", -- Western coverage
+ "HELO_SWEEP_RED_Afrikanda" -- Helicopter patrol
+ },
+
+ -- Optional Features
+ initialStandingPatrols = false -- Launch standing patrols on startup (legacy)
+}
+
+
+-- Initialize TADC Data Structures - Must be defined before any usage
+local TADC = {
+ -- Squadron Management
+ squadrons = {}, -- Squadron data and status
+ activeCAPs = {}, -- Currently airborne CAP flights
+ launchQueue = {}, -- Pending launch orders
+
+ -- Threat Tracking
+ threats = {}, -- Detected threat contacts
+ threatHistory = {}, -- Historical threat data
+
+ -- Mission Control
+ missions = {}, -- Active intercept missions
+ reserves = {}, -- Aircraft held in reserve
+ threatAssignments = {}, -- Maps threat IDs to assigned squadrons
+ squadronMissions = {}, -- Maps squadrons to their current threats
+
+ -- Persistent CAP Management
+ persistentCAPs = {}, -- Currently active persistent CAP flights
+ lastPersistentCheck = 0, -- Last time persistent CAP was checked/maintained
+
+ -- Statistics
+ stats = {
+ threatsDetected = 0,
+ interceptsLaunched = 0,
+ successfulEngagements = 0,
+ aircraftLost = 0
+ }
+}
+
+-- Setup Distributed Multi-Base CAP System
+-- This creates a dynamic response system where each squadron launches from its designated airbase
+-- and responds to threats based on proximity and zone coverage.
+
+-- Create border zones with error checking
+local redBorderGroup = GROUP:FindByName("RED BORDER")
+local heloBorderGroup = GROUP:FindByName("HELO BORDER")
+
+if redBorderGroup then
+ CCCPBorderZone = ZONE_POLYGON:New("RED BORDER", redBorderGroup)
+ env.info("RED BORDER zone created successfully")
+else
+ env.info("ERROR: RED BORDER group not found!")
+ return
+end
+
+if heloBorderGroup then
+ HeloBorderZone = ZONE_POLYGON:New("HELO BORDER", heloBorderGroup)
+ env.info("HELO BORDER zone created successfully")
+else
+ env.info("ERROR: HELO BORDER group not found!")
+ return
+end
+
+-- Define squadron configurations with their designated airbases and patrol zones
+local squadronConfigs = {
+ -- Fixed-wing fighters patrol RED BORDER zone
+ {
+ templateName = "FIGHTER_SWEEP_RED_Kilpyavr",
+ displayName = "Kilpyavr CAP",
+ airbaseName = "Kilpyavr",
+ patrolZone = CCCPBorderZone,
+ aircraft = 1,
+ skill = AI.Skill.GOOD,
+ altitude = 15000,
+ speed = 300,
+ patrolTime = 20,
+ type = "FIGHTER"
+ },
+ {
+ templateName = "FIGHTER_SWEEP_RED_Severomorsk-1",
+ displayName = "Severomorsk-1 CAP",
+ airbaseName = "Severomorsk-1",
+ patrolZone = CCCPBorderZone,
+ aircraft = 1,
+ skill = AI.Skill.GOOD,
+ altitude = 20000,
+ speed = 350,
+ patrolTime = 25,
+ type = "FIGHTER"
+ },
+ {
+ templateName = "FIGHTER_SWEEP_RED_Severomorsk-3",
+ displayName = "Severomorsk-3 CAP",
+ airbaseName = "Severomorsk-3",
+ patrolZone = CCCPBorderZone,
+ aircraft = 1,
+ skill = AI.Skill.GOOD,
+ altitude = 25000,
+ speed = 400,
+ patrolTime = 30,
+ type = "FIGHTER"
+ },
+ {
+ templateName = "FIGHTER_SWEEP_RED_Murmansk",
+ displayName = "Murmansk CAP",
+ airbaseName = "Murmansk International",
+ patrolZone = CCCPBorderZone,
+ aircraft = 1,
+ skill = AI.Skill.GOOD,
+ altitude = 18000,
+ speed = 320,
+ patrolTime = 22,
+ type = "FIGHTER"
+ },
+ {
+ templateName = "FIGHTER_SWEEP_RED_Monchegorsk",
+ displayName = "Monchegorsk CAP",
+ airbaseName = "Monchegorsk",
+ patrolZone = CCCPBorderZone,
+ aircraft = 1,
+ skill = AI.Skill.GOOD,
+ altitude = 22000,
+ speed = 380,
+ patrolTime = 25,
+ type = "FIGHTER"
+ },
+ {
+ templateName = "FIGHTER_SWEEP_RED_Olenya",
+ displayName = "Olenya CAP",
+ airbaseName = "Olenya",
+ patrolZone = CCCPBorderZone,
+ aircraft = 1,
+ skill = AI.Skill.GOOD,
+ altitude = 30000,
+ speed = 450,
+ patrolTime = 35,
+ type = "FIGHTER"
+ },
+ -- Helicopter squadron patrols HELO BORDER zone
+ {
+ templateName = "HELO_SWEEP_RED_Afrikanda",
+ displayName = "Afrikanda Helo CAP",
+ airbaseName = "Afrikanda",
+ patrolZone = HeloBorderZone,
+ aircraft = 4,
+ skill = AI.Skill.GOOD,
+ altitude = 1000,
+ speed = 150,
+ patrolTime = 30,
+ type = "HELICOPTER"
+ }
+}
+
+-- Check which squadron templates exist in the mission
+env.info("=== CHECKING SQUADRON TEMPLATES ===")
+local availableSquadrons = {}
+
+-- First, let's verify what airbases are actually available
+env.info("=== VERIFYING AIRBASE NAMES ===")
+local testAirbaseNames = {
+ "Kilpyavr", "Severomorsk-1", "Severomorsk-3",
+ "Murmansk International", "Monchegorsk", "Olenya", "Afrikanda"
+}
+for _, airbaseName in pairs(testAirbaseNames) do
+ local airbaseObj = AIRBASE:FindByName(airbaseName)
+ if airbaseObj then
+ env.info("✓ Airbase found: " .. airbaseName)
+ else
+ env.info("✗ Airbase NOT found: " .. airbaseName)
+ end
+end
+
+-- ================================================================================================
+-- SQUADRON MANAGEMENT SYSTEM
+-- ================================================================================================
+
+local function initializeSquadron(config)
+ return {
+ -- Basic Info
+ templateName = config.templateName,
+ displayName = config.displayName,
+ airbaseName = config.airbaseName,
+ type = config.type,
+
+ -- Aircraft Management
+ totalAircraft = GCI_Config.supplyMode == "INFINITE" and 999 or (config.totalAircraft or GCI_Config.defaultSquadronSize),
+ availableAircraft = GCI_Config.supplyMode == "INFINITE" and 999 or (config.totalAircraft or GCI_Config.defaultSquadronSize),
+ airborneAircraft = 0,
+ reserveAircraft = 0,
+
+ -- Mission Parameters
+ patrolZone = config.patrolZone,
+ altitude = config.altitude,
+ speed = config.speed,
+ patrolTime = config.patrolTime,
+ skill = config.skill,
+
+ -- Status
+ readinessLevel = "READY", -- READY, BUSY, UNAVAILABLE
+ lastLaunch = -GCI_Config.squadronCooldown, -- Allow immediate launch (set to -cooldown)
+ launchCooldown = GCI_Config.squadronCooldown, -- Cooldown from config
+
+ -- Statistics
+ sorties = 0,
+ kills = 0,
+ losses = 0
+ }
+end
+
+env.info("=== INITIALIZING SQUADRON DATABASE ===")
+for _, config in pairs(squadronConfigs) do
+ local template = GROUP:FindByName(config.templateName)
+ if template then
+ env.info("✓ Found squadron template: " .. config.templateName)
+
+ -- Verify airbase exists and is Red coalition
+ local airbaseObj = AIRBASE:FindByName(config.airbaseName)
+ if airbaseObj then
+ local airbaseCoalition = airbaseObj:GetCoalition()
+ if airbaseCoalition == 1 then -- Red coalition
+ env.info(" ✓ Airbase verified: " .. config.airbaseName .. " (Red Coalition)")
+
+ -- Initialize squadron in TADC database
+ local squadron = initializeSquadron(config)
+ TADC.squadrons[config.templateName] = squadron
+ availableSquadrons[config.templateName] = config -- Keep for compatibility
+
+ env.info(" ✓ Squadron initialized: " .. squadron.availableAircraft .. " aircraft available")
+ else
+ env.info(" ✗ Airbase " .. config.airbaseName .. " not Red coalition - squadron disabled")
+ end
+ else
+ env.info(" ✗ Airbase NOT found: " .. config.airbaseName .. " - squadron disabled")
+ end
+ else
+ env.info("✗ Missing squadron template: " .. config.templateName)
+ end
+end
+
+local squadronCount = 0
+local totalAircraft = 0
+for _, squadron in pairs(TADC.squadrons) do
+ squadronCount = squadronCount + 1
+ totalAircraft = totalAircraft + squadron.availableAircraft
+end
+
+env.info("✓ TADC Squadron Database: " .. squadronCount .. " squadrons, " .. totalAircraft .. " total aircraft")
+if GCI_Config.supplyMode == "INFINITE" then
+ env.info("✓ Supply Mode: INFINITE - unlimited aircraft spawning")
+else
+ env.info("✓ Supply Mode: FINITE - " .. totalAircraft .. " aircraft available")
+end
+
+-- ================================================================================================
+-- TACTICAL AIR DEFENSE CONTROLLER (TADC) SYSTEM
+-- A comprehensive GCI system for intelligent air defense coordination
+-- ================================================================================================
+
+env.info("=== INITIALIZING TACTICAL AIR DEFENSE CONTROLLER ===")
+
+-- Create EWR Detection Network with Detection System
+local RedEWR = SET_GROUP:New():FilterPrefixes("RED-EWR"):FilterStart()
+local RedDetection = nil
+
+if GCI_Config.useEWRDetection and RedEWR:Count() > 0 then
+ env.info("✓ Red EWR Network: " .. RedEWR:Count() .. " detection groups")
+
+ -- Create MOOSE Detection system using EWR network (basic version for compatibility)
+ local success, errorMsg = pcall(function()
+ RedDetection = DETECTION_AREAS:New(RedEWR, GCI_Config.ewrDetectionRadius)
+ RedDetection:Start()
+ end)
+
+ if success then
+ env.info("✓ EWR-based threat detection system initialized (" .. (GCI_Config.ewrDetectionRadius/1000) .. "km range)")
+ else
+ env.info("⚠ EWR detection failed: " .. tostring(errorMsg) .. " - falling back to omniscient detection")
+ RedDetection = nil
+ end
+else
+ if GCI_Config.useEWRDetection then
+ env.info("⚠ Warning: No RED-EWR groups found - falling back to omniscient detection")
+ else
+ env.info("✓ Using omniscient detection (EWR detection disabled in config)")
+ end
+end
+
+-- ================================================================================================
+-- THREAT DETECTION AND ASSESSMENT SYSTEM
+-- ================================================================================================
+
+local function classifyThreat(group)
+ local category = group:GetCategory()
+ local typeName = group:GetTypeName() or "Unknown"
+
+ -- Classify by DCS category and type name
+ if category == Group.Category.AIRPLANE then
+ if string.find(typeName:upper(), "B-") or string.find(typeName:upper(), "BOMBER") then
+ return "BOMBER"
+ elseif string.find(typeName:upper(), "A-") or string.find(typeName:upper(), "ATTACK") then
+ return "ATTACK"
+ else
+ return "FIGHTER"
+ end
+ elseif category == Group.Category.HELICOPTER then
+ return "HELICOPTER"
+ else
+ return "UNKNOWN"
+ end
+end
+
+local function assessThreatStrength(threats)
+ local fighters = 0
+ local bombers = 0
+ local helicopters = 0
+ local totalThreat = 0
+
+ for _, threat in pairs(threats) do
+ local aircraft = threat.size or 1
+ if threat.classification == "FIGHTER" then
+ fighters = fighters + aircraft
+ totalThreat = totalThreat + (aircraft * GCI_Config.fighterVsFighter)
+ elseif threat.classification == "BOMBER" then
+ bombers = bombers + aircraft
+ totalThreat = totalThreat + (aircraft * GCI_Config.fighterVsBomber)
+ elseif threat.classification == "HELICOPTER" then
+ helicopters = helicopters + aircraft
+ totalThreat = totalThreat + (aircraft * GCI_Config.fighterVsHelicopter)
+ end
+ end
+
+ return {
+ fighters = fighters,
+ bombers = bombers,
+ helicopters = helicopters,
+ totalAircraft = fighters + bombers + helicopters,
+ requiredDefenders = math.ceil(totalThreat)
+ }
+end
+
+local function updateThreatPicture()
+ local currentTime = timer.getTime()
+ local newThreats = {}
+
+ -- Use EWR-based detection if enabled and available, otherwise fall back to omniscient detection
+ if GCI_Config.useEWRDetection and RedDetection then
+ -- EWR-based realistic detection
+ local detectedItems = RedDetection:GetDetectedItems()
+
+ if GCI_Config.debugLevel >= 2 then
+ env.info("EWR DETECTION: Found " .. #detectedItems .. " detected items")
+ end
+
+ for _, detectedItem in pairs(detectedItems) do
+ local detectedSet = detectedItem.Set
+ if detectedSet then
+ detectedSet:ForEach(function(detectedGroup)
+ if detectedGroup and detectedGroup:IsAlive() and detectedGroup:GetCoalition() == coalition.side.BLUE then
+ local blueCoord = detectedGroup:GetCoordinate()
+ local threatId = detectedGroup:GetName()
+
+ -- DEBUG: Log each EWR-detected aircraft
+ if GCI_Config.debugLevel >= 2 then
+ env.info("EWR DETECTED: " .. threatId .. " at " .. blueCoord:ToStringLLDMS())
+ end
+
+ -- Check if threat is in any patrol zone
+ local inRedZone = CCCPBorderZone and CCCPBorderZone:IsCoordinateInZone(blueCoord)
+ local inHeloZone = HeloBorderZone and HeloBorderZone:IsCoordinateInZone(blueCoord)
+
+ -- DEBUG: Log zone check results
+ if GCI_Config.debugLevel >= 2 then
+ env.info(" Zone Check - RED: " .. tostring(inRedZone) .. ", HELO: " .. tostring(inHeloZone))
+ end
+
+ if inRedZone or inHeloZone then
+ local classification = classifyThreat(detectedGroup)
+ local size = detectedGroup:GetSize()
+
+ newThreats[threatId] = {
+ id = threatId,
+ group = detectedGroup,
+ coordinate = blueCoord,
+ classification = classification,
+ size = size,
+ zone = inRedZone and "RED_BORDER" or "HELO_BORDER",
+ firstDetected = TADC.threats[threatId] and TADC.threats[threatId].firstDetected or currentTime,
+ lastSeen = currentTime,
+ heading = detectedGroup:GetHeading(),
+ velocity = detectedGroup:GetVelocity(),
+ priority = classification == "BOMBER" and 3 or (classification == "FIGHTER" and 2 or 1),
+ detectionMethod = "EWR"
+ }
+
+ -- Update statistics
+ if not TADC.threats[threatId] then
+ TADC.stats.threatsDetected = TADC.stats.threatsDetected + 1
+ if GCI_Config.debugLevel >= 1 then
+ env.info("NEW EWR THREAT: " .. threatId .. " (" .. classification .. ", " .. size .. " aircraft) in " .. newThreats[threatId].zone)
+ end
+ end
+ end
+ end
+ end)
+ end
+ end
+ else
+ -- Fallback: Omniscient detection (original method)
+ local BlueAircraft = SET_GROUP:New():FilterCoalitions("blue"):FilterCategoryAirplane():FilterStart()
+
+ -- DEBUG: Log how many blue aircraft we found
+ local blueCount = BlueAircraft:Count()
+ if blueCount > 0 and GCI_Config.debugLevel >= 2 then
+ env.info("OMNISCIENT SCAN: Found " .. blueCount .. " blue aircraft on map")
+ end
+
+ BlueAircraft:ForEach(function(blueGroup)
+ if blueGroup and blueGroup:IsAlive() then
+ local blueCoord = blueGroup:GetCoordinate()
+ local threatId = blueGroup:GetName()
+
+ -- DEBUG: Log each blue aircraft found
+ if GCI_Config.debugLevel >= 2 then
+ env.info("CHECKING BLUE AIRCRAFT: " .. threatId .. " at " .. blueCoord:ToStringLLDMS())
+ end
+
+ -- Check if threat is in any patrol zone
+ local inRedZone = CCCPBorderZone and CCCPBorderZone:IsCoordinateInZone(blueCoord)
+ local inHeloZone = HeloBorderZone and HeloBorderZone:IsCoordinateInZone(blueCoord)
+
+ -- DEBUG: Log zone check results
+ if GCI_Config.debugLevel >= 2 then
+ env.info(" Zone Check - RED: " .. tostring(inRedZone) .. ", HELO: " .. tostring(inHeloZone))
+ end
+
+ if inRedZone or inHeloZone then
+ local classification = classifyThreat(blueGroup)
+ local size = blueGroup:GetSize()
+
+ newThreats[threatId] = {
+ id = threatId,
+ group = blueGroup,
+ coordinate = blueCoord,
+ classification = classification,
+ size = size,
+ zone = inRedZone and "RED_BORDER" or "HELO_BORDER",
+ firstDetected = TADC.threats[threatId] and TADC.threats[threatId].firstDetected or currentTime,
+ lastSeen = currentTime,
+ heading = blueGroup:GetHeading(),
+ velocity = blueGroup:GetVelocity(),
+ priority = classification == "BOMBER" and 3 or (classification == "FIGHTER" and 2 or 1),
+ detectionMethod = "OMNISCIENT"
+ }
+
+ -- Update statistics
+ if not TADC.threats[threatId] then
+ TADC.stats.threatsDetected = TADC.stats.threatsDetected + 1
+ if GCI_Config.debugLevel >= 1 then
+ env.info("NEW THREAT: " .. threatId .. " (" .. classification .. ", " .. size .. " aircraft) in " .. newThreats[threatId].zone)
+ end
+ end
+ end
+ end
+ end)
+ end
+
+ -- Remove old threats (not seen for threatTimeout seconds)
+ for threatId, threat in pairs(TADC.threats) do
+ if not newThreats[threatId] and (currentTime - threat.lastSeen) > GCI_Config.threatTimeout then
+ if GCI_Config.debugLevel >= 1 then
+ env.info("THREAT TIMEOUT: " .. threatId .. " - removing from threat picture")
+ end
+ end
+ end
+
+ TADC.threats = newThreats
+ return newThreats
+end
+
+local function findOptimalDefenders(threats, zone)
+ local assessment = assessThreatStrength(threats)
+ local availableSquadrons = {}
+ local totalAvailable = 0
+
+ -- Find available squadrons for this zone
+ local currentTime = timer.getTime()
+ for templateName, squadron in pairs(TADC.squadrons) do
+ -- Check availability, aircraft count, AND cooldown status
+ local cooldownRemaining = squadron.launchCooldown - (currentTime - squadron.lastLaunch)
+ if squadron.readinessLevel == "READY" and
+ squadron.availableAircraft > 0 and
+ cooldownRemaining <= 0 then
+ -- Check if squadron type matches zone
+ local canRespond = false
+ if zone == "RED_BORDER" and squadron.type == "FIGHTER" then
+ canRespond = true
+ elseif zone == "HELO_BORDER" and squadron.type == "HELICOPTER" then
+ canRespond = true
+ end
+
+ if canRespond then
+ local airbaseObj = AIRBASE:FindByName(squadron.airbaseName)
+ if airbaseObj then
+ -- Calculate average distance to threats
+ local totalDistance = 0
+ local threatCount = 0
+ for _, threat in pairs(threats) do
+ if threat.zone == zone then
+ local distance = threat.coordinate:Get2DDistance(airbaseObj:GetCoordinate())
+ totalDistance = totalDistance + distance
+ threatCount = threatCount + 1
+ end
+ end
+
+ if threatCount > 0 then
+ availableSquadrons[templateName] = {
+ squadron = squadron,
+ averageDistance = totalDistance / threatCount,
+ priority = squadron.type == "FIGHTER" and 2 or 1
+ }
+ totalAvailable = totalAvailable + squadron.availableAircraft
+ end
+ end
+ end
+ end
+ end
+
+ -- Sort by distance and priority
+ local sortedSquadrons = {}
+ for templateName, data in pairs(availableSquadrons) do
+ table.insert(sortedSquadrons, {templateName = templateName, data = data})
+ end
+
+ table.sort(sortedSquadrons, function(a, b)
+ if a.data.priority ~= b.data.priority then
+ return a.data.priority > b.data.priority
+ else
+ return a.data.averageDistance < b.data.averageDistance
+ end
+ end)
+
+ return sortedSquadrons, assessment, totalAvailable
+end
+
+-- ================================================================================================
+-- MISSION PLANNING AND LAUNCH COORDINATION
+-- ================================================================================================
+
+local function calculateRequiredForce(threats, zone)
+ local zoneThreats = {}
+ for _, threat in pairs(threats) do
+ if threat.zone == zone then
+ table.insert(zoneThreats, threat)
+ end
+ end
+
+ if #zoneThreats == 0 then
+ return 0, {}
+ end
+
+ local assessment = assessThreatStrength(zoneThreats)
+ return assessment.requiredDefenders, zoneThreats
+end
+
+local function assignThreatsToSquadrons(threats, zone)
+ local zoneThreats = {}
+ for _, threat in pairs(threats) do
+ if threat.zone == zone then
+ table.insert(zoneThreats, threat)
+ end
+ end
+
+ if #zoneThreats == 0 then
+ return {}
+ end
+
+ local availableSquadrons, assessment, totalAvailable = findOptimalDefenders(threats, zone)
+
+ -- Debug: Show squadron selection results
+ if GCI_Config.debugLevel >= 1 then
+ env.info("Squadron Selection for " .. zone .. ":")
+ for i, squadronData in pairs(availableSquadrons) do
+ local squadron = squadronData.data.squadron
+ local currentTime = timer.getTime()
+ local cooldownRemaining = math.max(0, squadron.launchCooldown - (currentTime - squadron.lastLaunch))
+ env.info(" " .. i .. ". " .. squadron.displayName .. " - Available: " .. squadron.availableAircraft .. ", Cooldown: " .. math.ceil(cooldownRemaining) .. "s")
+ end
+ end
+
+ -- Assign each unassigned threat to the best available squadron
+ local newAssignments = {}
+
+ for _, threat in pairs(zoneThreats) do
+ local threatId = threat.id .. "_" .. threat.firstDetected
+
+ -- Skip if threat is already assigned to an active squadron
+ local skipThreat = false
+ if TADC.threatAssignments[threatId] then
+ local assignedSquadron = TADC.threatAssignments[threatId]
+ local squadron = TADC.squadrons[assignedSquadron]
+ if squadron and squadron.readinessLevel == "BUSY" then
+ if GCI_Config.debugLevel >= 2 then
+ env.info("Threat " .. threat.id .. " already assigned to " .. assignedSquadron)
+ end
+ skipThreat = true -- Skip this threat, it's being handled
+ else
+ -- Squadron is no longer busy, clear the assignment
+ TADC.threatAssignments[threatId] = nil
+ if TADC.squadronMissions[assignedSquadron] then
+ TADC.squadronMissions[assignedSquadron] = nil
+ end
+ end
+ end
+
+ -- Only process threat if not skipped
+ if not skipThreat then
+ -- Find the best available squadron for this specific threat
+ local bestSquadron = nil
+ local bestDistance = math.huge
+
+ for _, squadronData in pairs(availableSquadrons) do
+ local squadron = squadronData.data.squadron
+ local templateName = squadronData.templateName
+
+ -- Check if squadron is available (not already assigned to another threat)
+ if not TADC.squadronMissions[templateName] and squadron.availableAircraft > 0 then
+ if squadronData.data.averageDistance < bestDistance then
+ bestDistance = squadronData.data.averageDistance
+ -- Find the original squadron config
+ local originalConfig = nil
+ for _, config in pairs(squadronConfigs) do
+ if config.templateName == templateName then
+ originalConfig = config
+ break
+ end
+ end
+
+ bestSquadron = {
+ templateName = templateName,
+ squadron = squadron,
+ distance = squadronData.data.averageDistance,
+ threat = threat,
+ threatId = threatId,
+ config = originalConfig -- Include original squadron config
+ }
+ end
+ end
+ end
+
+ if bestSquadron then
+ table.insert(newAssignments, bestSquadron)
+ -- Mark squadron as assigned to prevent double-booking
+ TADC.squadronMissions[bestSquadron.templateName] = bestSquadron.threatId
+ TADC.threatAssignments[bestSquadron.threatId] = bestSquadron.templateName
+
+ if GCI_Config.debugLevel >= 1 then
+ env.info("Assigning threat " .. threat.id .. " to " .. bestSquadron.squadron.displayName .. " (" .. math.floor(bestDistance/1000) .. "km)")
+ end
+ else
+ if GCI_Config.debugLevel >= 1 then
+ env.info("No available squadron for threat " .. threat.id)
+ end
+ end
+ end
+ end
+
+ return newAssignments
+end
+
+-- ================================================================================================
+-- CAP LAUNCH FUNCTION (Must be defined before executeInterceptMission)
+-- ================================================================================================
+
+local function launchCAP(config, aircraftCount, reason)
+ aircraftCount = aircraftCount or 1
+
+ env.info("=== LAUNCHING CAP ===")
+ env.info("Squadron: " .. config.displayName)
+ env.info("Airbase: " .. config.airbaseName)
+ env.info("Aircraft: " .. aircraftCount)
+ env.info("Reason: " .. reason)
+
+ local success, errorMsg = pcall(function()
+ -- Find the airbase object
+ local airbaseObj = AIRBASE:FindByName(config.airbaseName)
+ if not airbaseObj then
+ env.info("✗ Could not find airbase: " .. config.airbaseName)
+ return
+ end
+
+ env.info("✓ Airbase object found, attempting spawn...")
+ env.info("Template: " .. config.templateName)
+ env.info("Aircraft count: " .. config.aircraft)
+ env.info("Skill: " .. tostring(config.skill))
+
+ -- Check if template exists
+ local templateGroup = GROUP:FindByName(config.templateName)
+ if not templateGroup then
+ env.info("✗ CRITICAL: Template group not found: " .. config.templateName)
+ env.info("SOLUTION: In Mission Editor, ensure group '" .. config.templateName .. "' exists and is set to 'Late Activation'")
+ return
+ end
+
+ -- Check template group properties
+ local coalition = templateGroup:GetCoalition()
+ local coalitionName = coalition == 1 and "Red" or (coalition == 2 and "Blue" or "Neutral")
+ env.info("✓ Template group found - Coalition: " .. coalitionName)
+
+ if coalition ~= 1 then
+ env.info("✗ CRITICAL: Template group is not Red coalition (coalition=" .. coalition .. ")")
+ env.info("SOLUTION: In Mission Editor, set group '" .. config.templateName .. "' to Red coalition")
+ return
+ end
+
+ -- Template groups should NOT be alive if Late Activation is working correctly
+ local isAlive = templateGroup:IsAlive()
+ env.info("Template Status - Alive: " .. tostring(isAlive) .. " (should be false for Late Activation)")
+
+ if isAlive then
+ env.info("⚠ Warning: Template group is alive - Late Activation may not be set correctly")
+ env.info("This means the group has already spawned in the mission")
+ else
+ env.info("✓ Template group correctly set to Late Activation")
+ end
+
+ -- Create SPAWN object with proper initialization
+ env.info("Creating SPAWN object...")
+ local spawner = SPAWN:New(config.templateName)
+
+
+ -- Try different spawn methods that actually work
+ local spawnedGroup = nil
+
+ -- Method 1: Try spawning in air at proper altitude near airbase
+ local airbaseCoord = airbaseObj:GetCoordinate()
+ local spawnCoord = airbaseCoord:Translate(math.random(GCI_Config.spawnDistanceMin, GCI_Config.spawnDistanceMax), math.random(0, 360))
+ spawnCoord = spawnCoord:SetAltitude(config.altitude) -- Set proper altitude
+
+ env.info("Attempting air spawn at " .. config.altitude .. "ft near " .. config.airbaseName)
+ spawnedGroup = spawner:SpawnFromCoordinate(spawnCoord, nil, SPAWN.Takeoff.Air)
+
+ if not spawnedGroup then
+ -- Method 2: Try airbase spawn
+ env.info("Air spawn failed, trying airbase spawn")
+ spawnedGroup = spawner:SpawnAtAirbase(airbaseObj, SPAWN.Takeoff.Hot)
+ end
+
+ if not spawnedGroup then
+ -- Method 3: Try ground spawn then immediate takeoff
+ env.info("Airbase spawn failed, trying ground spawn with takeoff")
+ spawnedGroup = spawner:SpawnFromCoordinate(airbaseCoord)
+ if spawnedGroup then
+ -- Force immediate takeoff to proper altitude
+ local takeoffCoord = airbaseCoord:Translate(5000, math.random(0, 360)):SetAltitude(config.altitude)
+ spawnedGroup:RouteAirTo(takeoffCoord, config.speed, "BARO")
+ end
+ end
+
+ if spawnedGroup then
+ env.info("✓ Aircraft spawned successfully: " .. config.displayName)
+ -- Note: Skip immediate altitude check as coordinates may not be ready yet
+ -- Altitude will be set properly in the scheduled CAP setup task
+
+ -- Wait a moment then set up proper CAP mission
+ SCHEDULER:New(nil, function()
+ if spawnedGroup and spawnedGroup:IsAlive() then
+ env.info("Setting up CAP mission for " .. config.displayName)
+
+ -- Set proper altitude and speed (with nil checks)
+ local currentCoord = spawnedGroup:GetCoordinate()
+ if currentCoord then
+ local properAltCoord = currentCoord:SetAltitude(config.altitude)
+ spawnedGroup:RouteAirTo(properAltCoord, config.speed, "BARO")
+ env.info("✓ Set altitude to " .. config.altitude .. "ft")
+ else
+ env.info("⚠ Coordinate not ready yet, CAP task will handle altitude")
+ end
+
+ -- Set up comprehensive AI options with error handling
+ local success, errorMsg = pcall(function()
+ spawnedGroup:OptionROEOpenFire() -- Engage enemies
+ spawnedGroup:OptionRTBBingoFuel() -- RTB when low fuel
+ spawnedGroup:OptionRTBAmmo(0.1) -- RTB when 10% ammo left
+
+ -- Helicopter-specific AI options to prevent oscillation
+ if config.type == "HELICOPTER" then
+ spawnedGroup:OptionROTNoReaction() -- Less aggressive altitude changes
+ else
+ spawnedGroup:OptionROTVertical() -- No altitude restrictions for fighters
+ end
+ end)
+
+ if not success then
+ env.info("⚠ Warning: Could not set all AI options: " .. tostring(errorMsg))
+ end
+
+ -- Create randomized patrol system to prevent clustering
+ local function setupRandomPatrol()
+ if spawnedGroup and spawnedGroup:IsAlive() and config.patrolZone then
+ -- Get a random point within the patrol zone
+ local patrolZoneCoord = config.patrolZone:GetCoordinate()
+ if patrolZoneCoord then
+ -- Generate random patrol point within zone boundaries
+ local maxRadius = math.min(GCI_Config.capOrbitRadius, 25000) -- Max 25km from zone center
+ local randomRadius = math.random(GCI_Config.minPatrolSeparation, maxRadius)
+ local randomBearing = math.random(0, 360)
+
+ local patrolPoint = patrolZoneCoord:Translate(randomRadius, randomBearing)
+ patrolPoint = patrolPoint:SetAltitude(config.altitude * 0.3048) -- Convert to meters
+
+ env.info("Setting new patrol area for " .. config.displayName .. " at " .. randomRadius .. "m/" .. randomBearing .. "°")
+
+ -- Clear old tasks and set new patrol orbit
+ spawnedGroup:ClearTasks()
+
+ -- Primary orbit task at random location (helicopter-specific parameters)
+ local orbitTask = {}
+ if config.type == "HELICOPTER" then
+ -- Helicopter-specific orbit to prevent oscillation
+ orbitTask = {
+ id = 'Orbit',
+ params = {
+ pattern = 'Race-Track', -- More stable for helicopters
+ point = {x = patrolPoint.x, y = patrolPoint.z},
+ radius = math.min(GCI_Config.patrolAreaRadius, 8000), -- Smaller radius for helos
+ altitude = config.altitude * 0.3048,
+ speed = config.speed * 0.514444,
+ altitudeEdited = true -- Lock altitude to prevent oscillation
+ }
+ }
+ else
+ -- Standard fighter orbit
+ orbitTask = {
+ id = 'Orbit',
+ params = {
+ pattern = 'Circle',
+ point = {x = patrolPoint.x, y = patrolPoint.z},
+ radius = GCI_Config.patrolAreaRadius,
+ altitude = config.altitude * 0.3048,
+ speed = config.speed * 0.514444,
+ }
+ }
+ end
+ spawnedGroup:PushTask(orbitTask, 1)
+
+ -- Secondary engage task (always active)
+ local engageTask = {
+ id = 'EngageTargets',
+ params = {
+ targetTypes = {'Air'},
+ priority = 2,
+ maxDistEnabled = true,
+ maxDist = GCI_Config.capEngagementRange,
+ }
+ }
+ spawnedGroup:PushTask(engageTask, 2)
+
+ env.info("✓ " .. config.displayName .. " assigned to patrol area " .. randomRadius .. "m from zone center")
+ end
+ end
+ end
+
+ -- Set up initial patrol area
+ local capSuccess, capError = pcall(function()
+ setupRandomPatrol()
+
+ -- Schedule patrol area changes every AI_PATROL_TIME seconds
+ if TADC.activeCAPs[config.templateName] then
+ TADC.activeCAPs[config.templateName].patrolScheduler = SCHEDULER:New(nil, setupRandomPatrol, {}, GCI_Config.AI_PATROL_TIME, GCI_Config.AI_PATROL_TIME)
+ end
+ end)
+
+ if not capSuccess then
+ env.info("⚠ Warning: Could not set CAP tasks: " .. tostring(capError))
+ -- Fallback: just set basic engage task
+ spawnedGroup:OptionROEOpenFire()
+ end
+
+ env.info("✓ CAP mission established at " .. config.altitude .. "ft altitude")
+
+ end
+ end, {}, 5) -- 5 second delay to let aircraft stabilize
+
+ -- Mark as active
+ TADC.activeCAPs[config.templateName] = {
+ group = spawnedGroup,
+ launchTime = timer.getTime(),
+ config = config
+ }
+
+ -- Set up extended patrol timer (much longer than before)
+ local patrolDuration = math.max(config.patrolTime * 60, GCI_Config.minPatrolDuration) -- Minimum from config
+ SCHEDULER:New(nil, function()
+ if TADC.activeCAPs[config.templateName] then
+ env.info(config.displayName .. " completing patrol mission - RTB")
+ local group = TADC.activeCAPs[config.templateName].group
+ if group and group:IsAlive() then
+ -- Clear current tasks
+ group:ClearTasks()
+
+ -- Send back to base
+ local airbaseObj = AIRBASE:FindByName(config.airbaseName)
+ if airbaseObj then
+ group:RouteRTB(airbaseObj)
+ env.info("✓ " .. config.displayName .. " returning to " .. config.airbaseName)
+ end
+
+ -- Clean up after RTB delay
+ SCHEDULER:New(nil, function()
+ if TADC.activeCAPs[config.templateName] then
+ local capData = TADC.activeCAPs[config.templateName]
+ local rtbGroup = capData.group
+
+ -- Stop patrol scheduler
+ if capData.patrolScheduler then
+ capData.patrolScheduler:Stop()
+ end
+
+ if rtbGroup and rtbGroup:IsAlive() then
+ rtbGroup:Destroy()
+ env.info("✓ " .. config.displayName .. " landed and available for next sortie")
+ end
+ TADC.activeCAPs[config.templateName] = nil
+ end
+ end, {}, GCI_Config.rtbDuration) -- RTB time from config
+ else
+ -- Stop patrol scheduler if CAP is being cleaned up early
+ if TADC.activeCAPs[config.templateName] and TADC.activeCAPs[config.templateName].patrolScheduler then
+ TADC.activeCAPs[config.templateName].patrolScheduler:Stop()
+ end
+ TADC.activeCAPs[config.templateName] = nil
+ end
+ end
+ end, {}, patrolDuration)
+
+ else
+ env.info("✗ Failed to spawn " .. config.displayName)
+ end
+ end)
+
+ if not success then
+ env.info("✗ Error launching CAP: " .. tostring(errorMsg))
+ return false
+ else
+ env.info("✓ CAP launch completed successfully")
+ return true
+ end
+end
+
+-- ================================================================================================
+-- MISSION EXECUTION FUNCTION (Now can call launchCAP)
+-- ================================================================================================
+
+local function executeThreatsAssignments(assignments)
+ if not assignments or #assignments == 0 then
+ return false
+ end
+
+ local currentTime = timer.getTime()
+
+ env.info("=== EXECUTING THREAT ASSIGNMENTS ===")
+ env.info("Processing " .. #assignments .. " threat assignments")
+
+ local launchedFlights = {}
+
+ for _, assignment in pairs(assignments) do
+ local squadron = assignment.squadron
+ local templateName = assignment.templateName
+
+ -- Check squadron availability and cooldown
+ if squadron.readinessLevel == "READY" and
+ squadron.availableAircraft >= 1 and
+ (currentTime - squadron.lastLaunch) >= squadron.launchCooldown then
+
+ local reason = "Intercept: " .. (assignment.threat and assignment.threat.id or "Unknown") .. " (" .. (assignment.threat and assignment.threat.classification or "Unknown") .. ")"
+ local success = launchCAP(assignment.config, 1, reason)
+
+ if success then
+ -- Update squadron status - squadron is now BUSY with this threat
+ squadron.availableAircraft = squadron.availableAircraft - 1
+ squadron.airborneAircraft = squadron.airborneAircraft + 1
+ squadron.lastLaunch = currentTime
+ squadron.sorties = squadron.sorties + 1
+ squadron.readinessLevel = "BUSY" -- Squadron is now handling this threat
+
+ TADC.stats.interceptsLaunched = TADC.stats.interceptsLaunched + 1
+
+ -- Store mission details
+ TADC.missions[assignment.threatId] = {
+ threatId = assignment.threatId,
+ squadron = templateName,
+ threat = assignment.threat,
+ startTime = currentTime,
+ status = "ACTIVE"
+ }
+
+ table.insert(launchedFlights, {
+ squadron = templateName,
+ threat = assignment.threat and assignment.threat.id or "Unknown",
+ launchTime = currentTime
+ })
+
+ env.info("✓ Launched: " .. squadron.displayName .. " → " .. (assignment.threat and assignment.threat.id or "Unknown"))
+ else
+ env.info("✗ Launch failed: " .. squadron.displayName)
+ end
+ else
+ local reason = "Unknown"
+ if squadron.readinessLevel ~= "READY" then
+ reason = "Not ready (" .. squadron.readinessLevel .. ")"
+ elseif squadron.availableAircraft < 1 then
+ reason = "Insufficient aircraft (" .. squadron.availableAircraft .. " available)"
+ elseif (currentTime - squadron.lastLaunch) < squadron.launchCooldown then
+ reason = "On cooldown (" .. math.ceil(squadron.launchCooldown - (currentTime - squadron.lastLaunch)) .. "s remaining)"
+ end
+ env.info("✗ Cannot launch " .. squadron.displayName .. ": " .. reason)
+ end
+ end
+
+ -- Store mission for tracking
+ -- (Removed assignment to TADC.missions[missionId] due to undefined variables)
+
+ return #launchedFlights > 0
+end
+
+-- ================================================================================================
+-- PERSISTENT CAP HELPER FUNCTIONS
+-- ================================================================================================
+
+local function getPersistentCAPCount()
+ local count = 0
+ for _, capData in pairs(TADC.persistentCAPs) do
+ if capData.group and capData.group:IsAlive() then
+ count = count + 1
+ else
+ -- Clean up dead persistent CAPs
+ TADC.persistentCAPs[capData.templateName] = nil
+ end
+ end
+ return count
+end
+
+local function launchPersistentCAP(templateName, reason)
+ -- Find the original squadron config
+ local config = nil
+ for _, squadronConfig in pairs(squadronConfigs) do
+ if squadronConfig.templateName == templateName then
+ config = squadronConfig
+ break
+ end
+ end
+
+ if not config or not TADC.squadrons[templateName] then
+ return false
+ end
+
+ local squadron = TADC.squadrons[templateName]
+ local currentTime = timer.getTime()
+
+ -- Check if squadron is available (not on cooldown, has aircraft)
+ if squadron.readinessLevel ~= "READY" or
+ squadron.availableAircraft < 1 or
+ (currentTime - squadron.lastLaunch) < squadron.launchCooldown then
+ return false
+ end
+
+ -- Launch the CAP
+ local success = launchCAP(config, 1, reason)
+ if success then
+ -- Track as persistent CAP (separate from regular intercept CAPs)
+ TADC.persistentCAPs[templateName] = {
+ templateName = templateName,
+ group = TADC.activeCAPs[templateName] and TADC.activeCAPs[templateName].group,
+ launchTime = currentTime,
+ isPersistent = true
+ }
+
+ if GCI_Config.debugLevel >= 1 then
+ env.info("✓ Persistent CAP launched: " .. squadron.displayName)
+ end
+
+ return true
+ end
+
+ return false
+end
+
+local function maintainPersistentCAP()
+ if not GCI_Config.enablePersistentCAP then
+ return
+ end
+
+ local currentTime = timer.getTime()
+
+ -- Only check every persistentCAPInterval seconds
+ if (currentTime - TADC.lastPersistentCheck) < GCI_Config.persistentCAPInterval then
+ return
+ end
+
+ TADC.lastPersistentCheck = currentTime
+
+ -- Calculate total airborne aircraft to respect maxSimultaneousCAP limit
+ local totalAirborne = 0
+ for _, squadron in pairs(TADC.squadrons) do
+ totalAirborne = totalAirborne + squadron.airborneAircraft
+ end
+
+ local currentPersistentCount = getPersistentCAPCount()
+ local needed = GCI_Config.persistentCAPCount - currentPersistentCount
+
+ -- Respect maxSimultaneousCAP limit with reserve for threat response
+ local maxPersistentAllowed = math.floor(GCI_Config.maxSimultaneousCAP * (1 - GCI_Config.persistentCAPReserve))
+ local effectiveTarget = math.min(GCI_Config.persistentCAPCount, maxPersistentAllowed)
+ local availableSlots = maxPersistentAllowed - currentPersistentCount
+
+ -- Recalculate needed based on effective limits
+ needed = math.min(needed, availableSlots)
+ needed = math.max(0, needed)
+
+ if needed < (GCI_Config.persistentCAPCount - currentPersistentCount) and GCI_Config.debugLevel >= 1 then
+ env.info("⚠ Persistent CAP limited: Target=" .. GCI_Config.persistentCAPCount .. ", Effective=" .. effectiveTarget .. " (reserving " .. math.ceil(GCI_Config.maxSimultaneousCAP * GCI_Config.persistentCAPReserve) .. " slots for threats)")
+ end
+
+ if needed > 0 then
+ if GCI_Config.debugLevel >= 1 then
+ env.info("=== PERSISTENT CAP MAINTENANCE ===")
+ env.info("Current: " .. currentPersistentCount .. ", Target: " .. GCI_Config.persistentCAPCount .. ", Need: " .. needed)
+ env.info("Total airborne: " .. totalAirborne .. "/" .. GCI_Config.maxSimultaneousCAP .. " (available slots: " .. availableSlots .. ")")
+ end
+
+ -- Launch needed persistent CAPs from priority list
+ local launched = 0
+ for _, templateName in pairs(GCI_Config.persistentCAPPriority) do
+ if launched >= needed then
+ break
+ end
+
+ -- Skip if this squadron already has a persistent CAP
+ if not TADC.persistentCAPs[templateName] or
+ not TADC.persistentCAPs[templateName].group or
+ not TADC.persistentCAPs[templateName].group:IsAlive() then
+
+ if launchPersistentCAP(templateName, "Persistent CAP maintenance") then
+ launched = launched + 1
+ end
+ end
+ end
+
+ if GCI_Config.debugLevel >= 1 then
+ env.info("✓ Persistent CAP maintenance complete: " .. launched .. " new patrols launched")
+ end
+ end
+end
+
+
+
+-- ================================================================================================
+-- MAIN TADC CONTROL LOOP
+-- ================================================================================================
+
+local function mainTADCLoop()
+ local currentTime = timer.getTime()
+
+ -- Update threat picture
+ local threats = updateThreatPicture()
+ local threatCount = 0
+ for _ in pairs(threats) do threatCount = threatCount + 1 end
+
+ if threatCount > 0 then
+ if GCI_Config.debugLevel >= 2 then
+ env.info("=== TADC THREAT ASSESSMENT ===")
+ env.info("Active threats: " .. threatCount)
+ end
+
+ -- Plan responses for each zone
+ local zones = {"RED_BORDER", "HELO_BORDER"}
+
+ for _, zone in pairs(zones) do
+ local assignments = assignThreatsToSquadrons(threats, zone)
+
+ if #assignments > 0 then
+ -- Wait for response delay before executing (allows threat picture to stabilize)
+ local readyAssignments = {}
+
+ for _, assignment in pairs(assignments) do
+ local threatAge = currentTime - assignment.threat.firstDetected
+ if threatAge >= GCI_Config.responseDelay then
+ table.insert(readyAssignments, assignment)
+ elseif GCI_Config.debugLevel >= 2 then
+ env.info("Delaying assignment: " .. (assignment.threat and assignment.threat.id or "Unknown") .. " (age: " .. math.floor(threatAge) .. "s)")
+ end
+ end
+
+ if #readyAssignments > 0 then
+ executeThreatsAssignments(readyAssignments)
+ end
+ end
+ end
+ end
+
+ -- Clean up completed missions and free squadrons
+ for missionId, mission in pairs(TADC.missions) do
+ local squadron = TADC.squadrons[mission.squadron]
+ if squadron then
+ -- Check if mission should be completed (threat destroyed, timed out, or squadron has no airborne aircraft)
+ local missionAge = currentTime - mission.startTime
+ local shouldComplete = false
+
+ -- Mission timeout (30 minutes)
+ if missionAge > 1800 then
+ shouldComplete = true
+ end
+
+ -- Squadron has returned (no airborne aircraft)
+ if squadron.airborneAircraft == 0 and missionAge > 300 then -- Give 5 min minimum mission time
+ shouldComplete = true
+ end
+
+ -- Threat no longer exists
+ local threatExists = false
+ for _, threat in pairs(threats) do
+ if (threat.id .. "_" .. threat.firstDetected) == missionId then
+ threatExists = true
+ break
+ end
+ end
+ if not threatExists and missionAge > 60 then -- 1 minute grace period
+ shouldComplete = true
+ end
+
+ if shouldComplete then
+ -- Free the squadron for new missions
+ squadron.readinessLevel = "READY"
+ TADC.squadronMissions[mission.squadron] = nil
+ TADC.threatAssignments[missionId] = nil
+ TADC.missions[missionId] = nil
+
+ if GCI_Config.debugLevel >= 1 then
+ env.info("Mission completed: " .. mission.squadron .. " freed from " .. (mission.threat and mission.threat.id or "unknown threat"))
+ end
+ end
+ else
+ -- Squadron doesn't exist anymore, clean up
+ TADC.missions[missionId] = nil
+ TADC.threatAssignments[missionId] = nil
+ end
+ end
+
+ -- Periodic status report
+ if GCI_Config.debugLevel >= 1 and (currentTime % GCI_Config.statusReportInterval) < 30 then -- Status reports from config
+ local totalAirborne = 0
+ local totalAvailable = 0
+ for _, squadron in pairs(TADC.squadrons) do
+ totalAirborne = totalAirborne + squadron.airborneAircraft
+ totalAvailable = totalAvailable + squadron.availableAircraft
+ end
+
+ env.info("=== TADC STATUS REPORT ===")
+ env.info("Threats: " .. threatCount .. " active")
+ env.info("Aircraft: " .. totalAirborne .. " airborne, " .. totalAvailable .. " available")
+ env.info("Statistics: " .. TADC.stats.threatsDetected .. " threats detected, " .. TADC.stats.interceptsLaunched .. " intercepts launched")
+
+ -- Persistent CAP Status
+ if GCI_Config.enablePersistentCAP then
+ local persistentCount = getPersistentCAPCount()
+ local maxPersistentAllowed = math.floor(GCI_Config.maxSimultaneousCAP * (1 - GCI_Config.persistentCAPReserve))
+ local threatReserve = GCI_Config.maxSimultaneousCAP - maxPersistentAllowed
+ env.info("Persistent CAP: " .. persistentCount .. "/" .. GCI_Config.persistentCAPCount .. " target (" .. maxPersistentAllowed .. " max, " .. threatReserve .. " reserved for threats)")
+ end
+ end
+
+ -- Persistent CAP Management
+ if GCI_Config.enablePersistentCAP then
+ maintainPersistentCAP()
+ end
+end
+
+-- ================================================================================================
+-- PERSISTENT CAP MANAGEMENT SYSTEM
+-- ================================================================================================
+
+local function setupTADC()
+ env.info("=== INITIALIZING TACTICAL AIR DEFENSE CONTROLLER ===")
+ env.info("✓ Configuration loaded:")
+ env.info(" - Threat Ratio: " .. GCI_Config.threatRatio .. ":1")
+ env.info(" - Max Simultaneous CAP: " .. GCI_Config.maxSimultaneousCAP)
+ env.info(" - Reserve Percentage: " .. (GCI_Config.reservePercent * 100) .. "%")
+ env.info(" - Supply Mode: " .. GCI_Config.supplyMode)
+ env.info(" - Response Delay: " .. GCI_Config.responseDelay .. " seconds")
+
+ -- Persistent CAP Configuration
+ if GCI_Config.enablePersistentCAP then
+ local maxPersistentAllowed = math.floor(GCI_Config.maxSimultaneousCAP * (1 - GCI_Config.persistentCAPReserve))
+ local threatReserve = math.ceil(GCI_Config.maxSimultaneousCAP * GCI_Config.persistentCAPReserve)
+ env.info(" - Persistent CAP: ENABLED (" .. GCI_Config.persistentCAPCount .. " target, " .. maxPersistentAllowed .. " max allowed)")
+ env.info(" - Threat Response Reserve: " .. threatReserve .. " aircraft slots")
+ env.info(" - Persistent CAP Check Interval: " .. GCI_Config.persistentCAPInterval .. " seconds")
+ else
+ env.info(" - Persistent CAP: DISABLED")
+ end
+
+ -- CAP Behavior Configuration
+ env.info(" - CAP Orbit Radius: " .. (GCI_Config.capOrbitRadius / 1000) .. "km")
+ env.info(" - CAP Engagement Range: " .. (GCI_Config.capEngagementRange / 1000) .. "km")
+ env.info(" - Zone Constraint: " .. (GCI_Config.capZoneConstraint and "ENABLED" or "DISABLED"))
+
+ -- Start main control loop
+ SCHEDULER:New(nil, mainTADCLoop, {}, GCI_Config.mainLoopDelay, GCI_Config.mainLoopInterval) -- Main loop timing from config
+
+ env.info("✓ TADC main control loop started")
+ env.info("✓ Tactical Air Defense Controller operational!")
+end
+
+-- Initialize the TADC system
+SCHEDULER:New(nil, function()
+ setupTADC()
+
+ -- Launch initial persistent CAP flights if enabled
+ if GCI_Config.enablePersistentCAP then
+ env.info("=== LAUNCHING INITIAL PERSISTENT CAP ===")
+ TADC.lastPersistentCheck = 0 -- Force immediate check
+ maintainPersistentCAP()
+ end
+
+ -- Legacy: Optional initial standing patrols (if configured)
+ if GCI_Config.initialStandingPatrols then
+ local initialPatrols = {
+ "FIGHTER_SWEEP_RED_Severomorsk-1", -- Main base always has standing patrol
+ "HELO_SWEEP_RED_Afrikanda" -- Helo patrol coverage
+ }
+
+ for _, templateName in pairs(initialPatrols) do
+ if TADC.squadrons[templateName] then
+ -- Find the original squadron config
+ local config = nil
+ for _, squadronConfig in pairs(squadronConfigs) do
+ if squadronConfig.templateName == templateName then
+ config = squadronConfig
+ break
+ end
+ end
+
+ if config then
+ launchCAP(config, 1, "Initial standing patrol")
+ end
+ end
+ end
+ end
+
+ env.info("=== TADC INITIALIZATION COMPLETE ===")
+ env.info("✓ Intelligent threat assessment and response")
+ env.info("✓ Multi-squadron coordinated intercepts")
+ env.info("✓ Dynamic force sizing based on threat strength")
+ env.info("✓ Resource management with reserve forces")
+ env.info("✓ EWR network integration with " .. (RedEWR:Count()) .. " detection groups")
+ env.info("✓ Tactical Air Defense Controller operational!")
+
+end, {}, 5)
\ No newline at end of file
diff --git a/DCS_Kola/Operation_Polar_Shield/OnBirthMessage.lua b/DCS_Kola/Operation_Polar_Shield/OnBirthMessage.lua
new file mode 100644
index 0000000..b5f0b75
--- /dev/null
+++ b/DCS_Kola/Operation_Polar_Shield/OnBirthMessage.lua
@@ -0,0 +1,213 @@
+-- Immediate test to confirm script is loading
+env.info("=== OnBirthMessage.lua LOADING ===")
+trigger.action.outText("OnBirthMessage script is loading...", 10)
+
+-- Player preferences storage
+local playerWelcomeSettings = {}
+local processedPlayers = {} -- Track players to prevent double processing
+
+-- F10 Menu Functions
+local function enableWelcomeMessage(playerUnitID, playerName)
+ env.info("OnBirthMessage: enableWelcomeMessage called for " .. playerName)
+ playerWelcomeSettings[playerName] = true
+ trigger.action.outTextForUnit(playerUnitID, "✅ Welcome messages ENABLED", 10)
+ env.info("OnBirthMessage: Enabled for " .. playerName)
+end
+
+local function disableWelcomeMessage(playerUnitID, playerName)
+ env.info("OnBirthMessage: disableWelcomeMessage called for " .. playerName)
+ playerWelcomeSettings[playerName] = false
+ trigger.action.outTextForUnit(playerUnitID, "❌ Welcome messages DISABLED", 10)
+ env.info("OnBirthMessage: Disabled for " .. playerName)
+end
+
+local function addWelcomeMenuForPlayer(playerUnit, playerName)
+ env.info("OnBirthMessage: Adding menu for " .. playerName)
+
+ local success, errorMsg = pcall(function()
+ local playerGroup = playerUnit:getGroup()
+ local playerUnitID = playerUnit:getID()
+ local groupID = playerGroup:getID()
+
+ env.info("OnBirthMessage: Group ID: " .. groupID .. ", Unit ID: " .. playerUnitID)
+
+ -- Remove existing menus to prevent duplicates
+ env.info("OnBirthMessage: Cleaning up existing menus")
+ missionCommands.removeItemForGroup(groupID, {"Welcome Messages", "Enable Welcome Message"})
+ missionCommands.removeItemForGroup(groupID, {"Welcome Messages", "Disable Welcome Message"})
+ missionCommands.removeItemForGroup(groupID, {"Welcome Messages", "Test Menu Works"})
+ missionCommands.removeItemForGroup(groupID, {"Welcome Messages"})
+
+ -- Create main menu
+ env.info("OnBirthMessage: Creating new menu")
+ missionCommands.addSubMenuForGroup(groupID, "Welcome Messages")
+
+ -- Add commands with simpler functions to avoid freezing
+ missionCommands.addCommandForGroup(groupID, "Enable Welcome Message", {"Welcome Messages"},
+ function()
+ playerWelcomeSettings[playerName] = true
+ trigger.action.outTextForGroup(groupID, "✅ Welcome messages ENABLED for " .. playerName, 10)
+ end)
+
+ missionCommands.addCommandForGroup(groupID, "Disable Welcome Message", {"Welcome Messages"},
+ function()
+ playerWelcomeSettings[playerName] = false
+ trigger.action.outTextForGroup(groupID, "❌ Welcome messages DISABLED for " .. playerName, 10)
+ end)
+
+ -- Add a test command
+ missionCommands.addCommandForGroup(groupID, "Test Menu Works", {"Welcome Messages"},
+ function()
+ trigger.action.outTextForGroup(groupID, "✅ F10 Menu is working for " .. playerName, 10)
+ end)
+
+ env.info("OnBirthMessage: Menu added successfully for " .. playerName)
+ end)
+
+ if not success then
+ env.info("OnBirthMessage: Menu creation failed: " .. tostring(errorMsg))
+ end
+end
+
+onPlayerJoin = {}
+function onPlayerJoin:onEvent(event)
+ env.info("OnBirthMessage: Event triggered - ID: " .. tostring(event.id))
+
+ -- Trigger on both BIRTH and ENGINE_STARTUP events for better coverage
+ if (event.id == world.event.S_EVENT_BIRTH or event.id == world.event.S_EVENT_ENGINE_STARTUP) then
+ env.info("OnBirthMessage: Correct event type detected")
+
+ if event.initiator then
+ env.info("OnBirthMessage: Initiator exists")
+ local playerName = event.initiator:getPlayerName()
+ if playerName then
+ env.info("OnBirthMessage: Player name found: " .. playerName)
+
+ -- Check if we've already processed this player to prevent doubles
+ local playerKey = playerName .. "_" .. event.id
+ if processedPlayers[playerKey] then
+ env.info("OnBirthMessage: Already processed " .. playerName .. " for event " .. event.id .. " - skipping")
+ return
+ end
+ processedPlayers[playerKey] = true
+
+ -- Add error handling to prevent script crashes
+ local success, errorMsg = pcall(function()
+ local playerGroup = event.initiator:getGroup()
+ local playerUnit = playerGroup:getUnit(1)
+ local playerSide = playerGroup:getCoalition()
+ local playerID = playerGroup:getID()
+ local playerAircraft = playerUnit:getTypeName()
+ local playerUnitID = playerUnit:getID()
+
+ -- Debug message to confirm script is running
+ env.info("OnBirthMessage: Player " .. playerName .. " joined in " .. playerAircraft .. " (Coalition: " .. playerSide .. ")")
+
+ -- Send immediate test message
+ trigger.action.outTextForUnit(playerUnitID, "OnBirthMessage: Script detected you joining as " .. playerName, 15)
+
+ -- Initialize player preference if not set (default to enabled)
+ if playerWelcomeSettings[playerName] == nil then
+ playerWelcomeSettings[playerName] = true
+ end
+
+ -- Add F10 menu for welcome message control (only once per player)
+ env.info("OnBirthMessage: About to create menu for " .. playerName)
+ addWelcomeMenuForPlayer(playerUnit, playerName)
+
+ -- Only show welcome message if player has it enabled
+ if not playerWelcomeSettings[playerName] then
+ env.info("OnBirthMessage: Skipping welcome message for " .. playerName .. " (disabled by player)")
+ return
+ end
+
+ -- Prepare welcome message content
+ local MissionName =
+ "=====[ Fighting 99th - Operation Polar Shield / Polar Hammer]===="
+
+ local Discord =
+ "Please join our Discord Server @ https://discord.gg/WDZqAzAs for improved comms and a better mission experience!\n" ..
+ "You can turn off this message in the F10 menu under 'Welcome Messages'.\n"
+
+ local ObjectiveRed =
+ "==============[ OPERATION POLAR SHIELD ]=============\n" ..
+ "🛡️ DEFENSIVE MISSION - HOLD THE ARCTIC FRONTIER 🛡️\n\n" ..
+ "SITUATION: Russian forces have secured key strategic positions across the Kola Peninsula. Your mission is to maintain this defensive shield against NATO's 'Polar Hammer' offensive operations.\n\n" ..
+ "PRIMARY OBJECTIVES:\n" ..
+ "🎯 CAP - Maintain air superiority over the RED BORDER zone\n" ..
+ "🎯 INTERCEPT - Eliminate all NATO penetrations of Russian airspace\n" ..
+ "🎯 DEFEND - Protect airbases: Severomorsk, Murmansk, Olenya, Kilpyavr, Monchegorsk, Afrikanda\n\n" ..
+ "⚠️ INTELLIGENCE BRIEFING ⚠️\n" ..
+ "• Advanced TADC system provides automated threat response\n" ..
+ "• Persistent CAP flights maintain 24/7 border patrol\n" ..
+ "• AI squadrons will launch coordinated intercepts\n" ..
+ "• EWR network provides early warning coverage\n\n" ..
+ "WEATHER: Arctic conditions - limited visibility, icing risk\n" ..
+ "TERRAIN: Mountainous, frozen terrain - emergency landing sites scarce\n\n"
+
+ local ObjectiveBlue =
+ "==============[ OPERATION POLAR HAMMER ]=============\n" ..
+ "⚔️ OFFENSIVE MISSION - BREAK THE RUSSIAN SHIELD ⚔️\n\n" ..
+ "SITUATION: Russian forces have established a defensive 'Polar Shield' across the Kola Peninsula. NATO forces must execute 'Polar Hammer' - a coordinated offensive to break Russian air superiority and penetrate their defensive perimeter.\n\n" ..
+ "PRIMARY OBJECTIVES:\n" ..
+ "🎯 CAP - Establish air superiority within the RED BORDER zone\n" ..
+ "🎯 SWEEP - Clear Russian interceptors and defensive CAP flights\n" ..
+ "🎯 SEAD - Suppress Russian EWR network and SAM systems\n" ..
+ "🎯 STRIKE - Attack Russian airbases and defensive positions\n\n" ..
+ "⚠️ INTELLIGENCE BRIEFING ⚠️\n" ..
+ "• Enemy operates advanced Tactical Air Defense Controller (TADC)\n" ..
+ "• Expect coordinated multi-squadron intercepts\n" ..
+ "• Russians maintain persistent CAP along border zones\n" ..
+ "• Enemy response times: ~15 seconds from detection\n" ..
+ "• Multiple threats will trigger proportional defensive response\n\n" ..
+ "WEATHER: Arctic conditions - limited visibility, icing risk\n" ..
+ "TERRAIN: Mountainous, frozen terrain - plan fuel carefully\n\n" ..
+ "🔥 BREAK THE SHIELD - EXECUTE POLAR HAMMER! 🔥\n\n"
+
+ local TacticalInfo =
+ "================[ TACTICAL NOTES ]===============\n" ..
+ "RED SMOKE : Target areas or supply zones\n" ..
+ "BLUE SMOKE : Friendly pickup/drop zones\n" ..
+ "GREEN SMOKE: Medical evacuation points\n\n" ..
+ "COMMS: Use proper brevity codes for air-to-air combat\n" ..
+ "FUEL: Monitor fuel carefully in Arctic conditions\n\n"
+
+ local EndBrief = "==============[ END MISSION BRIEF ]==============\n\n"
+
+ -- Send appropriate message to each coalition (only to the individual player)
+ env.info("OnBirthMessage: Sending welcome message to " .. playerName)
+
+ if playerSide == coalition.side.BLUE then --blue team
+ trigger.action.outTextForUnit(playerUnitID, "" .. MissionName .. "\n\n" .. "Welcome to the Arctic Theater, " .. playerName .. "!" .. "\n\n" .. Discord .. "\n\n" .. ObjectiveBlue .. TacticalInfo .. EndBrief, 45)
+ env.info("OnBirthMessage: Blue team message sent to " .. playerName)
+ elseif playerSide == coalition.side.RED then -- red team
+ trigger.action.outTextForUnit(playerUnitID, "" .. MissionName .. "\n\n" .. "Добро пожаловать, " .. playerName .. "!" .. "\n\n" .. Discord .. "\n\n" .. ObjectiveRed .. TacticalInfo .. EndBrief, 45)
+ env.info("OnBirthMessage: Red team message sent to " .. playerName)
+ else
+ env.info("OnBirthMessage: Unknown coalition for " .. playerName .. " (coalition=" .. playerSide .. ")")
+ end
+
+ -- trigger.action.outSoundForGroup(playerID, "l10n/DEFAULT/battlemusic.ogg") -- Damn Cry Babbies
+ end)
+
+ if not success then
+ env.info("OnBirthMessage Error: " .. tostring(errorMsg))
+ end
+ else
+ env.info("OnBirthMessage: No player name found")
+ end
+ else
+ env.info("OnBirthMessage: No initiator found")
+ end
+ else
+ -- Uncomment next line if you want to see all events (very spammy)
+ -- env.info("OnBirthMessage: Ignoring event ID: " .. tostring(event.id))
+ end
+end
+
+-- Register event handler
+env.info("OnBirthMessage: Registering event handler...")
+world.addEventHandler(onPlayerJoin)
+env.info("OnBirthMessage: Event handler registered successfully")
+env.info("=== OnBirthMessage.lua LOADED SUCCESSFULLY ===")
+trigger.action.outText("OnBirthMessage script loaded - check for welcome messages when joining aircraft", 15)
\ No newline at end of file
diff --git a/DCS_Kola/Operation_Polar_Shield/test.lua b/DCS_Kola/Operation_Polar_Shield/test.lua
new file mode 100644
index 0000000..98fb7f3
--- /dev/null
+++ b/DCS_Kola/Operation_Polar_Shield/test.lua
@@ -0,0 +1,69 @@
+-- Add this at the very beginning after MOOSE loads
+env.info("=== MOOSE DEBUG INFO ===")
+env.info("MOOSE loaded: " .. tostring(MOOSE ~= nil))
+env.info("OPS available: " .. tostring(OPS ~= nil))
+env.info("_G.OPS available: " .. tostring(_G.OPS ~= nil))
+
+-- Check what's in the global namespace
+for k,v in pairs(_G) do
+ if string.find(k, "OPS") then
+ env.info("Found OPS-related: " .. k .. " = " .. tostring(v))
+ end
+end
+
+
+-- Debug airbase availability
+env.info("=== AIRBASE DEBUG ===")
+env.info("AIRBASE table exists: " .. tostring(AIRBASE ~= nil))
+
+if AIRBASE then
+ env.info("AIRBASE.Kola exists: " .. tostring(AIRBASE.Kola ~= nil))
+
+ -- List all airbases found on the map
+ env.info("=== ALL AIRBASES ON MAP ===")
+
+ -- Method 1: Try using SET_AIRBASE to get all airbases
+ if SET_AIRBASE then
+ env.info("Using SET_AIRBASE method...")
+ local airbaseSet = SET_AIRBASE:New():FilterOnce()
+ if airbaseSet then
+ local count = airbaseSet:Count()
+ env.info("Total airbases found: " .. count)
+ airbaseSet:ForEach(function(airbase)
+ if airbase then
+ local name = airbase:GetName()
+ local coalition = airbase:GetCoalition()
+ local coalitionName = "Unknown"
+ if coalition == 0 then coalitionName = "Neutral"
+ elseif coalition == 1 then coalitionName = "Red"
+ elseif coalition == 2 then coalitionName = "Blue"
+ end
+ env.info("Airbase: '" .. name .. "' (" .. coalitionName .. ")")
+ end
+ end)
+ end
+ end
+
+ -- Method 2: Try specific airbase names we expect
+ env.info("=== TESTING SPECIFIC AIRBASES ===")
+ local testNames = {
+ "Severomorsk-1", "Severomorsk-3", "Kilpyavr", "Murmansk",
+ "Monchegorsk", "Olenya", "Afrikanda"
+ }
+
+ for _, name in pairs(testNames) do
+ local airbase = AIRBASE:FindByName(name)
+ env.info(name .. ": " .. tostring(airbase ~= nil))
+ if airbase then
+ env.info(" - Coalition: " .. airbase:GetCoalition())
+ end
+ end
+
+ -- Alternative method - check AIRBASE.Kola if it exists
+ if AIRBASE.Kola then
+ env.info("=== AIRBASE.Kola CONSTANTS ===")
+ for k,v in pairs(AIRBASE.Kola) do
+ env.info("AIRBASE.Kola." .. k .. " = " .. tostring(v))
+ end
+ end
+end
\ No newline at end of file
diff --git a/Moose_.lua b/Moose_.lua
index 1859b96..a0357ab 100644
--- a/Moose_.lua
+++ b/Moose_.lua
@@ -1,4 +1,4 @@
-env.info('*** MOOSE GITHUB Commit Hash ID: 2024-12-03T18:10:06+01:00-86e899f39bc5120901cbbb4e8d4b87bf0ccc44af ***')
+env.info('*** MOOSE GITHUB Commit Hash ID: 2025-10-01T15:26:49+02:00-d8281b01032aa44a579c14d690ca79413b671d9d ***')
if not MOOSE_DEVELOPMENT_FOLDER then
MOOSE_DEVELOPMENT_FOLDER='Scripts'
end
@@ -1028,6 +1028,125 @@ ENUMS.Storage.weapons.bombs.AGM_62="weapons.bombs.AGM_62"
ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_WHITE="weapons.containers.{US_M10_SMOKE_TANK_WHITE}"
ENUMS.Storage.weapons.missiles.MICA_T="weapons.missiles.MICA_T"
ENUMS.Storage.weapons.containers.HVAR_rocket="weapons.containers.HVAR_rocket"
+ENUMS.Storage.weapons.containers.LANTIRN="weapons.containers.LANTIRN"
+ENUMS.Storage.weapons.missiles.AGM_78B="weapons.missiles.AGM_78B"
+ENUMS.Storage.weapons.containers.uh_60l_pilot="weapons.containers.uh-60l_pilot"
+ENUMS.Storage.weapons.missiles.AIM_92E="weapons.missiles.AIM-92E"
+ENUMS.Storage.weapons.missiles.KD_63B="weapons.missiles.KD_63B"
+ENUMS.Storage.weapons.bombs.Type_200A="weapons.bombs.Type_200A"
+ENUMS.Storage.weapons.missiles.HB_AIM_7E_2="weapons.missiles.HB-AIM-7E-2"
+ENUMS.Storage.weapons.containers.Spear="weapons.containers.Spear"
+ENUMS.Storage.weapons.missiles.LS_6="weapons.missiles.LS_6"
+ENUMS.Storage.weapons.containers.HB_ALE_40_0_120="weapons.containers.HB_ALE_40_0_120"
+ENUMS.Storage.weapons.containers.Fantasm="weapons.containers.Fantasm"
+ENUMS.Storage.weapons.nurs.FFAR_Mk61="weapons.nurs.FFAR_Mk61"
+ENUMS.Storage.weapons.bombs.HB_F4E_GBU15V1="weapons.bombs.HB_F4E_GBU15V1"
+ENUMS.Storage.weapons.containers.HB_F14_EXT_AN_APQ_167="weapons.containers.HB_F14_EXT_AN_APQ-167"
+ENUMS.Storage.weapons.nurs.LWL_RP="weapons.nurs.LWL_RP"
+ENUMS.Storage.weapons.bombs.AGM_62_I="weapons.bombs.AGM_62_I"
+ENUMS.Storage.weapons.containers.ETHER="weapons.containers.ETHER"
+ENUMS.Storage.weapons.containers.TANGAZH="weapons.containers.TANGAZH"
+ENUMS.Storage.weapons.bombs.LYSBOMB_11086="weapons.bombs.LYSBOMB 11086"
+ENUMS.Storage.weapons.containers.Stub_Wing="weapons.containers.Stub_Wing"
+ENUMS.Storage.weapons.missiles.AIM_9E="weapons.missiles.AIM-9E"
+ENUMS.Storage.weapons.missiles.C_701T="weapons.missiles.C_701T"
+ENUMS.Storage.weapons.bombs.BAP_100="weapons.bombs.BAP_100"
+ENUMS.Storage.weapons.missiles.CM_802AKG="weapons.missiles.CM-802AKG"
+ENUMS.Storage.weapons.missiles.CM_400AKG="weapons.missiles.CM-400AKG"
+ENUMS.Storage.weapons.missiles.C_802AK="weapons.missiles.C_802AK"
+ENUMS.Storage.weapons.missiles.KD_63="weapons.missiles.KD_63"
+ENUMS.Storage.weapons.containers.HB_ORD_Pave_Spike_Fast="weapons.containers.HB_ORD_Pave_Spike_Fast"
+ENUMS.Storage.weapons.missiles.SPIKE_ER2="weapons.missiles.SPIKE_ER2"
+ENUMS.Storage.weapons.containers.KINGAL="weapons.containers.KINGAL"
+ENUMS.Storage.weapons.containers.LANTIRN_F14_TARGET="weapons.containers.LANTIRN-F14-TARGET"
+ENUMS.Storage.weapons.containers.SPS_141="weapons.containers.SPS-141"
+ENUMS.Storage.weapons.bombs.BLU_3B_GROUP="weapons.bombs.BLU-3B_GROUP"
+ENUMS.Storage.weapons.containers.HB_ALE_40_30_0="weapons.containers.HB_ALE_40_30_0"
+ENUMS.Storage.weapons.droptanks.HB_HIGH_PERFORMANCE_CENTERLINE_600_GAL="weapons.droptanks.HB_HIGH_PERFORMANCE_CENTERLINE_600_GAL"
+ENUMS.Storage.weapons.containers.ALQ_184="weapons.containers.ALQ-184"
+ENUMS.Storage.weapons.missiles.AGM_45B="weapons.missiles.AGM_45B"
+ENUMS.Storage.weapons.bombs.BLU_3_GROUP="weapons.bombs.BLU-3_GROUP"
+ENUMS.Storage.weapons.missiles.SPIKE_ER="weapons.missiles.SPIKE_ER"
+ENUMS.Storage.weapons.nurs.ARAKM70BAPPX="weapons.nurs.ARAKM70BAPPX"
+ENUMS.Storage.weapons.bombs.LYSBOMB_11088="weapons.bombs.LYSBOMB 11088"
+ENUMS.Storage.weapons.bombs.LYSBOMB_11087="weapons.bombs.LYSBOMB 11087"
+ENUMS.Storage.weapons.missiles.KD_20="weapons.missiles.KD_20"
+ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank="weapons.droptanks.HB_F-4E_EXT_WingTank"
+ENUMS.Storage.weapons.missiles.Rb_04="weapons.missiles.Rb_04"
+ENUMS.Storage.weapons.containers.AAQ_33="weapons.containers.AAQ-33"
+ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_Center_Fuel_Tank_EMPTY="weapons.droptanks.HB_F-4E_EXT_Center_Fuel_Tank_EMPTY"
+ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_R_EMPTY="weapons.droptanks.HB_F-4E_EXT_WingTank_R_EMPTY"
+ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_EMPTY="weapons.droptanks.HB_F-4E_EXT_WingTank_EMPTY"
+ENUMS.Storage.weapons.containers.uh_60l_copilot="weapons.containers.uh-60l_copilot"
+ENUMS.Storage.weapons.droptanks.JAYHAWK_80gal_Fuel_Tankv2="weapons.droptanks.JAYHAWK_80gal_Fuel_Tankv2"
+ENUMS.Storage.weapons.containers.supply_m134="weapons.containers.supply_m134"
+ENUMS.Storage.weapons.containers.Seahawk_Pylon="weapons.containers.Seahawk_Pylon"
+ENUMS.Storage.weapons.nurs.LWL_MPP="weapons.nurs.LWL_MPP"
+ENUMS.Storage.weapons.nurs.S_5KP="weapons.nurs.S_5KP"
+ENUMS.Storage.weapons.missiles.AIM_92J="weapons.missiles.AIM-92J"
+ENUMS.Storage.weapons.missiles.HB_AIM_7E="weapons.missiles.HB-AIM-7E"
+ENUMS.Storage.weapons.containers.ALQ_131="weapons.containers.ALQ-131"
+ENUMS.Storage.weapons.containers.HB_F14_EXT_TARPS="weapons.containers.HB_F14_EXT_TARPS"
+ENUMS.Storage.weapons.containers.MH60_SOAR="weapons.containers.MH60_SOAR"
+ENUMS.Storage.weapons.missiles.YJ_83="weapons.missiles.YJ-83"
+ENUMS.Storage.weapons.bombs.GBU_8_B="weapons.bombs.GBU_8_B"
+ENUMS.Storage.weapons.containers.HB_F14_EXT_ECA="weapons.containers.HB_F14_EXT_ECA"
+ENUMS.Storage.weapons.bombs.BAP_100="weapons.bombs.BAP-100"
+ENUMS.Storage.weapons.nurs.M261_MPSM_Rocket="weapons.nurs.M261_MPSM_Rocket"
+ENUMS.Storage.weapons.droptanks.SEAHAWK_120_Fuel_Tank="weapons.droptanks.SEAHAWK_120_Fuel_Tank"
+ENUMS.Storage.weapons.containers.SHPIL="weapons.containers.SHPIL"
+ENUMS.Storage.weapons.bombs.GBU_39="weapons.bombs.GBU_39"
+ENUMS.Storage.weapons.nurs.S_5M="weapons.nurs.S_5M"
+ENUMS.Storage.weapons.containers.HB_ALE_40_15_90="weapons.containers.HB_ALE_40_15_90"
+ENUMS.Storage.weapons.missiles.AIM_7E="weapons.missiles.AIM-7E"
+ENUMS.Storage.weapons.missiles.AIM_9P3="weapons.missiles.AIM-9P3"
+ENUMS.Storage.weapons.missiles.AGM_12B="weapons.missiles.AGM_12B"
+ENUMS.Storage.weapons.missiles.CM_802AKG="weapons.missiles.CM_802AKG"
+ENUMS.Storage.weapons.droptanks.JAYHAWK_120_Fuel_Dual_Tank="weapons.droptanks.JAYHAWK_120_Fuel_Dual_Tank"
+ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_Center_Fuel_Tank="weapons.droptanks.HB_F-4E_EXT_Center_Fuel_Tank"
+ENUMS.Storage.weapons.containers.PAVETACK="weapons.containers.PAVETACK"
+ENUMS.Storage.weapons.missiles.LS_6_500="weapons.missiles.LS_6_500"
+ENUMS.Storage.weapons.bombs.LYSBOMB_11089="weapons.bombs.LYSBOMB 11089"
+ENUMS.Storage.weapons.bombs.BLU_4B_GROUP="weapons.bombs.BLU-4B_GROUP"
+ENUMS.Storage.weapons.containers.ah_64d_radar="weapons.containers.ah-64d_radar"
+ENUMS.Storage.weapons.containers.F_18_LDT_POD="weapons.containers.F-18-LDT-POD"
+ENUMS.Storage.weapons.containers.HB_ALE_40_30_60="weapons.containers.HB_ALE_40_30_60"
+ENUMS.Storage.weapons.bombs.LS_6_100="weapons.bombs.LS_6_100"
+ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_R="weapons.droptanks.HB_F-4E_EXT_WingTank_R"
+ENUMS.Storage.weapons.containers.SORBCIJA_R="weapons.containers.SORBCIJA_R"
+ENUMS.Storage.weapons.missiles.CATM_65K="weapons.missiles.CATM_65K"
+ENUMS.Storage.weapons.containers.HB_ORD_Pave_Spike="weapons.containers.HB_ORD_Pave_Spike"
+ENUMS.Storage.weapons.containers.RobbieTank1="weapons.containers.RobbieTank1"
+ENUMS.Storage.weapons.containers.SKY_SHADOW="weapons.containers.SKY_SHADOW"
+ENUMS.Storage.weapons.containers.SORBCIJA_L="weapons.containers.SORBCIJA_L"
+ENUMS.Storage.weapons.containers.Pavehawk="weapons.containers.Pavehawk"
+ENUMS.Storage.weapons.bombs.BLG66_EG="weapons.bombs.BLG66_EG"
+ENUMS.Storage.weapons.missiles.AGM_12C_ED="weapons.missiles.AGM_12C_ED"
+ENUMS.Storage.weapons.missiles.AIM_92C="weapons.missiles.AIM-92C"
+ENUMS.Storage.weapons.containers.MPS_410="weapons.containers.MPS-410"
+ENUMS.Storage.weapons.missiles.HJ_12="weapons.missiles.HJ-12"
+ENUMS.Storage.weapons.containers.AAQ_28_LITENING="weapons.containers.AAQ-28_LITENING"
+ENUMS.Storage.weapons.containers.F_18_FLIR_POD="weapons.containers.F-18-FLIR-POD"
+ENUMS.Storage.weapons.bombs.BLU_3B_GROUP="weapons.bombs.BLU_3B_GROUP"
+ENUMS.Storage.weapons.containers.UH60L_Jayhawk="weapons.containers.UH60L_Jayhawk"
+ENUMS.Storage.weapons.containers.BOZ_100="weapons.containers.BOZ-100"
+ENUMS.Storage.weapons.missiles.AGM_78A="weapons.missiles.AGM_78A"
+ENUMS.Storage.weapons.missiles.LAU_61_APKWS_M282="weapons.missiles.LAU_61_APKWS_M282"
+ENUMS.Storage.weapons.bombs.BAP_100="weapons.bombs.BAP-100"
+ENUMS.Storage.weapons.missiles.CM_802AKG="weapons.missiles.CM-802AKG"
+ENUMS.Storage.weapons.bombs.BLU_3B_GROUP="weapons.bombs.BLU_3B_GROUP"
+ENUMS.Storage.weapons.bombs.BLU_4B_GROUP="weapons.bombs.BLU-4B_GROUP"
+ENUMS.Storage.weapons.nurs.S_5M="weapons.nurs.S_5M"
+ENUMS.Storage.weapons.missiles.AGM_12A="weapons.missiles.AGM_12A"
+ENUMS.Storage.weapons.droptanks.JAYHAWK_120_Fuel_Tank="weapons.droptanks.JAYHAWK_120_Fuel_Tank"
+ENUMS.Storage.weapons.bombs.GBU_15_V_1_B="weapons.bombs.GBU_15_V_1_B"
+ENUMS.Storage.weapons.missiles.HYDRA_70_M151_APKWS={4,4,8,292}
+ENUMS.Storage.weapons.missiles.HYDRA_70_M282_APKWS={4,4,8,293}
+ENUMS.Storage.weapons.bombs.BAP100="weapons.bombs.BAP_100"
+ENUMS.Storage.weapons.bombs.BLU3B_GROUP="weapons.bombs.BLU-3B_GROUP"
+ENUMS.Storage.weapons.missiles.CM_802AKG="weapons.missiles.CM_802AKG"
+ENUMS.Storage.weapons.bombs.BLU_4B_GROUP="weapons.bombs.BLU_4B_GROUP"
+ENUMS.Storage.weapons.nurs.S5M="weapons.nurs.S-5M"
ENUMS.Storage.weapons.Gazelle.HMP400_100RDS={4,15,46,1771}
ENUMS.Storage.weapons.Gazelle.HMP400_200RDS={4,15,46,1770}
ENUMS.Storage.weapons.Gazelle.HMP400_400RDS={4,15,46,1769}
@@ -1038,15 +1157,15 @@ ENUMS.Storage.weapons.Gazelle.GIAT_M261_HEAP={4,15,46,1765}
ENUMS.Storage.weapons.Gazelle.GIAT_M261_APHE={4,15,46,1764}
ENUMS.Storage.weapons.Gazelle.GAZELLE_IR_DEFLECTOR={4,15,47,680}
ENUMS.Storage.weapons.Gazelle.GAZELLE_FAS_SANDFILTER={4,15,47,679}
-ENUMS.Storage.weapons.CH47.CH47_PORT_M60D={4,15,46,2476}
-ENUMS.Storage.weapons.CH47.CH47_STBD_M60D={4,15,46,2477}
-ENUMS.Storage.weapons.CH47.CH47_AFT_M60D={4,15,46,2478}
-ENUMS.Storage.weapons.CH47.CH47_PORT_M134D={4,15,46,2482}
-ENUMS.Storage.weapons.CH47.CH47_STBD_M134D={4,15,46,2483}
-ENUMS.Storage.weapons.CH47.CH47_AFT_M3M={4,15,46,2484}
-ENUMS.Storage.weapons.CH47.CH47_PORT_M240H={4,15,46,2479}
-ENUMS.Storage.weapons.CH47.CH47_STBD_M240H={4,15,46,2480}
-ENUMS.Storage.weapons.CH47.CH47_AFT_M240H={4,15,46,2481}
+ENUMS.Storage.weapons.CH47.CH47_PORT_M60D={4,15,46,2489}
+ENUMS.Storage.weapons.CH47.CH47_STBD_M60D={4,15,46,2488}
+ENUMS.Storage.weapons.CH47.CH47_AFT_M60D={4,15,46,2490}
+ENUMS.Storage.weapons.CH47.CH47_PORT_M134D={4,15,46,2494}
+ENUMS.Storage.weapons.CH47.CH47_STBD_M134D={4,15,46,2495}
+ENUMS.Storage.weapons.CH47.CH47_AFT_M3M={4,15,46,2496}
+ENUMS.Storage.weapons.CH47.CH47_PORT_M240H={4,15,46,2492}
+ENUMS.Storage.weapons.CH47.CH47_STBD_M240H={4,15,46,2491}
+ENUMS.Storage.weapons.CH47.CH47_AFT_M240H={4,15,46,2493}
ENUMS.Storage.weapons.UH1H.M134_MiniGun_Right={4,15,46,161}
ENUMS.Storage.weapons.UH1H.M134_MiniGun_Left={4,15,46,160}
ENUMS.Storage.weapons.UH1H.M134_MiniGun_Right_Door={4,15,46,175}
@@ -1054,19 +1173,24 @@ ENUMS.Storage.weapons.UH1H.M60_MG_Right_Door={4,15,46,177}
ENUMS.Storage.weapons.UH1H.M134_MiniGun_Left_Door={4,15,46,174}
ENUMS.Storage.weapons.UH1H.M60_MG_Left_Door={4,15,46,176}
ENUMS.Storage.weapons.OH58.FIM92={4,4,7,449}
-ENUMS.Storage.weapons.OH58.MG_M3P100={4,15,46,2608}
-ENUMS.Storage.weapons.OH58.MG_M3P200={4,15,46,2607}
-ENUMS.Storage.weapons.OH58.MG_M3P300={4,15,46,2606}
-ENUMS.Storage.weapons.OH58.MG_M3P400={4,15,46,2605}
-ENUMS.Storage.weapons.OH58.MG_M3P500={4,15,46,2604}
-ENUMS.Storage.weapons.OH58.Smk_Grenade_Blue={4,5,9,486}
-ENUMS.Storage.weapons.OH58.Smk_Grenade_Green={4,5,9,487}
-ENUMS.Storage.weapons.OH58.Smk_Grenade_Red={4,5,9,485}
-ENUMS.Storage.weapons.OH58.Smk_Grenade_Violet={4,5,9,488}
-ENUMS.Storage.weapons.OH58.Smk_Grenade_White={4,5,9,490}
-ENUMS.Storage.weapons.OH58.Smk_Grenade_Yellow={4,5,9,489}
-ENUMS.Storage.weapons.AH64D.AN_APG78={4,15,44,2138}
+ENUMS.Storage.weapons.OH58.MG_M3P100={4,15,46,2611}
+ENUMS.Storage.weapons.OH58.MG_M3P200={4,15,46,2610}
+ENUMS.Storage.weapons.OH58.MG_M3P300={4,15,46,2609}
+ENUMS.Storage.weapons.OH58.MG_M3P400={4,15,46,2608}
+ENUMS.Storage.weapons.OH58.MG_M3P500={4,15,46,2607}
+ENUMS.Storage.weapons.OH58.Smk_Grenade_Blue={4,5,9,488}
+ENUMS.Storage.weapons.OH58.Smk_Grenade_Green={4,5,9,489}
+ENUMS.Storage.weapons.OH58.Smk_Grenade_Red={4,5,9,487}
+ENUMS.Storage.weapons.OH58.Smk_Grenade_Violet={4,5,9,490}
+ENUMS.Storage.weapons.OH58.Smk_Grenade_White={4,5,9,492}
+ENUMS.Storage.weapons.OH58.Smk_Grenade_Yellow={4,5,9,491}
+ENUMS.Storage.weapons.AH64D.AN_APG78={4,15,44,2114}
ENUMS.Storage.weapons.AH64D.Internal_Aux_FuelTank={1,3,43,1700}
+ENUMS.Storage.weapons.droptanks.FuelTank_610gal={1,3,43,10}
+ENUMS.Storage.weapons.droptanks.FuelTank_370gal={1,3,43,11}
+ENUMS.Storage.weapons.containers.AV8BNA_GAU_12_AP_M79={4,15,46,824}
+ENUMS.Storage.weapons.containers.AV8BNA_GAU_12_HE_M792={4,15,46,825}
+ENUMS.Storage.weapons.containers.AV8BNA_GAU_12_SAPHEI_T={4,15,46,300}
ENUMS.FARPType={
FARP="FARP",
INVISIBLE="INVISIBLE",
@@ -1103,6 +1227,8 @@ Falklands="Falklands",
Sinai="SinaiMap",
Kola="Kola",
Afghanistan="Afghanistan",
+Iraq="Iraq",
+GermanyCW="GermanyCW",
}
CALLSIGN={
Aircraft={
@@ -1305,7 +1431,7 @@ local objectreturn=_copy(object)
return objectreturn
end
UTILS.OneLineSerialize=function(tbl)
-lookup_table={}
+local lookup_table={}
local function _Serialize(tbl)
if type(tbl)=='table'then
if lookup_table[tbl]then
@@ -1430,7 +1556,7 @@ if not noprint then
env.info(string.rep(" ",indent)..tostring(k).." = {")
end
text=text..string.rep(" ",indent)..tostring(k).." = {\n"
-text=text..tostring(UTILS.PrintTableToLog(v,indent+1)).."\n"
+text=text..tostring(UTILS.PrintTableToLog(v,indent+1),noprint).."\n"
if not noprint then
env.info(string.rep(" ",indent).."},")
end
@@ -2063,7 +2189,7 @@ local delta=UTILS.VecAngle(v1,v2)
return math.abs(delta)
end
function UTILS.HdgTo(a,b)
-local dz=b.z-a.z
+local dz=(b.z or b.y)-(a.z or a.y)
local dx=b.x-a.x
local heading=math.deg(math.atan2(dz,dx))
if heading<0 then
@@ -2175,6 +2301,10 @@ elseif map==DCSMAP.Kola then
declination=15
elseif map==DCSMAP.Afghanistan then
declination=3
+elseif map==DCSMAP.Iraq then
+declination=4.4
+elseif map==DCSMAP.GermanyCW then
+declination=0.1
else
declination=0
end
@@ -2248,6 +2378,11 @@ end
end
function UTILS.GetReportingName(Typename)
local typename=string.lower(Typename)
+if string.find(typename,"ka-50",1,true)then
+return"Shark"
+elseif string.find(typename,"a-50",1,true)then
+return"Mainstay"
+end
for name,value in pairs(ENUMS.ReportingName.NATO)do
local svalue=string.lower(value)
if string.find(typename,svalue,1,true)then
@@ -2348,6 +2483,10 @@ elseif theatre==DCSMAP.Kola then
return 3
elseif theatre==DCSMAP.Afghanistan then
return 4.5
+elseif theatre==DCSMAP.Iraq then
+return 3.0
+elseif theatre==DCSMAP.GermanyCW then
+return 1.0
else
BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0",tostring(theatre)))
return 0
@@ -2408,9 +2547,9 @@ local sinDec=0.39782*sin(L)
local cosDec=cos(asin(sinDec))
local cosH=(cos(zenith)-(sinDec*sin(latitude)))/(cosDec*cos(latitude))
if rising and cosH>1 then
-return"N/S"
-elseif cosH<-1 then
return"N/R"
+elseif cosH<-1 then
+return"N/S"
end
local H
if rising then
@@ -2540,7 +2679,9 @@ if type_name=="CH-47Fbl1"and(unit:getDrawArgumentValue(86)>0.5)then
BASE:T(unit_name.." rear cargo door is open")
return true
end
-return false
+local UnitDescriptor=unit:getDesc()
+local IsGroundResult=(UnitDescriptor.category==Unit.Category.GROUND_UNIT)
+return IsGroundResult
end
return nil
end
@@ -3719,7 +3860,7 @@ end
UTILS.lcg.seed=(UTILS.lcg.a*UTILS.lcg.seed+UTILS.lcg.c)%UTILS.lcg.m
return UTILS.lcg.seed/UTILS.lcg.m
end
-function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment)
+function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart)
local farplocation=Coordinate
local farptype=FARPType or ENUMS.FARPType.FARP
local Coalition=Coalition or coalition.side.BLUE
@@ -3731,6 +3872,7 @@ if radius<0 or radius>150 then radius=100 end
local liquids=Liquids or 10
liquids=liquids*1000
local equip=Equipment or 10
+local airframes=Airframes or 10
local statictypes=ENUMS.FARPObjectTypeNamesAndShape[farptype]or{TypeName="FARP",ShapeName="FARPS"}
local STypeName=statictypes.TypeName
local SShapeName=statictypes.ShapeName
@@ -3738,7 +3880,7 @@ local Country=Country or(Coalition==coalition.side.BLUE and country.id.USA or co
local ReturnObjects={}
local newfarp=SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country)
newfarp:InitShape(SShapeName)
-newfarp:InitFARP(callsign,freq,mod)
+newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart)
local spawnedfarp=newfarp:SpawnFromCoordinate(farplocation,0,Name)
table.insert(ReturnObjects,spawnedfarp)
local FARPStaticObjectsNato={
@@ -3783,6 +3925,11 @@ newWH:SetItem(item,equip)
end
end
end
+if airframes and airframes>0 then
+for typename in pairs(CSAR.AircraftType)do
+newWH:SetItem(typename,airframes)
+end
+end
local ADFName
if ADF and type(ADF)=="number"then
local ADFFreq=ADF*1000
@@ -3791,7 +3938,484 @@ local vec3=farplocation:GetVec3()
ADFName=Name.." ADF "..tostring(ADF).."KHz"
trigger.action.radioTransmission(Sound,vec3,0,true,ADFFreq,250,ADFName)
end
-return ReturnObjects,ADFName
+local MarkerID=nil
+if F10Text then
+local Color={0,0,1}
+if Coalition==coalition.side.RED then
+Color={1,0,0}
+elseif Coalition==coalition.side.NEUTRAL then
+Color={0,1,0}
+end
+local Alpha=0.75
+local coordinate=Coordinate:Translate(600,0)
+MarkerID=coordinate:TextToAll(F10Text,Coalition,Color,1,{1,1,1},Alpha,14,true)
+end
+return ReturnObjects,ADFName,MarkerID
+end
+function UTILS.SpawnMASHStatics(Name,Coordinate,Country,ADF,Livery,DeployHelo,MASHRadio,MASHRadioModulation,MASHCallsign,Templates)
+local MASHTemplates={
+[1]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.000000,y=0.000000,},
+[2]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.313533,y=8.778935,},
+[3]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=16.303737,y=20.379671,},
+[4]={category='Helicopters',type='CH-47Fbl1',shape_name='none',heading=0,x=-20.047735,y=-63.166179,livery_id="us army dark green",},
+[5]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=26.650339,y=20.066138,},
+[6]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.432292,y=9.077099,},
+[7]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=-3.216114,},
+[8]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.439281,y=-3.216114,},
+[9]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=9.155603,},
+[10]={category='Fortifications',type='TACAN_beacon',shape_name='none',heading=0,x=-2.329847,y=-16.579903,},
+[11]={category='Fortifications',type='FARP Fuel Depot',shape_name='GSM Rus',heading=0,x=2.222011,y=4.487030,},
+[12]={category='Fortifications',type='APFC fuel',shape_name='M92_APFCfuel',heading=0,x=3.614927,y=0.367838,},
+[13]={category='Fortifications',type='Camouflage03',shape_name='M92_Camouflage03',heading=0,x=21.544148,y=21.998879,},
+[14]={category='Fortifications',type='Container_generator',shape_name='M92_Container_generator',heading=0,x=20.989192,y=37.314334,},
+[15]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=3.988003,y=8.362333,},
+[16]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=-3.953195,y=12.945844,},
+[17]={category='Fortifications',type='Windsock',shape_name='H-Windsock_RW',heading=0,x=-18.944173,y=-33.042196,},
+[18]={category='Fortifications',type='Tent04',shape_name='M92_Tent04',heading=0,x=21.220671,y=30.247529,},
+}
+if Templates then MASHTemplates=Templates end
+local name=Name or"Florence Nightingale"
+local positionVec2
+local positionVec3
+local ReturnStatics={}
+local CountryID=Country or country.id.USA
+local livery="us army dark green"
+local MASHRadio=MASHRadio or 127.5
+local MASHRadioModulation=MASHRadioModulation or radio.modulation.AM
+local MASHCallsign=MASHCallsign or CALLSIGN.FARP.Berlin
+if type(Coordinate)=="table"then
+if Coordinate:IsInstanceOf("COORDINATE")or Coordinate:IsInstanceOf("ZONE_BASE")then
+positionVec2=Coordinate:GetVec2()
+positionVec3=Coordinate:GetVec3()
+end
+else
+BASE:E("Spawn MASH - no ZONE or COORDINATE handed!")
+return
+end
+local BaseX=positionVec2.x
+local BaseY=positionVec2.y
+for id,object in pairs(MASHTemplates)do
+local NewName=string.format("%s#%3d",name,id)
+local vec2={x=BaseX+object.x,y=BaseY+object.y}
+local Coordinate=COORDINATE:NewFromVec2(vec2)
+local static=SPAWNSTATIC:NewFromType(object.type,object.category,CountryID)
+if object.shape_name and object.shape_name~="none"then
+static:InitShape(object.shape_name)
+end
+if object.category=="Helicopters"and DeployHelo==true then
+if object.livery_id~=nil then
+livery=object.livery_id
+end
+static:InitLivery(livery)
+local newstatic=static:SpawnFromCoordinate(Coordinate,object.heading,NewName)
+table.insert(ReturnStatics,newstatic)
+elseif object.category=="Heliports"then
+static:InitFARP(MASHCallsign,MASHRadio,MASHRadioModulation,false,false)
+local newstatic=static:SpawnFromCoordinate(Coordinate,object.heading,NewName)
+table.insert(ReturnStatics,newstatic)
+elseif object.category~="Helicopters"and object.category~="Heliports"then
+local newstatic=static:SpawnFromCoordinate(Coordinate,object.heading,NewName)
+table.insert(ReturnStatics,newstatic)
+end
+end
+local ADFName
+if ADF and type(ADF)=="number"then
+local ADFFreq=ADF*1000
+local Sound="l10n/DEFAULT/beacon.ogg"
+ADFName=Name.." ADF "..tostring(ADF).."KHz"
+trigger.action.radioTransmission(Sound,positionVec3,0,true,ADFFreq,250,ADFName)
+end
+return ReturnStatics,ADFName
+end
+function UTILS.Vec2toVec3(vec,y)
+if not vec.z then
+if vec.alt and not y then
+y=vec.alt
+elseif not y then
+y=0
+end
+return{x=vec.x,y=y,z=vec.y}
+else
+return{x=vec.x,y=vec.y,z=vec.z}
+end
+end
+function UTILS.GetNorthCorrection(gPoint)
+local point=UTILS.DeepCopy(gPoint)
+if not point.z then
+point.z=point.y
+point.y=0
+end
+local lat,lon=coord.LOtoLL(point)
+local north_posit=coord.LLtoLO(lat+1,lon)
+return math.atan2(north_posit.z-point.z,north_posit.x-point.x)
+end
+function UTILS.GetDHMS(timeInSec)
+if timeInSec and type(timeInSec)=='number'then
+local tbl={d=0,h=0,m=0,s=0}
+if timeInSec>86400 then
+while timeInSec>86400 do
+tbl.d=tbl.d+1
+timeInSec=timeInSec-86400
+end
+end
+if timeInSec>3600 then
+while timeInSec>3600 do
+tbl.h=tbl.h+1
+timeInSec=timeInSec-3600
+end
+end
+if timeInSec>60 then
+while timeInSec>60 do
+tbl.m=tbl.m+1
+timeInSec=timeInSec-60
+end
+end
+tbl.s=timeInSec
+return tbl
+else
+BASE:E("No number handed!")
+return
+end
+end
+function UTILS.GetDirectionRadians(vec,point)
+local dir=math.atan2(vec.z,vec.x)
+if point then
+dir=dir+UTILS.GetNorthCorrection(point)
+end
+if dir<0 then
+dir=dir+2*math.pi
+end
+return dir
+end
+function UTILS.IsPointInPolygon(point,poly,maxalt)
+point=UTILS.Vec2toVec3(point)
+local px=point.x
+local pz=point.z
+local cn=0
+local newpoly=UTILS.DeepCopy(poly)
+if not maxalt or(point.y<=maxalt)then
+local polysize=#newpoly
+newpoly[#newpoly+1]=newpoly[1]
+newpoly[1]=UTILS.Vec2toVec3(newpoly[1])
+for k=1,polysize do
+newpoly[k+1]=UTILS.Vec2toVec3(newpoly[k+1])
+if((newpoly[k].z<=pz)and(newpoly[k+1].z>pz))or((newpoly[k].z>pz)and(newpoly[k+1].z<=pz))then
+local vt=(pz-newpoly[k].z)/(newpoly[k+1].z-newpoly[k].z)
+if(px5000 then value=5000 end
+return world.weather.setFogThickness(value)
+end
+function UTILS.Weather.RemoveFog()
+return world.weather.setFogThickness(0)
+end
+function UTILS.Weather.GetFogVisibilityDistanceMax()
+return world.weather.getFogVisibilityDistance()
+end
+function UTILS.Weather.SetFogVisibilityDistance(Thickness)
+local value=Thickness
+if value<100 then value=100
+elseif value>100000 then value=100000 end
+return world.weather.setFogVisibilityDistance(value)
+end
+function UTILS.Weather.SetFogAnimation(AnimationKeys)
+return world.weather.setFogAnimation(AnimationKeys)
+end
+function UTILS.Weather.StopFogAnimation()
+return world.weather.setFogAnimation({})
+end
+function UTILS.GetEnvZone(name)
+for _,v in ipairs(env.mission.triggers.zones)do
+if v.name==name then
+return v
+end
+end
+end
+function UTILS.DoStringIn(State,DoString)
+return net.dostring_in(State,DoString)
+end
+function UTILS.ShowPictureToAll(FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)
+ClearView=ClearView or false
+StartDelay=StartDelay or 0
+HorizontalAlign=HorizontalAlign or 1
+VerticalAlign=VerticalAlign or 1
+Size=Size or 100
+SizeUnits=SizeUnits or 0
+if ClearView then ClearView="true"else ClearView="false"end
+net.dostring_in("mission",string.format("a_out_picture(\"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits))
+end
+function UTILS.ShowPictureToCoalition(Coalition,FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)
+ClearView=ClearView or false
+StartDelay=StartDelay or 0
+HorizontalAlign=HorizontalAlign or 1
+VerticalAlign=VerticalAlign or 1
+Size=Size or 100
+SizeUnits=SizeUnits or 0
+if ClearView then ClearView="true"else ClearView="false"end
+local coalName=string.lower(UTILS.GetCoalitionName(Coalition))
+net.dostring_in("mission",string.format("a_out_picture_s(\"%s\", \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",coalName,FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits))
+end
+function UTILS.ShowPictureToCountry(Country,FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)
+ClearView=ClearView or false
+StartDelay=StartDelay or 0
+HorizontalAlign=HorizontalAlign or 1
+VerticalAlign=VerticalAlign or 1
+Size=Size or 100
+SizeUnits=SizeUnits or 0
+if ClearView then ClearView="true"else ClearView="false"end
+net.dostring_in("mission",string.format("a_out_picture_c(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",Country,FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits))
+end
+function UTILS.ShowPictureToGroup(Group,FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)
+ClearView=ClearView or false
+StartDelay=StartDelay or 0
+HorizontalAlign=HorizontalAlign or 1
+VerticalAlign=VerticalAlign or 1
+Size=Size or 100
+SizeUnits=SizeUnits or 0
+if ClearView then ClearView="true"else ClearView="false"end
+net.dostring_in("mission",string.format("a_out_picture_g(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",Group:GetID(),FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits))
+end
+function UTILS.ShowPictureToUnit(Unit,FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)
+ClearView=ClearView or false
+StartDelay=StartDelay or 0
+HorizontalAlign=HorizontalAlign or 1
+VerticalAlign=VerticalAlign or 1
+Size=Size or 100
+SizeUnits=SizeUnits or 0
+if ClearView then ClearView="true"else ClearView="false"end
+net.dostring_in("mission",string.format("a_out_picture_u(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",Unit:GetID(),FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits))
+end
+function UTILS.LoadMission(FileName)
+net.dostring_in("mission",string.format("a_load_mission(\"%s\")",FileName))
+end
+function UTILS.SetMissionBriefing(Coalition,Text,Picture)
+Text=Text or""
+Text=Text:gsub("\n","\\n")
+Picture=Picture or""
+local coalName=string.lower(UTILS.GetCoalitionName(Coalition))
+net.dostring_in("mission",string.format("a_set_briefing(\"%s\", \"%s\", \"%s\")",coalName,Picture,Text))
+end
+function UTILS.ShowHelperGate(pos,heading)
+net.dostring_in("mission",string.format("a_show_helper_gate(%s, %s, %s, %f)",pos.x,pos.y,pos.z,math.rad(heading)))
+end
+function UTILS.ShowHelperGateForUnit(Unit,Flag)
+net.dostring_in("mission",string.format("a_show_route_gates_for_unit(%d, \"%d\")",Unit:GetID(),Flag))
+end
+function UTILS.SetCarrierIlluminationMode(UnitID,Mode)
+net.dostring_in("mission",string.format("a_set_carrier_illumination_mode(%d, %d)",UnitID,Mode))
+end
+function UTILS.ShellZone(name,power,count)
+local z=UTILS.GetEnvZone(name)
+if z then
+net.dostring_in("mission",string.format("a_shelling_zone(%d, %d, %d)",z.zoneId,power,count))
+end
+end
+function UTILS.RemoveObjects(name,type)
+local z=UTILS.GetEnvZone(name)
+if z then
+net.dostring_in("mission",string.format("a_remove_scene_objects(%d, %d)",z.zoneId,type))
+end
+end
+function UTILS.DestroyScenery(name,level)
+local z=UTILS.GetEnvZone(name)
+if z then
+net.dostring_in("mission",string.format("a_scenery_destruction_zone(%d, %d)",z.zoneId,level))
+end
+end
+function UTILS.GetSimpleZones(Vec3,SearchRadius,PosRadius,NumPositions)
+return Disposition.getSimpleZones(Vec3,SearchRadius,PosRadius,NumPositions)
+end
+function UTILS.GetClearZonePositions(Zone,PosRadius,NumPositions)
+local radius=PosRadius or math.min(Zone:GetRadius()/10,200)
+local clearPositions=UTILS.GetSimpleZones(Zone:GetVec3(),Zone:GetRadius(),radius,NumPositions or 50)
+if clearPositions and#clearPositions>0 then
+local validZones={}
+for _,vec2 in pairs(clearPositions)do
+if Zone:IsVec2InZone(vec2)then
+table.insert(validZones,vec2)
+end
+end
+if#validZones>0 then
+return validZones,radius
+end
+end
+return nil
+end
+function UTILS.GetRandomClearZoneCoordinate(Zone,PosRadius,NumPositions)
+local clearPositions=UTILS.GetClearZonePositions(Zone,PosRadius,NumPositions)
+if clearPositions and#clearPositions>0 then
+local randomPosition,radius=clearPositions[math.random(1,#clearPositions)]
+return COORDINATE:NewFromVec2(randomPosition),radius
+end
+return nil
+end
+function UTILS.FindNearestPointOnCircle(Vec1,Radius,Vec2)
+local r=Radius
+local cx=Vec1.x or 1
+local cy=Vec1.y or 1
+local px=Vec2.x or 1
+local py=Vec2.y or 1
+local dx=px-cx
+local dy=py-cy
+local dist=math.sqrt(dx*dx+dy*dy)
+if dist==0 then
+return{x=cx+r,y=cy}
+end
+local norm_dx=dx/dist
+local norm_dy=dy/dist
+local qx=cx+r*norm_dx
+local qy=cy+r*norm_dy
+local shift_factor=1
+qx=qx+shift_factor*norm_dx
+qy=qy+shift_factor*norm_dy
+return{x=qx,y=qy}
+end
+function UTILS.ValidateAndRepositionGroundUnits(Positions,Anchor,MaxRadius,Spacing)
+local units=Positions
+Anchor=Anchor or UTILS.GetCenterPoint(units)
+local gPos={x=Anchor.x,y=Anchor.z or Anchor.y}
+local maxRadius=0
+local unitCount=0
+for _,unit in pairs(units)do
+local pos={x=unit.x,y=unit.z or unit.y}
+local dist=UTILS.VecDist2D(pos,gPos)
+if dist>maxRadius then
+maxRadius=dist
+end
+unitCount=unitCount+1
+end
+maxRadius=MaxRadius or math.max(maxRadius*2,10)
+local spacing=Spacing or math.max(maxRadius*0.05,5)
+if unitCount>0 and maxRadius>5 then
+local spots=UTILS.GetSimpleZones(UTILS.Vec2toVec3(gPos),maxRadius,spacing,1000)
+if spots and#spots>0 then
+local validSpots={}
+for _,spot in pairs(spots)do
+if land.getSurfaceType(spot)==land.SurfaceType.LAND then
+table.insert(validSpots,spot)
+end
+end
+spots=validSpots
+end
+local step=spacing
+for _,unit in pairs(units)do
+local pos={x=unit.x,y=unit.z or unit.y}
+local isOnLand=land.getSurfaceType(pos)==land.SurfaceType.LAND
+local isValid=false
+if spots and#spots>0 then
+local si=1
+local sid=0
+local closestDist=100000000
+local closestSpot
+for _,spot in pairs(spots)do
+local dist=UTILS.VecDist2D(pos,spot)
+if dist=spacing then
+pos=closestSpot
+end
+isValid=true
+table.remove(spots,sid)
+end
+end
+if not isValid and not isOnLand then
+local h=UTILS.HdgTo(pos,gPos)
+local retries=0
+while not isValid and retries<500 do
+local dist=UTILS.VecDist2D(pos,gPos)
+pos=UTILS.Vec2Translate(pos,step,h)
+local skip=false
+for _,unit2 in pairs(units)do
+if unit~=unit2 then
+local pos2={x=unit2.x,y=unit2.z or unit2.y}
+local dist2=UTILS.VecDist2D(pos,pos2)
+if dist2<12 then
+isValid=false
+skip=true
+break
+end
+end
+end
+if not skip and dist>step and land.getSurfaceType(pos)==land.SurfaceType.LAND then
+isValid=true
+break
+elseif dist<=step then
+break
+end
+retries=retries+1
+end
+end
+if isValid then
+unit.x=pos.x
+if unit.z then
+unit.z=pos.y
+else
+unit.y=pos.y
+end
+end
+end
+end
+end
+function UTILS.ValidateAndRepositionStatic(Country,Category,Type,Position,ShapeName,MaxRadius)
+local coord=COORDINATE:NewFromVec2(Position)
+local st=SPAWNSTATIC:NewFromType(Type,Category,Country)
+if ShapeName then
+st:InitShape(ShapeName)
+end
+local sName="s-"..timer.getTime().."-"..math.random(1,10000)
+local tempStatic=st:SpawnFromCoordinate(coord,0,sName)
+if tempStatic then
+local sRadius=tempStatic:GetBoundingRadius(2)or 3
+tempStatic:Destroy()
+sRadius=sRadius*0.5
+MaxRadius=MaxRadius or math.max(sRadius*10,100)
+local positions=UTILS.GetSimpleZones(coord:GetVec3(),MaxRadius,sRadius,20)
+if positions and#positions>0 then
+local closestSpot
+local closestDist=math.huge
+for _,spot in pairs(positions)do
+if land.getSurfaceType(spot)==land.SurfaceType.LAND then
+local dist=UTILS.VecDist2D(Position,spot)
+if dist=sRadius then
+return closestSpot
+else
+return Position
+end
+end
+end
+end
+return nil
end
PROFILER={
ClassName="PROFILER",
@@ -4066,500 +4690,6 @@ PROFILER._flog(f,"**************************************************************
f:close()
PROFILER.printCSV(t,runTimeGame)
end
-TEMPLATE={
-ClassName="TEMPLATE",
-Ground={},
-Naval={},
-Airplane={},
-Helicopter={},
-}
-TEMPLATE.TypeGround={
-InfantryAK="Infantry AK",
-ParatrooperAKS74="Paratrooper AKS-74",
-ParatrooperRPG16="Paratrooper RPG-16",
-SoldierWWIIUS="soldier_wwii_us",
-InfantryM248="Infantry M249",
-SoldierM4="Soldier M4",
-}
-TEMPLATE.TypeNaval={
-Ticonderoga="TICONDEROG",
-}
-TEMPLATE.TypeAirplane={
-A10C="A-10C",
-}
-TEMPLATE.TypeHelicopter={
-AH1W="AH-1W",
-}
-function TEMPLATE.GetGround(TypeName,GroupName,CountryID,Vec3,Nunits,Radius)
-TypeName=TypeName or TEMPLATE.TypeGround.SoldierM4
-GroupName=GroupName or"Ground-1"
-CountryID=CountryID or country.id.USA
-Vec3=Vec3 or{x=0,y=0,z=0}
-Nunits=Nunits or 1
-Radius=Radius or 50
-local template=UTILS.DeepCopy(TEMPLATE.GenericGround)
-template.name=GroupName
-template.CountryID=CountryID
-template.CoalitionID=coalition.getCountryCoalition(template.CountryID)
-template.CategoryID=Unit.Category.GROUND_UNIT
-template.units[1].type=TypeName
-template.units[1].name=GroupName.."-1"
-if Vec3 then
-TEMPLATE.SetPositionFromVec3(template,Vec3)
-end
-TEMPLATE.SetUnits(template,Nunits,COORDINATE:NewFromVec3(Vec3),Radius)
-return template
-end
-function TEMPLATE.GetNaval(TypeName,GroupName,CountryID,Vec3,Nunits,Radius)
-TypeName=TypeName or TEMPLATE.TypeNaval.Ticonderoga
-GroupName=GroupName or"Naval-1"
-CountryID=CountryID or country.id.USA
-Vec3=Vec3 or{x=0,y=0,z=0}
-Nunits=Nunits or 1
-Radius=Radius or 500
-local template=UTILS.DeepCopy(TEMPLATE.GenericNaval)
-template.name=GroupName
-template.CountryID=CountryID
-template.CoalitionID=coalition.getCountryCoalition(template.CountryID)
-template.CategoryID=Unit.Category.SHIP
-template.units[1].type=TypeName
-template.units[1].name=GroupName.."-1"
-if Vec3 then
-TEMPLATE.SetPositionFromVec3(template,Vec3)
-end
-TEMPLATE.SetUnits(template,Nunits,COORDINATE:NewFromVec3(Vec3),Radius)
-return template
-end
-function TEMPLATE.GetAirplane(TypeName,GroupName,CountryID,Vec3,Nunits,Radius)
-TypeName=TypeName or TEMPLATE.TypeAirplane.A10C
-GroupName=GroupName or"Airplane-1"
-CountryID=CountryID or country.id.USA
-Vec3=Vec3 or{x=0,y=1000,z=0}
-Nunits=Nunits or 1
-Radius=Radius or 100
-local template=TEMPLATE._GetAircraft(true,TypeName,GroupName,CountryID,Vec3,Nunits,Radius)
-return template
-end
-function TEMPLATE.GetHelicopter(TypeName,GroupName,CountryID,Vec3,Nunits,Radius)
-TypeName=TypeName or TEMPLATE.TypeHelicopter.AH1W
-GroupName=GroupName or"Helicopter-1"
-CountryID=CountryID or country.id.USA
-Vec3=Vec3 or{x=0,y=500,z=0}
-Nunits=Nunits or 1
-Radius=Radius or 100
-Nunits=math.min(Nunits,4)
-local template=TEMPLATE._GetAircraft(false,TypeName,GroupName,CountryID,Vec3,Nunits,Radius)
-return template
-end
-function TEMPLATE._GetAircraft(Airplane,TypeName,GroupName,CountryID,Vec3,Nunits,Radius)
-TypeName=TypeName
-GroupName=GroupName or"Aircraft-1"
-CountryID=CountryID or country.id.USA
-Vec3=Vec3 or{x=0,y=0,z=0}
-Nunits=Nunits or 1
-Radius=Radius or 100
-local template=UTILS.DeepCopy(TEMPLATE.GenericAircraft)
-template.name=GroupName
-template.CountryID=CountryID
-template.CoalitionID=coalition.getCountryCoalition(template.CountryID)
-if Airplane then
-template.CategoryID=Unit.Category.AIRPLANE
-else
-template.CategoryID=Unit.Category.HELICOPTER
-end
-template.units[1].type=TypeName
-template.units[1].name=GroupName.."-1"
-if Vec3 then
-TEMPLATE.SetPositionFromVec3(template,Vec3)
-end
-TEMPLATE.SetUnits(template,Nunits,COORDINATE:NewFromVec3(Vec3),Radius)
-return template
-end
-function TEMPLATE.SetPositionFromVec2(Template,Vec2)
-Template.x=Vec2.x
-Template.y=Vec2.y
-for _,unit in pairs(Template.units)do
-unit.x=Vec2.x
-unit.y=Vec2.y
-end
-Template.route.points[1].x=Vec2.x
-Template.route.points[1].y=Vec2.y
-Template.route.points[1].alt=0
-end
-function TEMPLATE.SetPositionFromVec3(Template,Vec3)
-local Vec2={x=Vec3.x,y=Vec3.z}
-TEMPLATE.SetPositionFromVec2(Template,Vec2)
-end
-function TEMPLATE.SetUnits(Template,N,Coordinate,Radius)
-local units=Template.units
-local unit1=units[1]
-local Vec3=Coordinate:GetVec3()
-unit1.x=Vec3.x
-unit1.y=Vec3.z
-unit1.alt=Vec3.y
-for i=2,N do
-units[i]=UTILS.DeepCopy(unit1)
-end
-for i=1,N do
-local unit=units[i]
-unit.name=string.format("%s-%d",Template.name,i)
-if i>1 then
-local vec2=Coordinate:GetRandomCoordinateInRadius(Radius,5):GetVec2()
-unit.x=vec2.x
-unit.y=vec2.y
-unit.alt=unit1.alt
-end
-end
-end
-function TEMPLATE.SetAirbase(Template,AirBase,ParkingSpots,EngineOn)
-local AirbaseID=AirBase:GetID()
-local point=Template.route.points[1]
-if AirBase:IsAirdrome()then
-point.airdromeId=AirbaseID
-else
-point.helipadId=AirbaseID
-point.linkUnit=AirbaseID
-end
-if EngineOn then
-point.action=COORDINATE.WaypointAction.FromParkingAreaHot
-point.type=COORDINATE.WaypointType.TakeOffParkingHot
-else
-point.action=COORDINATE.WaypointAction.FromParkingArea
-point.type=COORDINATE.WaypointType.TakeOffParking
-end
-for i,unit in ipairs(Template.units)do
-unit.parking_id=ParkingSpots[i]
-end
-end
-function TEMPLATE.AddWaypoint(Template,Waypoint)
-table.insert(Template.route.points,Waypoint)
-end
-TEMPLATE.GenericGround=
-{
-["visible"]=false,
-["tasks"]={},
-["uncontrollable"]=false,
-["task"]="Ground Nothing",
-["route"]=
-{
-["spans"]={},
-["points"]=
-{
-[1]=
-{
-["alt"]=0,
-["type"]="Turning Point",
-["ETA"]=0,
-["alt_type"]="BARO",
-["formation_template"]="",
-["y"]=0,
-["x"]=0,
-["ETA_locked"]=true,
-["speed"]=0,
-["action"]="Off Road",
-["task"]=
-{
-["id"]="ComboTask",
-["params"]=
-{
-["tasks"]=
-{
-},
-},
-},
-["speed_locked"]=true,
-},
-},
-},
-["groupId"]=nil,
-["hidden"]=false,
-["units"]=
-{
-[1]=
-{
-["transportable"]=
-{
-["randomTransportable"]=false,
-},
-["skill"]="Average",
-["type"]="Infantry AK",
-["unitId"]=nil,
-["y"]=0,
-["x"]=0,
-["name"]="Infantry AK-47 Rus",
-["heading"]=0,
-["playerCanDrive"]=false,
-},
-},
-["y"]=0,
-["x"]=0,
-["name"]="Infantry AK-47 Rus",
-["start_time"]=0,
-}
-TEMPLATE.GenericNaval=
-{
-["visible"]=false,
-["tasks"]={},
-["uncontrollable"]=false,
-["route"]=
-{
-["points"]=
-{
-[1]=
-{
-["alt"]=0,
-["type"]="Turning Point",
-["ETA"]=0,
-["alt_type"]="BARO",
-["formation_template"]="",
-["y"]=0,
-["x"]=0,
-["ETA_locked"]=true,
-["speed"]=0,
-["action"]="Turning Point",
-["task"]=
-{
-["id"]="ComboTask",
-["params"]=
-{
-["tasks"]=
-{
-},
-},
-},
-["speed_locked"]=true,
-},
-},
-},
-["groupId"]=nil,
-["hidden"]=false,
-["units"]=
-{
-[1]=
-{
-["transportable"]=
-{
-["randomTransportable"]=false,
-},
-["skill"]="Average",
-["type"]="TICONDEROG",
-["unitId"]=nil,
-["y"]=0,
-["x"]=0,
-["name"]="Naval-1-1",
-["heading"]=0,
-["modulation"]=0,
-["frequency"]=127500000,
-},
-},
-["y"]=0,
-["x"]=0,
-["name"]="Naval-1",
-["start_time"]=0,
-}
-TEMPLATE.GenericAircraft=
-{
-["groupId"]=nil,
-["name"]="Rotary-1",
-["uncontrolled"]=false,
-["hidden"]=false,
-["task"]="Nothing",
-["y"]=0,
-["x"]=0,
-["start_time"]=0,
-["communication"]=true,
-["radioSet"]=false,
-["frequency"]=127.5,
-["modulation"]=0,
-["taskSelected"]=true,
-["tasks"]={},
-["route"]=
-{
-["points"]=
-{
-[1]=
-{
-["y"]=0,
-["x"]=0,
-["alt"]=1000,
-["alt_type"]="BARO",
-["action"]="Turning Point",
-["type"]="Turning Point",
-["airdromeId"]=nil,
-["task"]=
-{
-["id"]="ComboTask",
-["params"]=
-{
-["tasks"]={},
-},
-},
-["ETA"]=0,
-["ETA_locked"]=true,
-["speed"]=100,
-["speed_locked"]=true,
-["formation_template"]="",
-},
-},
-},
-["units"]=
-{
-[1]=
-{
-["name"]="Rotary-1-1",
-["unitId"]=nil,
-["type"]="AH-1W",
-["onboard_num"]="050",
-["livery_id"]="USA X Black",
-["skill"]="High",
-["ropeLength"]=15,
-["speed"]=0,
-["x"]=0,
-["y"]=0,
-["alt"]=10,
-["alt_type"]="BARO",
-["heading"]=0,
-["psi"]=0,
-["parking"]=nil,
-["parking_id"]=nil,
-["payload"]=
-{
-["pylons"]={},
-["fuel"]="1250.0",
-["flare"]=30,
-["chaff"]=30,
-["gun"]=100,
-},
-["callsign"]=
-{
-[1]=2,
-[2]=1,
-[3]=1,
-["name"]="Springfield11",
-},
-},
-},
-}
-STTS={
-ClassName="STTS",
-DIRECTORY="",
-SRS_PORT=5002,
-GOOGLE_CREDENTIALS="C:\\Users\\Ciaran\\Downloads\\googletts.json",
-EXECUTABLE="DCS-SR-ExternalAudio.exe"
-}
-STTS.DIRECTORY="D:/DCS/_SRS"
-STTS.SRS_PORT=5002
-STTS.GOOGLE_CREDENTIALS="C:\\Users\\Ciaran\\Downloads\\googletts.json"
-STTS.EXECUTABLE="DCS-SR-ExternalAudio.exe"
-function STTS.uuid()
-local random=math.random
-local template='yxxx-xxxxxxxxxxxx'
-return string.gsub(template,'[xy]',function(c)
-local v=(c=='x')and random(0,0xf)or random(8,0xb)
-return string.format('%x',v)
-end)
-end
-function STTS.round(x,n)
-n=math.pow(10,n or 0)
-x=x*n
-if x>=0 then
-x=math.floor(x+0.5)
-else
-x=math.ceil(x-0.5)
-end
-return x/n
-end
-function STTS.getSpeechTime(length,speed,isGoogle)
-local maxRateRatio=3
-speed=speed or 1.0
-isGoogle=isGoogle or false
-local speedFactor=1.0
-if isGoogle then
-speedFactor=speed
-else
-if speed~=0 then
-speedFactor=math.abs(speed)*(maxRateRatio-1)/10+1
-end
-if speed<0 then
-speedFactor=1/speedFactor
-end
-end
-local wpm=math.ceil(100*speedFactor)
-local cps=math.floor((wpm*5)/60)
-if type(length)=="string"then
-length=string.len(length)
-end
-return length/cps
-end
-function STTS.TextToSpeech(message,freqs,modulations,volume,name,coalition,point,speed,gender,culture,voice,googleTTS)
-if os==nil or io==nil then
-env.info("[DCS-STTS] LUA modules os or io are sanitized. skipping. ")
-return
-end
-speed=speed or 1
-gender=gender or"female"
-culture=culture or""
-voice=voice or""
-coalition=coalition or"0"
-name=name or"ROBOT"
-volume=1
-speed=1
-message=message:gsub("\"","\\\"")
-local cmd=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h",STTS.DIRECTORY,STTS.EXECUTABLE,freqs or"305",modulations or"AM",coalition,STTS.SRS_PORT,name)
-if voice~=""then
-cmd=cmd..string.format(" -V \"%s\"",voice)
-else
-if culture~=""then
-cmd=cmd..string.format(" -l %s",culture)
-end
-if gender~=""then
-cmd=cmd..string.format(" -g %s",gender)
-end
-end
-if googleTTS==true then
-cmd=cmd..string.format(" -G \"%s\"",STTS.GOOGLE_CREDENTIALS)
-end
-if speed~=1 then
-cmd=cmd..string.format(" -s %s",speed)
-end
-if volume~=1.0 then
-cmd=cmd..string.format(" -v %s",volume)
-end
-if point and type(point)=="table"and point.x then
-local lat,lon,alt=coord.LOtoLL(point)
-lat=STTS.round(lat,4)
-lon=STTS.round(lon,4)
-alt=math.floor(alt)
-cmd=cmd..string.format(" -L %s -O %s -A %s",lat,lon,alt)
-end
-cmd=cmd..string.format(" -t \"%s\"",message)
-if string.len(cmd)>255 then
-local filename=os.getenv('TMP').."\\DCS_STTS-"..STTS.uuid()..".bat"
-local script=io.open(filename,"w+")
-script:write(cmd.." && exit")
-script:close()
-cmd=string.format("\"%s\"",filename)
-timer.scheduleFunction(os.remove,filename,timer.getTime()+1)
-end
-if string.len(cmd)>255 then
-env.info("[DCS-STTS] - cmd string too long")
-env.info("[DCS-STTS] TextToSpeech Command :\n"..cmd.."\n")
-end
-os.execute(cmd)
-return STTS.getSpeechTime(message,speed,googleTTS)
-end
-function STTS.PlayMP3(pathToMP3,freqs,modulations,volume,name,coalition,point)
-local cmd=string.format("start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h",STTS.DIRECTORY,STTS.EXECUTABLE,pathToMP3,freqs or"305",modulations or"AM",coalition or"0",STTS.SRS_PORT,name or"ROBOT",volume or"1")
-if point and type(point)=="table"and point.x then
-local lat,lon,alt=coord.LOtoLL(point)
-lat=STTS.round(lat,4)
-lon=STTS.round(lon,4)
-alt=math.floor(alt)
-cmd=cmd..string.format(" -L %s -O %s -A %s",lat,lon,alt)
-end
-env.info("[DCS-STTS] MP3/OGG Command :\n"..cmd.."\n")
-os.execute(cmd)
-end
do
FIFO={
ClassName="FIFO",
@@ -5097,15 +5227,12 @@ Events={},
States={},
Debug=debug,
Scheduler=nil,
+Properties={},
}
BASE.__={}
BASE._={
Schedules={},
}
-FORMATION={
-Cone="Cone",
-Vee="Vee",
-}
function BASE:New()
local self=UTILS.DeepCopy(self)
_ClassID=_ClassID+1
@@ -5397,6 +5524,17 @@ if self.States[ClassNameAndID]then
self.States[ClassNameAndID][StateName]=nil
end
end
+function BASE:SetProperty(Key,Value)
+self.Properties=self.Properties or{}
+self.Properties[Key]=Value
+end
+function BASE:GetProperty(Key)
+self.Properties=self.Properties or{}
+return self.Properties[Key]
+end
+function BASE:GetProperties()
+return self.Properties
+end
function BASE:TraceOn()
self:TraceOnOff(true)
end
@@ -6558,7 +6696,7 @@ local Source=Info.source or"?"
local Line=Info.currentline or"?"
local Name=Info.name or"?"
local ErrorHandler=function(errmsg)
-env.info("Error in timer function: "..errmsg)
+env.info("Error in timer function: "..errmsg or"")
if BASE.Debug~=nil then
env.info(BASE.Debug.traceback())
end
@@ -6647,7 +6785,7 @@ function SCHEDULEDISPATCHER:Stop(Scheduler,CallID)
self:F2({Stop=CallID,Scheduler=Scheduler})
if CallID then
local Schedule=self.Schedule[Scheduler][CallID]
-if Schedule.ScheduleID then
+if Schedule and Schedule.ScheduleID then
self:T(string.format("SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s",tostring(CallID),tostring(Schedule.ScheduleID)))
timer.removeFunction(Schedule.ScheduleID)
Schedule.ScheduleID=nil
@@ -7490,11 +7628,12 @@ Event.IniCategory=Event.IniDCSUnit:getDesc().category
Event.IniTypeName=Event.IniDCSUnit:getTypeName()
elseif Event.IniObjectCategory==Object.Category.SCENERY then
Event.IniDCSUnit=Event.initiator
-Event.IniDCSUnitName=Event.IniDCSUnit.getName and Event.IniDCSUnit:getName()or"Scenery no name "..math.random(1,20000)
+Event.IniDCSUnitName=(Event.IniDCSUnit and Event.IniDCSUnit.getName)and Event.IniDCSUnit:getName()or"Scenery no name "..math.random(1,20000)
Event.IniUnitName=Event.IniDCSUnitName
Event.IniUnit=SCENERY:Register(Event.IniDCSUnitName,Event.initiator)
-Event.IniCategory=Event.IniDCSUnit:getDesc().category
-Event.IniTypeName=Event.initiator:isExist()and Event.IniDCSUnit:getTypeName()or"SCENERY"
+Event.IniCategory=(Event.IniDCSUnit and Event.IniDCSUnit.getDesc)and Event.IniDCSUnit:getDesc().category
+Event.IniTypeName=(Event.initiator and Event.initiator.isExist
+and Event.initiator:isExist()and Event.IniDCSUnit and Event.IniDCSUnit.getTypeName)and Event.IniDCSUnit:getTypeName()or"SCENERY"
elseif Event.IniObjectCategory==Object.Category.BASE then
Event.IniDCSUnit=Event.initiator
Event.IniDCSUnitName=Event.IniDCSUnit:getName()
@@ -7564,14 +7703,15 @@ end
end
elseif Event.TgtObjectCategory==Object.Category.SCENERY then
Event.TgtDCSUnit=Event.target
-Event.TgtDCSUnitName=Event.TgtDCSUnit:getName()
-if Event.TgtDCSUnitName==nil then return end
+Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit.getName()or nil
+if Event.TgtDCSUnitName~=nil then
Event.TgtUnitName=Event.TgtDCSUnitName
Event.TgtUnit=SCENERY:Register(Event.TgtDCSUnitName,Event.target)
Event.TgtCategory=Event.TgtDCSUnit:getDesc().category
Event.TgtTypeName=Event.TgtDCSUnit:getTypeName()
end
end
+end
if Event.weapon and type(Event.weapon)=="table"and Event.weapon.isExist and Event.weapon:isExist()then
Event.Weapon=Event.weapon
Event.WeaponName=Event.weapon:isExist()and Event.weapon:getTypeName()or"Unknown Weapon"
@@ -7586,10 +7726,12 @@ if Event.id==EVENTS.LandingAfterEjection then
else
if Event.place:isExist()and Object.getCategory(Event.place)~=Object.Category.SCENERY then
Event.Place=AIRBASE:Find(Event.place)
+if Event.Place then
Event.PlaceName=Event.Place:GetName()
end
end
end
+end
if Event.idx then
Event.MarkID=Event.idx
Event.MarkVec3=Event.pos
@@ -8348,13 +8490,14 @@ end
function MENU_INDEX:HasGroupMenu(Group,Path)
if Group and Group:IsAlive()then
local MenuGroupName=Group:GetName()
+if self.Group[MenuGroupName]and self.Group[MenuGroupName].Menus and self.Group[MenuGroupName].Menus[Path]then
return self.Group[MenuGroupName].Menus[Path]
end
+end
return nil
end
function MENU_INDEX:SetGroupMenu(Group,Path,Menu)
local MenuGroupName=Group:GetName()
-Group:F({MenuGroupName=MenuGroupName,Path=Path})
self.Group[MenuGroupName].Menus[Path]=Menu
end
function MENU_INDEX:ClearGroupMenu(Group,Path)
@@ -8999,7 +9142,7 @@ return nil
end
function ZONE_BASE:GetPointVec2()
local Vec2=self:GetVec2()
-local PointVec2=POINT_VEC2:NewFromVec2(Vec2)
+local PointVec2=COORDINATE:NewFromVec2(Vec2)
return PointVec2
end
function ZONE_BASE:GetVec3(Height)
@@ -9010,7 +9153,7 @@ return Vec3
end
function ZONE_BASE:GetPointVec3(Height)
local Vec3=self:GetVec3(Height)
-local PointVec3=POINT_VEC3:NewFromVec3(Vec3)
+local PointVec3=COORDINATE:NewFromVec3(Vec3)
return PointVec3
end
function ZONE_BASE:GetCoordinate(Height)
@@ -9143,6 +9286,14 @@ end
function ZONE_BASE:GetZoneProbability()
return self.ZoneProbability
end
+function ZONE_BASE:FindNearestCoordinateOnRadius(Outsidecoordinate)
+local Vec1=self:GetVec2()
+local Radius=self:GetRadius()
+local Vec2=Outsidecoordinate:GetVec2()
+local Point=UTILS.FindNearestPointOnCircle(Vec1,Radius,Vec2)
+local rc=COORDINATE:NewFromVec2(Point)
+return rc
+end
function ZONE_BASE:GetZoneMaybe()
local Randomization=math.random()
if Randomization<=self.ZoneProbability then
@@ -9160,10 +9311,13 @@ self:SetStartState("TriggerStopped")
self:AddTransition("TriggerStopped","TriggerStart","TriggerRunning")
self:AddTransition("*","EnteredZone","*")
self:AddTransition("*","LeftZone","*")
+self:AddTransition("*","ZoneEmpty","*")
+self:AddTransition("*","ObjectDead","*")
self:AddTransition("*","TriggerRunCheck","*")
self:AddTransition("*","TriggerStop","TriggerStopped")
self:TriggerStart()
self.checkobjects=Objects
+self.ObjectsInZone=false
if UTILS.IsInstanceOf(Objects,"SET_BASE")then
self.objectset=Objects.Set
else
@@ -9173,19 +9327,28 @@ self:_TriggerCheck(true)
self:__TriggerRunCheck(self.Checktime)
return self
end
+function ZONE_BASE:SetPartlyInside(state)
+self.PartlyInside=state or not(state==false)
+return self
+end
function ZONE_BASE:_TriggerCheck(fromstart)
local objectset=self.objectset or{}
if fromstart then
for _,_object in pairs(objectset)do
local obj=_object
-if not obj.TriggerInZone then obj.TriggerInZone={}end
+if not obj.TriggerInZone then
+obj.TriggerInZone={}
+obj.TriggerZoneDeadNotification=false
+end
if obj and obj:IsAlive()and self:IsCoordinateInZone(obj:GetCoordinate())then
obj.TriggerInZone[self.ZoneName]=true
+self.ObjectsInZone=true
else
obj.TriggerInZone[self.ZoneName]=false
end
end
else
+local objcount=0
for _,_object in pairs(objectset)do
local obj=_object
if obj and obj:IsAlive()then
@@ -9195,18 +9358,41 @@ end
if not obj.TriggerInZone[self.ZoneName]then
obj.TriggerInZone[self.ZoneName]=false
end
-local inzone=self:IsCoordinateInZone(obj:GetCoordinate())
+local inzone
+if self.PartlyInside and obj.ClassName=="GROUP"then
+inzone=obj:IsAnyInZone(self)
+else
+inzone=self:IsCoordinateInZone(obj:GetCoordinate())
+end
+if inzone and obj.TriggerInZone[self.ZoneName]then
+objcount=objcount+1
+self.ObjectsInZone=true
+obj.TriggerZoneDeadNotification=false
+end
if inzone and not obj.TriggerInZone[self.ZoneName]then
self:__EnteredZone(0.5,obj)
obj.TriggerInZone[self.ZoneName]=true
+objcount=objcount+1
+self.ObjectsInZone=true
+obj.TriggerZoneDeadNotification=false
elseif(not inzone)and obj.TriggerInZone[self.ZoneName]then
self:__LeftZone(0.5,obj)
obj.TriggerInZone[self.ZoneName]=false
else
end
+else
+if not obj.TriggerZoneDeadNotification==true then
+obj.TriggerInZone=nil
+self:__ObjectDead(0.5,obj)
+obj.TriggerZoneDeadNotification=true
end
end
end
+if objcount==0 and self.ObjectsInZone==true then
+self.ObjectsInZone=false
+self:__ZoneEmpty(0.5)
+end
+end
return self
end
function ZONE_BASE:onafterTriggerRunCheck(From,Event,To)
@@ -9314,7 +9500,7 @@ for Angle=0,360,360/Points do
local Radial=(Angle+AngleOffset)*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
-POINT_VEC2:New(Point.x,Point.y,AddHeight):Smoke(SmokeColor)
+COORDINATE:New(Point.x,AddHeight,Point.y):Smoke(SmokeColor)
end
return self
end
@@ -9329,7 +9515,7 @@ for Angle=0,360,360/Points do
local Radial=Angle*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
-POINT_VEC2:New(Point.x,Point.y,AddHeight):Flare(FlareColor,Azimuth)
+COORDINATE:New(Point.x,AddHeight,Point.y):Flare(FlareColor,Azimuth)
end
return self
end
@@ -9369,10 +9555,9 @@ radius=ZoneRadius,
}
}
local function EvaluateZone(ZoneObject)
-if ZoneObject then
+if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())then
local ObjectCategory=Object.getCategory(ZoneObject)
if(ObjectCategory==Object.Category.UNIT and ZoneObject:isExist()and ZoneObject:isActive())or(ObjectCategory==Object.Category.STATIC and ZoneObject:isExist())then
-local CoalitionDCSUnit=ZoneObject:getCoalition()
local Include=false
if not UnitCategories then
Include=true
@@ -9522,7 +9707,7 @@ local SphereSearch={
id=world.VolumeType.SPHERE,
params={
point=ZoneCoord:GetVec3(),
-radius=ZoneRadius/2,
+radius=ZoneRadius,
}
}
local function EvaluateZone(ZoneDCSUnit)
@@ -9546,10 +9731,19 @@ if not Vec3 then return false end
local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z})
return InZone
end
+function ZONE_RADIUS:GetClearZonePositions(PosRadius,NumPositions)
+return UTILS.GetClearZonePositions(self,PosRadius,NumPositions)
+end
+function ZONE_RADIUS:GetRandomClearZoneCoordinate(PosRadius,NumPositions)
+return UTILS.GetRandomClearZoneCoordinate(self,PosRadius,NumPositions)
+end
function ZONE_RADIUS:GetRandomVec2(inner,outer,surfacetypes)
local Vec2=self:GetVec2()
local _inner=inner or 0
local _outer=outer or self:GetRadius()
+math.random()
+math.random()
+math.random()
if surfacetypes and type(surfacetypes)~="table"then
surfacetypes={surfacetypes}
end
@@ -9584,7 +9778,7 @@ end
return point
end
function ZONE_RADIUS:GetRandomPointVec2(inner,outer)
-local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2(inner,outer))
+local PointVec2=COORDINATE:NewFromVec2(self:GetRandomVec2(inner,outer))
return PointVec2
end
function ZONE_RADIUS:GetRandomVec3(inner,outer)
@@ -9592,7 +9786,7 @@ local Vec2=self:GetRandomVec2(inner,outer)
return{x=Vec2.x,y=self.y,z=Vec2.y}
end
function ZONE_RADIUS:GetRandomPointVec3(inner,outer)
-local PointVec3=POINT_VEC3:NewFromVec2(self:GetRandomVec2(inner,outer))
+local PointVec3=COORDINATE:NewFromVec2(self:GetRandomVec2(inner,outer))
return PointVec3
end
function ZONE_RADIUS:GetRandomCoordinate(inner,outer,surfacetypes)
@@ -9795,7 +9989,7 @@ Point.y=Vec2.y+math.sin(angle)*math.random()*self:GetRadius();
return Point
end
function ZONE_GROUP:GetRandomPointVec2(inner,outer)
-local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2())
+local PointVec2=COORDINATE:NewFromVec2(self:GetRandomVec2())
return PointVec2
end
_ZONE_TRIANGLE={
@@ -10035,6 +10229,12 @@ end
function ZONE_POLYGON_BASE:Flush()
return self
end
+function ZONE_POLYGON_BASE:GetClearZonePositions(PosRadius,NumPositions)
+return UTILS.GetClearZonePositions(self,PosRadius,NumPositions)
+end
+function ZONE_POLYGON_BASE:GetRandomClearZoneCoordinate(PosRadius,NumPositions)
+return UTILS.GetRandomClearZoneCoordinate(self,PosRadius,NumPositions)
+end
function ZONE_POLYGON_BASE:BoundZone(UnBound)
local i
local j
@@ -10193,7 +10393,7 @@ local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y
for Segment=0,Segments do
local PointX=self._.Polygon[i].x+(Segment*DeltaX/Segments)
local PointY=self._.Polygon[i].y+(Segment*DeltaY/Segments)
-POINT_VEC2:New(PointX,PointY):Smoke(SmokeColor)
+COORDINATE:New(PointX,0,PointY):Smoke(SmokeColor)
end
j=i
i=i+1
@@ -10211,7 +10411,7 @@ local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y
for Segment=0,Segments do
local PointX=self._.Polygon[i].x+(Segment*DeltaX/Segments)
local PointY=self._.Polygon[i].y+(Segment*DeltaY/Segments)
-POINT_VEC2:New(PointX,PointY,AddHeight):Flare(FlareColor,Azimuth)
+COORDINATE:New(PointX,AddHeight,PointY):Flare(FlareColor,Azimuth)
end
j=i
i=i+1
@@ -10242,6 +10442,9 @@ local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z})
return InZone
end
function ZONE_POLYGON_BASE:GetRandomVec2()
+math.random()
+math.random()
+math.random()
local weights={}
for _,triangle in pairs(self._Triangles)do
weights[triangle]=triangle.SurfaceArea/self.SurfaceArea
@@ -10256,11 +10459,11 @@ end
end
end
function ZONE_POLYGON_BASE:GetRandomPointVec2()
-local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2())
+local PointVec2=COORDINATE:NewFromVec2(self:GetRandomVec2())
return PointVec2
end
function ZONE_POLYGON_BASE:GetRandomPointVec3()
-local PointVec3=POINT_VEC3:NewFromVec2(self:GetRandomVec2())
+local PointVec3=COORDINATE:NewFromVec2(self:GetRandomVec2())
return PointVec3
end
function ZONE_POLYGON_BASE:GetRandomCoordinate()
@@ -10395,11 +10598,7 @@ self.ScanData.Scenery={}
self.ScanData.SceneryTable={}
self.ScanData.Units={}
local vectors=self:GetBoundingSquare()
-local minVec3={x=vectors.x1,y=0,z=vectors.y1}
-local maxVec3={x=vectors.x2,y=0,z=vectors.y2}
-local minmarkcoord=COORDINATE:NewFromVec3(minVec3)
-local maxmarkcoord=COORDINATE:NewFromVec3(maxVec3)
-local ZoneRadius=minmarkcoord:Get2DDistance(maxmarkcoord)/2
+local ZoneRadius=UTILS.VecDist2D({x=vectors.x1,y=vectors.y1},{x=vectors.x2,y=vectors.y2})/2
local CenterVec3=self:GetCoordinate():GetVec3()
local SphereSearch={
id=world.VolumeType.SPHERE,
@@ -10409,10 +10608,9 @@ radius=ZoneRadius,
}
}
local function EvaluateZone(ZoneObject)
-if ZoneObject then
+if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())then
local ObjectCategory=Object.getCategory(ZoneObject)
if(ObjectCategory==Object.Category.UNIT and ZoneObject:isExist()and ZoneObject:isActive())or(ObjectCategory==Object.Category.STATIC and ZoneObject:isExist())then
-local CoalitionDCSUnit=ZoneObject:getCoalition()
local Include=false
if not UnitCategories then
Include=true
@@ -10431,7 +10629,7 @@ self.ScanData.Coalitions[CoalitionDCSUnit]=true
self.ScanData.Units[ZoneObject]=ZoneObject
end
end
-if ObjectCategory==Object.Category.SCENERY and self:IsVec3InZone(ZoneObject:getPoint())then
+if ObjectCategory==Object.Category.SCENERY then
local SceneryType=ZoneObject:getTypeName()
local SceneryName=ZoneObject:getName()
self.ScanData.Scenery[SceneryType]=self.ScanData.Scenery[SceneryType]or{}
@@ -10587,6 +10785,24 @@ function ZONE_ELASTIC:AddVertex2D(Vec2)
table.insert(self.points,Vec2)
return self
end
+function ZONE_ELASTIC:RemoveVertex2D(Vec2)
+local found=false
+local findex=0
+for _id,_vec2 in pairs(self.points)do
+if _vec2.x==Vec2.x and _vec2.y==Vec2.y then
+found=true
+findex=_id
+break
+end
+end
+if found==true and findex>0 then
+table.remove(self.points,findex)
+end
+return self
+end
+function ZONE_ELASTIC:RemoveVertex3D(Vec3)
+return self:RemoveVertex2D({x=Vec3.x,y=Vec3.z})
+end
function ZONE_ELASTIC:AddVertex3D(Vec3)
table.insert(self.points,{x=Vec3.x,y=Vec3.z})
return self
@@ -10609,6 +10825,8 @@ end
end
end
self._.Polygon=self:_ConvexHull(points)
+self._Triangles=self:_Triangulate()
+self.SurfaceArea=self:_CalculateSurfaceArea()
if Draw~=false then
if self.DrawID or Draw==true then
self:UndrawZone()
@@ -10750,10 +10968,10 @@ local ry=(x_e*math.sin(theta)+y_e*math.cos(theta))+self.CenterVec2.y
return{x=rx,y=ry}
end
function ZONE_OVAL:GetRandomPointVec2()
-return POINT_VEC2:NewFromVec2(self:GetRandomVec2())
+return COORDINATE:NewFromVec2(self:GetRandomVec2())
end
function ZONE_OVAL:GetRandomPointVec3()
-return POINT_VEC3:NewFromVec3(self:GetRandomVec2())
+return COORDINATE:NewFromVec3(self:GetRandomVec2())
end
function ZONE_OVAL:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType)
Coalition=Coalition or self:GetDrawCoalition()
@@ -10819,7 +11037,7 @@ end
return ZoneVec2
end
function ZONE_AIRBASE:GetRandomPointVec2(inner,outer)
-local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2())
+local PointVec2=COORDINATE:NewFromVec2(self:GetRandomVec2())
return PointVec2
end
end
@@ -10875,7 +11093,7 @@ for Angle=0,360,360/Points do
local Radial=(Angle+AngleOffset)*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
-POINT_VEC2:New(Point.x,Point.y,AddHeight):Smoke(SmokeColor)
+COORDINATE:New(Point.x,AddHeight,Point.y):Smoke(SmokeColor)
end
return self
end
@@ -10891,7 +11109,7 @@ for Angle=0,360,360/Points do
local Radial=Angle*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
-POINT_VEC2:New(Point.x,Point.y,AddHeight):Flare(FlareColor,Azimuth)
+COORDINATE:New(Point.x,AddHeight,Point.y):Flare(FlareColor,Azimuth)
end
return self
end
@@ -11019,9 +11237,8 @@ end
function DATABASE:AddStatic(DCSStaticName)
if not self.STATICS[DCSStaticName]then
self.STATICS[DCSStaticName]=STATIC:Register(DCSStaticName)
-return self.STATICS[DCSStaticName]
end
-return nil
+return self.STATICS[DCSStaticName]
end
function DATABASE:DeleteStatic(DCSStaticName)
self.STATICS[DCSStaticName]=nil
@@ -11033,9 +11250,8 @@ end
function DATABASE:AddDynamicCargo(Name)
if not self.DYNAMICCARGO[Name]then
self.DYNAMICCARGO[Name]=DYNAMICCARGO:Register(Name)
-return self.DYNAMICCARGO[Name]
end
-return nil
+return self.DYNAMICCARGO[Name]
end
function DATABASE:FindDynamicCargo(DynamicCargoName)
local StaticFound=self.DYNAMICCARGO[DynamicCargoName]
@@ -11176,11 +11392,17 @@ local ZoneName=objectData.name or"Unknown rect Polygon Drawing"
local vec2={x=objectData.mapX,y=objectData.mapY}
local w=objectData.width
local h=objectData.height
-local points={}
-points[1]={x=vec2.x-h/2,y=vec2.y+w/2}
-points[2]={x=vec2.x+h/2,y=vec2.y+w/2}
-points[3]={x=vec2.x+h/2,y=vec2.y-w/2}
-points[4]={x=vec2.x-h/2,y=vec2.y-w/2}
+local rotation=UTILS.ToRadian(objectData.angle or 0)
+local sinRot=math.sin(rotation)
+local cosRot=math.cos(rotation)
+local dx=h/2
+local dy=w/2
+local points={
+{x=-dx*cosRot-(-dy*sinRot)+vec2.x,y=-dx*sinRot+(-dy*cosRot)+vec2.y},
+{x=dx*cosRot-(-dy*sinRot)+vec2.x,y=dx*sinRot+(-dy*cosRot)+vec2.y},
+{x=dx*cosRot-(dy*sinRot)+vec2.x,y=dx*sinRot+(dy*cosRot)+vec2.y},
+{x=-dx*cosRot-(dy*sinRot)+vec2.x,y=-dx*sinRot+(dy*cosRot)+vec2.y},
+}
self:I(string.format("Register ZONE: %s (Polygon (rect) drawing with %d vertices)",ZoneName,#points))
local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName,points)
Zone:SetColor({1,0,0},0.15)
@@ -11321,6 +11543,7 @@ end
return self.CLIENTS[DCSUnitName]
end
function DATABASE:FindGroup(GroupName)
+if type(GroupName)~="string"or GroupName==""then return end
local GroupFound=self.GROUPS[GroupName]
if GroupFound==nil and GroupName~=nil and self.Templates.Groups[GroupName]==nil then
self:_RegisterDynamicGroup(GroupName)
@@ -11449,7 +11672,7 @@ if stn==nil or stn<1 then
self:E("WARNING: Invalid STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for "..UnitTemplate.name)
else
self.STNS[stn]=UnitTemplate.name
-self:I("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for "..UnitTemplate.name)
+self:T("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for "..UnitTemplate.name)
end
end
if UnitTemplate.AddPropAircraft.SADL_TN then
@@ -11458,7 +11681,7 @@ if sadl==nil or sadl<1 then
self:E("WARNING: Invalid SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for "..UnitTemplate.name)
else
self.SADL[sadl]=UnitTemplate.name
-self:I("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for "..UnitTemplate.name)
+self:T("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for "..UnitTemplate.name)
end
end
end
@@ -11638,21 +11861,21 @@ function DATABASE:GetCoalitionFromClientTemplate(ClientName)
if self.Templates.ClientsByName[ClientName]then
return self.Templates.ClientsByName[ClientName].CoalitionID
end
-self:E("WARNING: Template does not exist for client "..tostring(ClientName))
+self:T("WARNING: Template does not exist for client "..tostring(ClientName))
return nil
end
function DATABASE:GetCategoryFromClientTemplate(ClientName)
if self.Templates.ClientsByName[ClientName]then
return self.Templates.ClientsByName[ClientName].CategoryID
end
-self:E("WARNING: Template does not exist for client "..tostring(ClientName))
+self:T("WARNING: Template does not exist for client "..tostring(ClientName))
return nil
end
function DATABASE:GetCountryFromClientTemplate(ClientName)
if self.Templates.ClientsByName[ClientName]then
return self.Templates.ClientsByName[ClientName].CountryID
end
-self:E("WARNING: Template does not exist for client "..tostring(ClientName))
+self:T("WARNING: Template does not exist for client "..tostring(ClientName))
return nil
end
function DATABASE:GetCoalitionFromAirbase(AirbaseName)
@@ -11800,7 +12023,7 @@ if client then
end
local PlayerName=Event.IniUnit:GetPlayerName()
if PlayerName then
-self:I(string.format("Player '%s' joined unit '%s' of group '%s'",tostring(PlayerName),tostring(Event.IniDCSUnitName),tostring(Event.IniDCSGroupName)))
+self:I(string.format("Player '%s' joined unit '%s' (%s) of group '%s'",tostring(PlayerName),tostring(Event.IniDCSUnitName),tostring(Event.IniTypeName),tostring(Event.IniDCSGroupName)))
if client==nil or(client and client:CountPlayers()==0)then
client=self:AddClient(Event.IniDCSUnitName,true)
end
@@ -12252,6 +12475,11 @@ end
end
end
function SET_BASE:Add(ObjectName,Object)
+if not ObjectName or ObjectName==""then
+self:E("SET_BASE:Add - Invalid ObjectName handed")
+self:E({ObjectName=ObjectName,Object=Object})
+return self
+end
if self.Set[ObjectName]then
self:Remove(ObjectName,true)
end
@@ -12361,6 +12589,17 @@ end
function SET_BASE:GetSomeIteratorLimit()
return self.SomeIteratorLimit or self:Count()
end
+function SET_BASE:GetThreatLevelMax()
+local ThreatMax=0
+for _,_unit in pairs(self.Set or{})do
+local unit=_unit
+local threat=unit.GetThreatLevel and unit:GetThreatLevel()or 0
+if threat>ThreatMax then
+ThreatMax=threat
+end
+end
+return ThreatMax
+end
function SET_BASE:FilterOnce()
for ObjectName,Object in pairs(self.Database)do
if self:IsIncludeObject(Object)then
@@ -12399,15 +12638,15 @@ self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.Crash)
return self
end
-function SET_BASE:FindNearestObjectFromPointVec2(PointVec2)
+function SET_BASE:FindNearestObjectFromPointVec2(Coordinate)
local NearestObject=nil
local ClosestDistance=nil
for ObjectID,ObjectData in pairs(self.Set)do
if NearestObject==nil then
NearestObject=ObjectData
-ClosestDistance=PointVec2:DistanceFromPointVec2(ObjectData:GetCoordinate())
+ClosestDistance=Coordinate:DistanceFromPointVec2(ObjectData:GetCoordinate())
else
-local Distance=PointVec2:DistanceFromPointVec2(ObjectData:GetCoordinate())
+local Distance=Coordinate:DistanceFromPointVec2(ObjectData:GetCoordinate())
if Distance1 then
@@ -16867,6 +17239,17 @@ function COORDINATE:GetLandHeight()
local Vec2={x=self.x,y=self.z}
return land.getHeight(Vec2)
end
+function COORDINATE:GetLandProfileVec3(Destination)
+return land.profile(self:GetVec3(),Destination)
+end
+function COORDINATE:GetLandProfileCoordinates(Destination)
+local points=self:GetLandProfileVec3(Destination:GetVec3())
+local coords={}
+for _,point in ipairs(points)do
+table.insert(coords,COORDINATE:NewFromVec3(point))
+end
+return coords
+end
function COORDINATE:SetHeading(Heading)
self.Heading=Heading
end
@@ -16928,8 +17311,12 @@ return coord
end
function COORDINATE:Get2DDistance(TargetCoordinate)
if not TargetCoordinate then return 1000000 end
-local a={x=TargetCoordinate.x-self.x,y=0,z=TargetCoordinate.z-self.z}
-local norm=UTILS.VecNorm(a)
+local a=self:GetVec2()
+if not TargetCoordinate.ClassName then
+TargetCoordinate=COORDINATE:NewFromVec3(TargetCoordinate)
+end
+local b=TargetCoordinate:GetVec2()
+local norm=UTILS.VecDist2D(a,b)
return norm
end
function COORDINATE:GetTemperature(height)
@@ -17011,6 +17398,79 @@ local point={x=self.x,y=math.max(height or self.y,landheight),z=self.z}
local vec3=atmosphere.getWindWithTurbulence(point)
return vec3
end
+function COORDINATE:GetX()
+return self.x
+end
+function COORDINATE:GetY()
+if self:IsInstanceOf("POINT_VEC2")then
+return self.z
+end
+return self.y
+end
+function COORDINATE:GetZ()
+return self.z
+end
+function COORDINATE:SetX(x)
+self.x=x
+return self
+end
+function COORDINATE:SetY(y)
+if self:IsInstanceOf("POINT_VEC2")then
+self.z=y
+else
+self.y=y
+end
+return self
+end
+function COORDINATE:SetZ(z)
+self.z=z
+return self
+end
+function COORDINATE:AddX(x)
+self.x=self.x+x
+return self
+end
+function COORDINATE:GetLat()
+return self.x
+end
+function COORDINATE:SetLat(x)
+self.x=x
+return self
+end
+function COORDINATE:GetLon()
+return self.z
+end
+function COORDINATE:SetLon(z)
+self.z=z
+return self
+end
+function COORDINATE:GetAlt()
+return self.y~=0 or land.getHeight({x=self.x,y=self.z})
+end
+function COORDINATE:SetAlt(Altitude)
+self.y=Altitude or land.getHeight({x=self.x,y=self.z})
+return self
+end
+function COORDINATE:AddAlt(Altitude)
+self.y=land.getHeight({x=self.x,y=self.z})+Altitude or 0
+return self
+end
+function COORDINATE:GetRandomPointVec2InRadius(OuterRadius,InnerRadius)
+self:F2({OuterRadius,InnerRadius})
+return COORDINATE:NewFromVec2(self:GetRandomVec2InRadius(OuterRadius,InnerRadius))
+end
+function COORDINATE:AddY(y)
+if self:IsInstanceOf("POINT_VEC2")then
+return self:AddZ(y)
+else
+self.y=self.y+y
+end
+return self
+end
+function COORDINATE:AddZ(z)
+self.z=self.z+z
+return self
+end
function COORDINATE:GetWindText(height,Settings)
local Direction,Strength=self:GetWind(height)
local Settings=Settings or _SETTINGS
@@ -17036,7 +17496,7 @@ local Settings=Settings or _SETTINGS
local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),Precision)
local s=string.format('%03d°',AngleDegrees)
if MagVar then
-local variation=UTILS.GetMagneticDeclination()or 0
+local variation=self:GetMagneticDeclination()or 0
local AngleMagnetic=AngleDegrees-variation
if AngleMagnetic<0 then AngleMagnetic=360-AngleMagnetic end
s=string.format('%03d°M|%03d°',AngleMagnetic,AngleDegrees)
@@ -17108,10 +17568,11 @@ else
return" bearing unknown"
end
end
-function COORDINATE:GetBRText(AngleRadians,Distance,Settings,Language,MagVar)
+function COORDINATE:GetBRText(AngleRadians,Distance,Settings,Language,MagVar,Precision)
local Settings=Settings or _SETTINGS
+Precision=Precision or 0
local BearingText=self:GetBearingText(AngleRadians,0,Settings,MagVar)
-local DistanceText=self:GetDistanceText(Distance,Settings,Language,0)
+local DistanceText=self:GetDistanceText(Distance,Settings,Language,Precision)
local BRText=BearingText..DistanceText
return BRText
end
@@ -17412,79 +17873,141 @@ trigger.action.illuminationBomb(self:GetVec3(),Power)
end
return self
end
-function COORDINATE:Smoke(SmokeColor)
-self:F2({SmokeColor})
-trigger.action.smoke(self:GetVec3(),SmokeColor)
+function COORDINATE:Smoke(SmokeColor,Duration,Delay,Name,Offset,Direction,Distance)
+self:F2({SmokeColor,Name,Duration,Delay,Offset})
+SmokeColor=SmokeColor or SMOKECOLOR.Green
+if Delay and Delay>0 then
+self:ScheduleOnce(Delay,COORDINATE.Smoke,self,SmokeColor,Duration,0,Name,Direction,Distance)
+else
+self.firename=Name or"Smoke-"..math.random(1,100000)
+if Offset or self.SmokeOffset then
+local Angle=Direction or self:GetSmokeOffsetDirection()
+local Distance=Distance or self:GetSmokeOffsetDistance()
+local newpos=self:Translate(Distance,Angle,true,false)
+local newvec3=newpos:GetVec3()
+trigger.action.smoke(newvec3,SmokeColor,self.firename)
+else
+trigger.action.smoke(self:GetVec3(),SmokeColor,self.firename)
end
-function COORDINATE:SmokeGreen()
-self:F2()
-self:Smoke(SMOKECOLOR.Green)
+if Duration and Duration>0 then
+self:ScheduleOnce(Duration,COORDINATE.StopSmoke,self,self.firename)
end
-function COORDINATE:SmokeRed()
-self:F2()
-self:Smoke(SMOKECOLOR.Red)
end
-function COORDINATE:SmokeWhite()
-self:F2()
-self:Smoke(SMOKECOLOR.White)
+return self
end
-function COORDINATE:SmokeOrange()
-self:F2()
-self:Smoke(SMOKECOLOR.Orange)
+function COORDINATE:GetSmokeOffsetDirection()
+local direction=self.SmokeOffsetDirection or math.random(1,359)
+return direction
end
-function COORDINATE:SmokeBlue()
-self:F2()
-self:Smoke(SMOKECOLOR.Blue)
+function COORDINATE:SetSmokeOffsetDirection(Direction)
+if self then
+self.SmokeOffsetDirection=Direction or math.random(1,359)
+return self
+else
+COORDINATE.SmokeOffsetDirection=Direction or math.random(1,359)
end
-function COORDINATE:BigSmokeAndFire(preset,density,name)
-self:F2({preset=preset,density=density})
-density=density or 0.5
-self.firename=name or"Fire-"..math.random(1,10000)
-trigger.action.effectSmokeBig(self:GetVec3(),preset,density,self.firename)
+end
+function COORDINATE:GetSmokeOffsetDistance()
+local distance=self.SmokeOffsetDistance or math.random(10,20)
+return distance
+end
+function COORDINATE:SetSmokeOffsetDistance(Distance)
+if self then
+self.SmokeOffsetDistance=Distance or math.random(10,20)
+return self
+else
+COORDINATE.SmokeOffsetDistance=Distance or math.random(10,20)
+end
+end
+function COORDINATE:SwitchSmokeOffsetOn()
+if self then
+self.SmokeOffset=true
+return self
+else
+COORDINATE.SmokeOffset=true
+end
+end
+function COORDINATE:SwitchSmokeOffsetOff()
+if self then
+self.SmokeOffset=false
+return self
+else
+COORDINATE.SmokeOffset=false
+end
+end
+function COORDINATE:StopSmoke(name)
+self:StopBigSmokeAndFire(name)
+end
+function COORDINATE:SmokeGreen(Duration,Delay)
+self:Smoke(SMOKECOLOR.Green,Duration,Delay)
+return self
+end
+function COORDINATE:SmokeRed(Duration,Delay)
+self:Smoke(SMOKECOLOR.Red,Duration,Delay)
+return self
+end
+function COORDINATE:SmokeWhite(Duration,Delay)
+self:Smoke(SMOKECOLOR.White,Duration,Delay)
+return self
+end
+function COORDINATE:SmokeOrange(Duration,Delay)
+self:Smoke(SMOKECOLOR.Orange,Duration,Delay)
+return self
+end
+function COORDINATE:SmokeBlue(Duration,Delay)
+self:Smoke(SMOKECOLOR.Blue,Duration,Delay)
+return self
+end
+function COORDINATE:BigSmokeAndFire(Preset,Density,Duration,Delay,Name)
+self:F2({preset=Preset,density=Density})
+Preset=Preset or BIGSMOKEPRESET.SmallSmokeAndFire
+Density=Density or 0.5
+if Delay and Delay>0 then
+self:ScheduleOnce(Delay,COORDINATE.BigSmokeAndFire,self,Preset,Density,Duration,0,Name)
+else
+self.firename=Name or"Fire-"..math.random(1,10000)
+trigger.action.effectSmokeBig(self:GetVec3(),Preset,Density,self.firename)
+if Duration and Duration>0 then
+self:ScheduleOnce(Duration,COORDINATE.StopBigSmokeAndFire,self,self.firename)
+end
+end
+return self
end
function COORDINATE:StopBigSmokeAndFire(name)
name=name or self.firename
trigger.action.effectSmokeStop(name)
end
-function COORDINATE:BigSmokeAndFireSmall(density,name)
-self:F2({density=density})
-density=density or 0.5
-self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire,density,name)
+function COORDINATE:BigSmokeAndFireSmall(Density,Duration,Delay,Name)
+self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire,Density,Duration,Delay,Name)
+return self
end
-function COORDINATE:BigSmokeAndFireMedium(density,name)
-self:F2({density=density})
-density=density or 0.5
-self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire,density,name)
+function COORDINATE:BigSmokeAndFireMedium(Density,Duration,Delay,Name)
+self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire,Density,Duration,Delay,Name)
+return self
end
-function COORDINATE:BigSmokeAndFireLarge(density,name)
-self:F2({density=density})
-density=density or 0.5
-self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire,density,name)
+function COORDINATE:BigSmokeAndFireLarge(Density,Duration,Delay,Name)
+self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire,Density,Duration,Delay,Name)
+return self
end
-function COORDINATE:BigSmokeAndFireHuge(density,name)
-self:F2({density=density})
-density=density or 0.5
-self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire,density,name)
+function COORDINATE:BigSmokeAndFireHuge(Density,Duration,Delay,Name)
+self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire,Density,Duration,Delay,Name)
+return self
end
-function COORDINATE:BigSmokeSmall(density,name)
-self:F2({density=density})
-density=density or 0.5
-self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke,density,name)
+function COORDINATE:BigSmokeSmall(Density,Duration,Delay,Name)
+self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke,Density,Duration,Delay,Name)
+return self
end
-function COORDINATE:BigSmokeMedium(density,name)
-self:F2({density=density})
-density=density or 0.5
-self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke,density,name)
+function COORDINATE:BigSmokeMedium(Density,Duration,Delay,Name)
+self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke,Density,Duration,Delay,Name)
+return self
end
-function COORDINATE:BigSmokeLarge(density,name)
-self:F2({density=density})
-density=density or 0.5
-self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke,density,name)
+function COORDINATE:BigSmokeLarge(Density,Duration,Delay,Name)
+self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke,Density,Duration,Delay,Name)
+return self
end
-function COORDINATE:BigSmokeHuge(density,name)
-self:F2({density=density})
-density=density or 0.5
-self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke,density,name)
+function COORDINATE:BigSmokeHuge(Density,Duration,Delay,Name)
+self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke,Density,Duration,Delay,Name)
+return self
end
function COORDINATE:Flare(FlareColor,Azimuth)
self:F2({FlareColor})
@@ -17789,8 +18312,10 @@ local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff)
local sunset=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff)
+if type(sunrise)=="string"or type(sunset)=="string"then
if sunrise=="N/R"then return false end
-if sunrise=="N/S"then return true end
+if sunset=="N/S"then return true end
+end
local time=UTILS.ClockToSeconds(clock)
if time>sunrise and time<=sunset then
return true
@@ -17800,6 +18325,10 @@ end
else
local sunrise=self:GetSunrise(true)
local sunset=self:GetSunset(true)
+if type(sunrise)=="string"or type(sunset)=="string"then
+if sunrise=="N/R"then return false end
+if sunset=="N/S"then return true end
+end
local time=UTILS.SecondsOfToday()
if time>sunrise and time<=sunset then
return true
@@ -17850,11 +18379,11 @@ delta=sunset+UTILS.SecondsToMidnight()
end
return delta/60
end
-function COORDINATE:ToStringBR(FromCoordinate,Settings,MagVar)
+function COORDINATE:ToStringBR(FromCoordinate,Settings,MagVar,Precision)
local DirectionVec3=FromCoordinate:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local Distance=self:Get2DDistance(FromCoordinate)
-return"BR, "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar)
+return"BR, "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar,Precision)
end
function COORDINATE:ToStringBRA(FromCoordinate,Settings,MagVar)
local DirectionVec3=FromCoordinate:GetDirectionVec3(self)
@@ -17869,6 +18398,8 @@ local currentCoord=FromCoordinate
local DirectionVec3=FromCoordinate:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local bearing=UTILS.Round(UTILS.ToDegree(AngleRadians),0)
+local magnetic=self:GetMagneticDeclination()or 0
+bearing=bearing-magnetic
local rangeMetres=self:Get2DDistance(currentCoord)
local rangeNM=UTILS.Round(UTILS.MetersToNM(rangeMetres),0)
local aspect=self:ToStringAspect(currentCoord)
@@ -18100,25 +18631,9 @@ return self:ToStringMGRS(Settings)
end
return nil
end
-function COORDINATE:ToString(Controllable,Settings,Task)
+function COORDINATE:ToString(Controllable,Settings)
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
local ModeA2A=nil
-if Task then
-if Task:IsInstanceOf(TASK_A2A)then
-ModeA2A=true
-else
-if Task:IsInstanceOf(TASK_A2G)then
-ModeA2A=false
-else
-if Task:IsInstanceOf(TASK_CARGO)then
-ModeA2A=false
-end
-if Task:IsInstanceOf(TASK_CAPTURE_ZONE)then
-ModeA2A=false
-end
-end
-end
-end
if ModeA2A==nil then
local IsAir=Controllable and(Controllable:IsAirPlane()or Controllable:IsHelicopter())or false
if IsAir then
@@ -18179,6 +18694,21 @@ local steep,elev=self:IsInSteepArea(Radius,Minelevation)
local flat=not steep
return flat,elev
end
+function COORDINATE:GetRandomPointVec3InRadius(OuterRadius,InnerRadius)
+return COORDINATE:NewFromVec3(self:GetRandomVec3InRadius(OuterRadius,InnerRadius))
+end
+function COORDINATE:GetSimpleZones(SearchRadius,PosRadius,NumPositions)
+local clearPositions=UTILS.GetSimpleZones(self:GetVec3(),SearchRadius,PosRadius,NumPositions)
+if clearPositions and#clearPositions>0 then
+local coords={}
+for _,pos in pairs(clearPositions)do
+local coord=COORDINATE:NewFromVec2(pos)
+table.insert(coords,coord)
+end
+return coords
+end
+return nil
+end
end
do
POINT_VEC3={
@@ -18201,52 +18731,6 @@ local self=BASE:Inherit(self,COORDINATE:New(x,y,z))
self:F2(self)
return self
end
-function POINT_VEC3:NewFromVec2(Vec2,LandHeightAdd)
-local self=BASE:Inherit(self,COORDINATE:NewFromVec2(Vec2,LandHeightAdd))
-self:F2(self)
-return self
-end
-function POINT_VEC3:NewFromVec3(Vec3)
-local self=BASE:Inherit(self,COORDINATE:NewFromVec3(Vec3))
-self:F2(self)
-return self
-end
-function POINT_VEC3:GetX()
-return self.x
-end
-function POINT_VEC3:GetY()
-return self.y
-end
-function POINT_VEC3:GetZ()
-return self.z
-end
-function POINT_VEC3:SetX(x)
-self.x=x
-return self
-end
-function POINT_VEC3:SetY(y)
-self.y=y
-return self
-end
-function POINT_VEC3:SetZ(z)
-self.z=z
-return self
-end
-function POINT_VEC3:AddX(x)
-self.x=self.x+x
-return self
-end
-function POINT_VEC3:AddY(y)
-self.y=self.y+y
-return self
-end
-function POINT_VEC3:AddZ(z)
-self.z=self.z+z
-return self
-end
-function POINT_VEC3:GetRandomPointVec3InRadius(OuterRadius,InnerRadius)
-return POINT_VEC3:NewFromVec3(self:GetRandomVec3InRadius(OuterRadius,InnerRadius))
-end
end
do
POINT_VEC2={
@@ -18260,76 +18744,6 @@ local self=BASE:Inherit(self,COORDINATE:New(x,LandHeight,y))
self:F2(self)
return self
end
-function POINT_VEC2:NewFromVec2(Vec2,LandHeightAdd)
-local LandHeight=land.getHeight(Vec2)
-LandHeightAdd=LandHeightAdd or 0
-LandHeight=LandHeight+LandHeightAdd
-local self=BASE:Inherit(self,COORDINATE:NewFromVec2(Vec2,LandHeightAdd))
-self:F2(self)
-return self
-end
-function POINT_VEC2:NewFromVec3(Vec3)
-local self=BASE:Inherit(self,COORDINATE:NewFromVec3(Vec3))
-self:F2(self)
-return self
-end
-function POINT_VEC2:GetX()
-return self.x
-end
-function POINT_VEC2:GetY()
-return self.z
-end
-function POINT_VEC2:SetX(x)
-self.x=x
-return self
-end
-function POINT_VEC2:SetY(y)
-self.z=y
-return self
-end
-function POINT_VEC2:GetLat()
-return self.x
-end
-function POINT_VEC2:SetLat(x)
-self.x=x
-return self
-end
-function POINT_VEC2:GetLon()
-return self.z
-end
-function POINT_VEC2:SetLon(z)
-self.z=z
-return self
-end
-function POINT_VEC2:GetAlt()
-return self.y~=0 or land.getHeight({x=self.x,y=self.z})
-end
-function POINT_VEC2:SetAlt(Altitude)
-self.y=Altitude or land.getHeight({x=self.x,y=self.z})
-return self
-end
-function POINT_VEC2:AddX(x)
-self.x=self.x+x
-return self
-end
-function POINT_VEC2:AddY(y)
-self.z=self.z+y
-return self
-end
-function POINT_VEC2:AddAlt(Altitude)
-self.y=land.getHeight({x=self.x,y=self.z})+Altitude or 0
-return self
-end
-function POINT_VEC2:GetRandomPointVec2InRadius(OuterRadius,InnerRadius)
-self:F2({OuterRadius,InnerRadius})
-return POINT_VEC2:NewFromVec2(self:GetRandomVec2InRadius(OuterRadius,InnerRadius))
-end
-function POINT_VEC2:DistanceFromPointVec2(PointVec2Reference)
-self:F2(PointVec2Reference)
-local Distance=((PointVec2Reference.x-self.x)^2+(PointVec2Reference.z-self.z)^2)^0.5
-self:T2(Distance)
-return Distance
-end
end
do
VELOCITY={
@@ -18467,7 +18881,7 @@ return self
end
function MESSAGE:ToGroup(Group,Settings)
self:F(Group.GroupName)
-if Group then
+if Group and Group:IsAlive()then
if self.MessageType then
local Settings=Settings or(Group and _DATABASE:GetPlayerSettings(Group:GetPlayerName()))or _SETTINGS
self.MessageDuration=Settings:GetMessageTime(self.MessageType)
@@ -18482,7 +18896,7 @@ return self
end
function MESSAGE:ToUnit(Unit,Settings)
self:F(Unit.IdentifiableName)
-if Unit then
+if Unit and Unit:IsAlive()then
if self.MessageType then
local Settings=Settings or(Unit and _DATABASE:GetPlayerSettings(Unit:GetPlayerName()))or _SETTINGS
self.MessageDuration=Settings:GetMessageTime(self.MessageType)
@@ -18585,8 +18999,8 @@ end
return self
end
_MESSAGESRS={}
-function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate)
-_MESSAGESRS.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
+function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate,Backend)
+_MESSAGESRS.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
_MESSAGESRS.frequency=Frequency or MSRS.frequencies or 243
_MESSAGESRS.modulation=Modulation or MSRS.modulations or radio.modulation.AM
_MESSAGESRS.MSRS=MSRS:New(_MESSAGESRS.PathToSRS,_MESSAGESRS.frequency,_MESSAGESRS.modulation)
@@ -18596,6 +19010,9 @@ _MESSAGESRS.coordinate=Coordinate
if Coordinate then
_MESSAGESRS.MSRS:SetCoordinate(Coordinate)
end
+if Backend then
+_MESSAGESRS.MSRS:SetBackend(Backend)
+end
_MESSAGESRS.Culture=Culture or MSRS.culture or"en-GB"
_MESSAGESRS.MSRS:SetCulture(Culture)
_MESSAGESRS.Gender=Gender or MSRS.gender or"female"
@@ -19467,6 +19884,12 @@ self.SpawnUnitsWithAbsolutePositions=true
self.UnitsAbsolutePositions=Positions
return self
end
+function SPAWN:InitValidateAndRepositionGroundUnits(OnOff,MaxRadius,Spacing)
+self.SpawnValidateAndRepositionGroundUnits=OnOff
+self.SpawnValidateAndRepositionGroundUnitsRadius=MaxRadius
+self.SpawnValidateAndRepositionGroundUnitsSpacing=Spacing
+return self
+end
function SPAWN:InitRandomizeTemplate(SpawnTemplatePrefixTable)
local temptable={}
for _,_temp in pairs(SpawnTemplatePrefixTable)do
@@ -19475,25 +19898,25 @@ end
self.SpawnTemplatePrefixTable=UTILS.ShuffleTable(temptable)
self.SpawnRandomizeTemplate=true
for SpawnGroupID=1,self.SpawnMaxGroups do
-self:_RandomizeTemplate(SpawnGroupID)
+self:_RandomizeTemplate(SpawnGroupID,RandomizePositionInZone)
end
return self
end
-function SPAWN:InitRandomizeTemplateSet(SpawnTemplateSet)
+function SPAWN:InitRandomizeTemplateSet(SpawnTemplateSet,RandomizePositionInZone)
local setnames=SpawnTemplateSet:GetSetNames()
-self:InitRandomizeTemplate(setnames)
+self:InitRandomizeTemplate(setnames,RandomizePositionInZone)
return self
end
-function SPAWN:InitRandomizeTemplatePrefixes(SpawnTemplatePrefixes)
+function SPAWN:InitRandomizeTemplatePrefixes(SpawnTemplatePrefixes,RandomizePositionInZone)
local SpawnTemplateSet=SET_GROUP:New():FilterPrefixes(SpawnTemplatePrefixes):FilterOnce()
-self:InitRandomizeTemplateSet(SpawnTemplateSet)
+self:InitRandomizeTemplateSet(SpawnTemplateSet,RandomizePositionInZone)
return self
end
function SPAWN:InitGrouping(Grouping)
self.SpawnGrouping=Grouping
return self
end
-function SPAWN:InitRandomizeZones(SpawnZoneTable)
+function SPAWN:InitRandomizeZones(SpawnZoneTable,RandomizePositionInZone)
local temptable={}
for _,_temp in pairs(SpawnZoneTable)do
temptable[#temptable+1]=_temp
@@ -19501,7 +19924,7 @@ end
self.SpawnZoneTable=UTILS.ShuffleTable(temptable)
self.SpawnRandomizeZones=true
for SpawnGroupID=1,self.SpawnMaxGroups do
-self:_RandomizeZones(SpawnGroupID)
+self:_RandomizeZones(SpawnGroupID,RandomizePositionInZone)
end
return self
end
@@ -19536,10 +19959,11 @@ self.RepeatOnEngineShutDown=false
self.RepeatOnLanding=true
return self
end
-function SPAWN:InitRepeatOnLanding()
+function SPAWN:InitRepeatOnLanding(WaitingTime)
self:InitRepeat()
self.RepeatOnEngineShutDown=false
self.RepeatOnLanding=true
+self.RepeatOnLandingTime=(WaitingTime and WaitingTime>3)and WaitingTime or 3
return self
end
function SPAWN:InitRepeatOnEngineShutDown()
@@ -19688,7 +20112,7 @@ else
local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate
local SpawnZone=self.SpawnGroups[self.SpawnIndex].SpawnZone
if SpawnTemplate then
-local PointVec3=POINT_VEC3:New(SpawnTemplate.route.points[1].x,SpawnTemplate.route.points[1].alt,SpawnTemplate.route.points[1].y)
+local PointVec3=COORDINATE:New(SpawnTemplate.route.points[1].x,SpawnTemplate.route.points[1].alt,SpawnTemplate.route.points[1].y)
if self.SpawnRandomizePosition then
local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnRandomizePositionOuterRadius,self.SpawnRandomizePositionInnerRadius)
local CurrentX=SpawnTemplate.units[1].x
@@ -19835,6 +20259,11 @@ end
if self.SpawnHiddenOnMap then
SpawnTemplate.hidden=self.SpawnHiddenOnMap
end
+if self.SpawnValidateAndRepositionGroundUnits then
+local units=SpawnTemplate.units
+local gPos={x=SpawnTemplate.x,y=SpawnTemplate.y}
+UTILS.ValidateAndRepositionGroundUnits(units,gPos,self.SpawnValidateAndRepositionGroundUnitsRadius,self.SpawnValidateAndRepositionGroundUnitsSpacing)
+end
SpawnTemplate.CategoryID=self.SpawnInitCategory or SpawnTemplate.CategoryID
SpawnTemplate.CountryID=self.SpawnInitCountry or SpawnTemplate.CountryID
SpawnTemplate.CoalitionID=self.SpawnInitCoalition or SpawnTemplate.CoalitionID
@@ -19911,16 +20340,14 @@ end
if self:_GetSpawnIndex(self.SpawnIndex+1)then
local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate
if SpawnTemplate then
-local GroupAlive=self:GetGroupFromIndex(self.SpawnIndex)
-local TemplateGroup=GROUP:FindByName(self.SpawnTemplatePrefix)
-local TemplateUnit=TemplateGroup:GetUnit(1)
-local group=TemplateGroup
+local group=GROUP:FindByName(self.SpawnTemplatePrefix)
+local unit=group:GetUnit(1)
local istransport=group:HasAttribute("Transports")and group:HasAttribute("Planes")
local isawacs=group:HasAttribute("AWACS")
local isfighter=group:HasAttribute("Fighters")or group:HasAttribute("Interceptors")or group:HasAttribute("Multirole fighters")or(group:HasAttribute("Bombers")and not group:HasAttribute("Strategic bombers"))
local isbomber=group:HasAttribute("Strategic bombers")
local istanker=group:HasAttribute("Tankers")
-local ishelo=TemplateUnit:HasAttribute("Helicopters")
+local ishelo=unit:HasAttribute("Helicopters")
local nunits=#SpawnTemplate.units
local SpawnPoint=SpawnTemplate.route.points[1]
SpawnPoint.linkUnit=nil
@@ -19934,26 +20361,18 @@ SpawnPoint.helipadId=AirbaseID
elseif AirbaseCategory==Airbase.Category.HELIPAD then
SpawnPoint.linkUnit=AirbaseID
SpawnPoint.helipadId=AirbaseID
-elseif AirbaseCategory==Airbase.Category.AIRDROME then
+else
SpawnPoint.airdromeId=AirbaseID
end
SpawnPoint.alt=0
SpawnPoint.type=GROUPTEMPLATE.Takeoff[Takeoff][1]
SpawnPoint.action=GROUPTEMPLATE.Takeoff[Takeoff][2]
local spawnonground=not(Takeoff==SPAWN.Takeoff.Air)
-local spawnonship=false
-local spawnonfarp=false
-local spawnonrunway=false
-local spawnonairport=false
-if spawnonground then
-if AirbaseCategory==Airbase.Category.SHIP then
-spawnonship=true
-elseif AirbaseCategory==Airbase.Category.HELIPAD then
-spawnonfarp=true
-elseif AirbaseCategory==Airbase.Category.AIRDROME then
-spawnonairport=true
-end
-spawnonrunway=Takeoff==SPAWN.Takeoff.Runway
+local autoparking=false
+if SpawnAirbase.isAirdrome then
+autoparking=false
+else
+autoparking=true
end
local parkingspots={}
local parkingindex={}
@@ -19961,8 +20380,8 @@ local spots
if spawnonground and not SpawnTemplate.parked then
local nfree=0
local termtype=TerminalType
-if spawnonrunway then
-if spawnonship then
+if Takeoff==SPAWN.Takeoff.Runway then
+if SpawnAirbase.isShip then
if ishelo then
termtype=AIRBASE.TerminalType.HelicopterUsable
else
@@ -19977,43 +20396,46 @@ local scanunits=true
local scanstatics=true
local scanscenery=false
local verysafe=false
-if spawnonship or spawnonfarp or spawnonrunway then
+if autoparking then
nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype,true)
spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype,true)
+elseif Parkingdata~=nil then
+nfree=#Parkingdata
+spots=Parkingdata
else
if ishelo then
if termtype==nil then
-spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.HelicopterOnly,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
+spots=SpawnAirbase:FindFreeParkingSpotForAircraft(group,AIRBASE.TerminalType.HelicopterOnly,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
if nfree=1 then
for i=1,nunits do
table.insert(parkingspots,spots[1].Coordinate)
@@ -20023,7 +20445,7 @@ PointVec3=spots[1].Coordinate
else
_notenough=true
end
-elseif spawnonairport then
+else
if nfree>=nunits then
for i=1,nunits do
table.insert(parkingspots,spots[i].Coordinate)
@@ -20036,10 +20458,7 @@ end
if _notenough then
if EmergencyAirSpawn and not self.SpawnUnControlled then
self:E(string.format("WARNING: Group %s has no parking spots at %s ==> air start!",self.SpawnTemplatePrefix,SpawnAirbase:GetName()))
-spawnonground=false
-spawnonship=false
-spawnonfarp=false
-spawnonrunway=false
+autoparking=false
SpawnPoint.type=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1]
SpawnPoint.action=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2]
PointVec3.x=PointVec3.x+math.random(-500,500)
@@ -20077,7 +20496,7 @@ local BY=SpawnTemplate.route.points[1].y
local TX=PointVec3.x+(SX-BX)
local TY=PointVec3.z+(SY-BY)
if spawnonground then
-if spawnonship or spawnonfarp or spawnonrunway then
+if autoparking then
SpawnTemplate.units[UnitID].x=PointVec3.x
SpawnTemplate.units[UnitID].y=PointVec3.z
SpawnTemplate.units[UnitID].alt=PointVec3.y
@@ -20107,12 +20526,9 @@ SpawnTemplate.uncontrolled=self.SpawnUnControlled
local GroupSpawned=self:SpawnWithIndex(self.SpawnIndex)
if Takeoff==GROUP.Takeoff.Air then
for UnitID,UnitSpawned in pairs(GroupSpawned:GetUnits())do
-SCHEDULER:New(nil,BASE.CreateEventTakeoff,{GroupSpawned,timer.getTime(),UnitSpawned:GetDCSObject()},5)
+self:ScheduleOnce(5,BASE.CreateEventTakeoff,{GroupSpawned,timer.getTime(),UnitSpawned:GetDCSObject()})
end
end
-if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then
-SCHEDULER:New(nil,AIRBASE.CheckOnRunWay,{SpawnAirbase,GroupSpawned,75,true},1.0)
-end
return GroupSpawned
end
end
@@ -20329,7 +20745,7 @@ self:SetSpawnIndex(0)
return nil
end
function SPAWN:SpawnFromVec3(Vec3,SpawnIndex)
-local PointVec3=POINT_VEC3:NewFromVec3(Vec3)
+local PointVec3=COORDINATE:NewFromVec3(Vec3)
if SpawnIndex then
else
SpawnIndex=self.SpawnIndex+1
@@ -20830,14 +21246,17 @@ SpawnTemplate.y=SpawnVec2.y
end
return self
end
-function SPAWN:_RandomizeZones(SpawnIndex)
+function SPAWN:_RandomizeZones(SpawnIndex,RandomizePositionInZone)
if self.SpawnRandomizeZones then
local SpawnZone=nil
while not SpawnZone do
local ZoneID=math.random(#self.SpawnZoneTable)
SpawnZone=self.SpawnZoneTable[ZoneID]:GetZoneMaybe()
end
-local SpawnVec2=SpawnZone:GetRandomVec2()
+local SpawnVec2=SpawnZone:GetVec2()
+if RandomizePositionInZone~=false then
+SpawnVec2=SpawnZone:GetRandomVec2()
+end
local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate
self.SpawnGroups[SpawnIndex].SpawnZone=SpawnZone
for UnitID=1,#SpawnTemplate.units do
@@ -20964,7 +21383,7 @@ if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefi
SpawnGroup:SetState(SpawnGroup,"Spawn_Landed",true)
if self.RepeatOnLanding then
local SpawnGroupIndex=self:GetSpawnIndexFromGroup(SpawnGroup)
-SCHEDULER:New(nil,self.ReSpawn,{self,SpawnGroupIndex},3)
+SCHEDULER:New(nil,self.ReSpawn,{self,SpawnGroupIndex},self.RepeatOnLandingTime or 3)
end
end
end
@@ -21048,6 +21467,7 @@ self.CountryID=SpawnCountryID or CountryID
self.CategoryID=CategoryID
self.CoalitionID=CoalitionID
self.SpawnIndex=0
+self.StaticCopyFrom=SpawnTemplateName
else
error("SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '"..tostring(SpawnTemplateName).."'")
end
@@ -21128,11 +21548,13 @@ function SPAWNSTATIC:InitShape(StaticShape)
self.InitStaticShape=StaticShape
return self
end
-function SPAWNSTATIC:InitFARP(CallsignID,Frequency,Modulation)
+function SPAWNSTATIC:InitFARP(CallsignID,Frequency,Modulation,DynamicSpawns,DynamicHotStarts)
self.InitFarp=true
self.InitFarpCallsignID=CallsignID or 1
self.InitFarpFreq=Frequency or 127.5
self.InitFarpModu=Modulation or 0
+self.InitFarpDynamicSpawns=DynamicSpawns
+self.InitFarpDynamicHotStarts=(DynamicSpawns==true and DynamicHotStarts==true)and true or nil
return self
end
function SPAWNSTATIC:InitCargoMass(Mass)
@@ -21162,6 +21584,11 @@ self.InitOffsetY=OffsetY or 0
self.InitOffsetAngle=OffsetAngle or 0
return self
end
+function SPAWNSTATIC:InitValidateAndRepositionStatic(OnOff,MaxRadius)
+self.ValidateAndRepositionStatic=OnOff
+self.ValidateAndRepositionStaticMaxRadius=MaxRadius
+return self
+end
function SPAWNSTATIC:OnSpawnStatic(SpawnCallBackFunction,...)
self:F("OnSpawnStatic")
self.SpawnFunctionHook=SpawnCallBackFunction
@@ -21196,7 +21623,7 @@ end
return self:_SpawnStatic(self.TemplateStaticUnit,self.CountryID)
end
function SPAWNSTATIC:SpawnFromZone(Zone,Heading,NewName)
-local Static=self:SpawnFromPointVec2(Zone:GetPointVec2(),Heading,NewName)
+local Static=self:SpawnFromCoordinate(Zone:GetCoordinate(),Heading,NewName)
return Static
end
function SPAWNSTATIC:_SpawnStatic(Template,CountryID)
@@ -21247,9 +21674,14 @@ Template.unitId=nil
end
self.SpawnIndex=self.SpawnIndex+1
Template.name=self.InitStaticName or string.format("%s#%05d",self.SpawnTemplatePrefix,self.SpawnIndex)
-local mystatic=_DATABASE:AddStatic(Template.name)
-self:T(Template)
local Static=nil
+if self.ValidateAndRepositionStatic then
+local validPos=UTILS.ValidateAndRepositionStatic(CountryID,Template.category,Template.type,Template,Template.shape_name,self.ValidateAndRepositionStaticMaxRadius)
+if validPos then
+Template.x=validPos.x
+Template.y=validPos.y
+end
+end
if self.InitFarp then
local TemplateGroup={}
TemplateGroup.units={}
@@ -21259,6 +21691,12 @@ TemplateGroup.hidden=false
TemplateGroup.x=Template.x
TemplateGroup.y=Template.y
TemplateGroup.name=Template.name
+if self.InitFarpDynamicSpawns==true then
+TemplateGroup.units[1].dynamicSpawn=true
+if self.InitFarpDynamicHotStarts==true then
+TemplateGroup.units[1].allowHotStart=true
+end
+end
self:T("Spawning FARP")
self:T({Template=Template})
self:T({TemplateGroup=TemplateGroup})
@@ -21273,10 +21711,28 @@ else
self:T("Spawning Static")
self:T2({Template=Template})
Static=coalition.addStaticObject(CountryID,Template)
+if Static then
+self:T(string.format("Succesfully spawned static object \"%s\" ID=%d",Static:getName(),Static:getID()))
+else
+self:E(string.format("ERROR: DCS static object \"%s\" is nil!",tostring(Template.name)))
end
+end
+local mystatic=_DATABASE:AddStatic(Template.name)
if self.SpawnFunctionHook then
self:ScheduleOnce(0.3,self.SpawnFunctionHook,mystatic,unpack(self.SpawnFunctionArguments))
end
+if self.StaticCopyFrom~=nil then
+mystatic.StaticCopyFrom=self.StaticCopyFrom
+if not _DATABASE.Templates.Statics[Template.name]then
+local TemplateGroup={}
+TemplateGroup.units={}
+TemplateGroup.units[1]=Template
+TemplateGroup.x=Template.x
+TemplateGroup.y=Template.y
+TemplateGroup.name=Template.name
+_DATABASE:_RegisterStaticTemplate(TemplateGroup,self.CoalitionID,self.CategoryID,CountryID)
+end
+end
return mystatic
end
TIMER={
@@ -21524,7 +21980,7 @@ MARKEROPS_BASE={
ClassName="MARKEROPS",
Tag="mytag",
Keywords={},
-version="0.1.3",
+version="0.1.4",
debug=false,
Casesensitive=true,
}
@@ -21557,19 +22013,18 @@ if Event==nil or Event.idx==nil then
self:E("Skipping onEvent. Event or Event.idx unknown.")
return true
end
-local vec3={y=Event.pos.y,x=Event.pos.x,z=Event.pos.z}
-local coord=COORDINATE:NewFromVec3(vec3)
-if self.debug then
-local coordtext=coord:ToStringLLDDM()
-local text=tostring(Event.text)
-local m=MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
-end
local coalition=Event.MarkCoalition
if Event.id==world.event.S_EVENT_MARK_ADDED then
self:T({event="S_EVENT_MARK_ADDED",carrier=Event.IniGroupName,vec3=Event.pos})
local Eventtext=tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext)then
+local coord=COORDINATE:NewFromVec3({y=Event.pos.y,x=Event.pos.x,z=Event.pos.z})
+if self.debug then
+local coordtext=coord:ToStringLLDDM()
+local text=tostring(Event.text)
+local m=MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
+end
local matchtable=self:_MatchKeywords(Eventtext)
self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event)
end
@@ -21579,6 +22034,12 @@ self:T({event="S_EVENT_MARK_CHANGE",carrier=Event.IniGroupName,vec3=Event.pos})
local Eventtext=tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext)then
+local coord=COORDINATE:NewFromVec3({y=Event.pos.y,x=Event.pos.x,z=Event.pos.z})
+if self.debug then
+local coordtext=coord:ToStringLLDDM()
+local text=tostring(Event.text)
+local m=MESSAGE:New(string.format("Mark changed at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
+end
local matchtable=self:_MatchKeywords(Eventtext)
self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event)
end
@@ -21868,9 +22329,9 @@ point.markerID=nil
return point
end
CLIENTMENU={
-ClassName="CLIENTMENUE",
+ClassName="CLIENTMENU",
lid="",
-version="0.1.2",
+version="0.1.3",
name=nil,
path=nil,
group=nil,
@@ -22035,7 +22496,7 @@ end
CLIENTMENUMANAGER={
ClassName="CLIENTMENUMANAGER",
lid="",
-version="0.1.6",
+version="0.1.7",
name=nil,
clientset=nil,
menutree={},
@@ -22059,7 +22520,7 @@ self:I(self.lid.."Created")
end
return self
end
-function CLIENTMENUMANAGER:_EventHandler(EventData)
+function CLIENTMENUMANAGER:_EventHandler(EventData,Retry)
self:T(self.lid.."_EventHandler: "..EventData.id)
if EventData.id==EVENTS.PlayerLeaveUnit or EventData.id==EVENTS.Ejection or EventData.id==EVENTS.Crash or EventData.id==EVENTS.PilotDead then
self:T(self.lid.."Leave event for player: "..tostring(EventData.IniPlayerName))
@@ -22071,6 +22532,9 @@ elseif(EventData.id==EVENTS.PlayerEnterAircraft)and EventData.IniCoalition==self
if EventData.IniPlayerName and EventData.IniGroup then
if(not self.clientset:IsIncludeObject(_DATABASE:FindClient(EventData.IniUnitName)))then
self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName)
+if not Retry then
+self:ScheduleOnce(2,CLIENTMENUMANAGER._EventHandler,self,EventData,true)
+end
return self
end
local player=_DATABASE:FindClient(EventData.IniUnitName)
@@ -22106,7 +22570,7 @@ self:HandleEvent(EVENTS.Crash,self._EventHandler)
self:HandleEvent(EVENTS.PilotDead,self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler)
-self:SetEventPriority(5)
+self:SetEventPriority(6)
return self
end
function CLIENTMENUMANAGER:NewEntry(Text,Parent,Function,...)
@@ -22306,6 +22770,10 @@ self.menutree=nil
self.menutree={}
return self
end
+function CLIENTMENUMANAGER:DeleteEntry(Entry,Client)
+self:T(self.lid.."DeleteEntry")
+return self:DeleteF10Entry(Entry,Client)
+end
function CLIENTMENUMANAGER:DeleteF10Entry(Entry,Client)
self:T(self.lid.."DeleteF10Entry")
local Set=self.clientset.Set
@@ -22582,6 +23050,9 @@ end
function POSITIONABLE:GetPosition()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
+if self:IsInstanceOf("GROUP")then
+DCSPositionable=self:GetFirstUnitAlive():GetDCSObject()
+end
if DCSPositionable then
local PositionablePosition=DCSPositionable:getPosition()
self:T3(PositionablePosition)
@@ -22641,6 +23112,14 @@ function POSITIONABLE:GetVec3()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local vec3=DCSPositionable:getPoint()
+if not vec3 then
+local pos=DCSPositionable:getPosition()
+if pos and pos.p then
+vec3=pos.p
+else
+self:E({"Cannot get the position from DCS Object for GetVec3",Positionable=self,Alive=self:IsAlive()})
+end
+end
return vec3
end
self:E({"Cannot get the Positionable DCS Object for GetVec3",Positionable=self,Alive=self:IsAlive()})
@@ -22660,10 +23139,10 @@ self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionableVec3=DCSPositionable:getPosition().p
-local PositionablePointVec2=POINT_VEC2:NewFromVec3(PositionableVec3)
+local PositionablePointVec2=COORDINATE:NewFromVec3(PositionableVec3)
return PositionablePointVec2
end
-self:E({"Cannot GetPointVec2",Positionable=self,Alive=self:IsAlive()})
+self:E({"Cannot Coordinate",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetPointVec3()
@@ -22675,7 +23154,7 @@ self.pointvec3.x=PositionableVec3.x
self.pointvec3.y=PositionableVec3.y
self.pointvec3.z=PositionableVec3.z
else
-self.pointvec3=POINT_VEC3:NewFromVec3(PositionableVec3)
+self.pointvec3=COORDINATE:NewFromVec3(PositionableVec3)
end
return self.pointvec3
end
@@ -22686,6 +23165,7 @@ function POSITIONABLE:GetCoord()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionableVec3=self:GetVec3()
+if PositionableVec3 then
if self.coordinate then
self.coordinate:UpdateFromVec3(PositionableVec3)
else
@@ -22693,6 +23173,7 @@ self.coordinate=COORDINATE:NewFromVec3(PositionableVec3)
end
return self.coordinate
end
+end
BASE:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()})
return nil
end
@@ -22700,11 +23181,13 @@ function POSITIONABLE:GetCoordinate()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionableVec3=self:GetVec3()
+if PositionableVec3 then
local coord=COORDINATE:NewFromVec3(PositionableVec3)
local heading=self:GetHeading()
coord.Heading=heading
return coord
end
+end
self:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()})
return nil
end
@@ -24029,6 +24512,49 @@ self:SetCommand(CommandSetFrequencyForUnit)
end
return self
end
+function CONTROLLABLE:CommandSmokeOnOff(OnOff,Delay)
+local switch=(OnOff==nil)and true or OnOff
+local command={
+id='SMOKE_ON_OFF',
+params={
+value=switch
+}
+}
+if Delay and Delay>0 then
+SCHEDULER:New(nil,self.CommandSmokeOnOff,{self,switch},Delay)
+else
+self:SetCommand(command)
+end
+return self
+end
+function CONTROLLABLE:CommandSmokeON(Delay)
+local command={
+id='SMOKE_ON_OFF',
+params={
+value=true
+}
+}
+if Delay and Delay>0 then
+SCHEDULER:New(nil,self.CommandSmokeON,{self},Delay)
+else
+self:SetCommand(command)
+end
+return self
+end
+function CONTROLLABLE:CommandSmokeOFF(Delay)
+local command={
+id='SMOKE_ON_OFF',
+params={
+value=false
+}
+}
+if Delay and Delay>0 then
+SCHEDULER:New(nil,self.CommandSmokeOFF,{self},Delay)
+else
+self:SetCommand(command)
+end
+return self
+end
function CONTROLLABLE:TaskEPLRS(SwitchOnOff,idx)
if SwitchOnOff==nil then
SwitchOnOff=true
@@ -24228,7 +24754,6 @@ groupsForEmbarking=g4e,
return Disembarking
end
function CONTROLLABLE:TaskOrbitCircleAtVec2(Point,Altitude,Speed)
-self:F2({self.ControllableName,Point,Altitude,Speed})
local DCSTask={
id='Orbit',
params={
@@ -25384,6 +25909,18 @@ return self
end
return nil
end
+function CONTROLLABLE:OptionPreferVerticalLanding()
+self:F2({self.ControllableName})
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local Controller=self:_GetController()
+if self:IsAir()then
+Controller:setOption(AI.Option.Air.id.PREFER_VERTICAL,true)
+end
+return self
+end
+return nil
+end
function CONTROLLABLE:OptionROTEvadeFirePossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
@@ -25697,6 +26234,34 @@ return self
end
return nil
end
+function CONTROLLABLE:SetOptionLandingStraightIn()
+self:F2({self.ControllableName})
+if self:IsAir()then
+self:SetOption("36","0")
+end
+return self
+end
+function CONTROLLABLE:SetOptionLandingForcePair()
+self:F2({self.ControllableName})
+if self:IsAir()then
+self:SetOption("36","1")
+end
+return self
+end
+function CONTROLLABLE:SetOptionLandingRestrictPair()
+self:F2({self.ControllableName})
+if self:IsAir()then
+self:SetOption("36","2")
+end
+return self
+end
+function CONTROLLABLE:SetOptionLandingOverheadBreak()
+self:F2({self.ControllableName})
+if self:IsAir()then
+self:SetOption("36","3")
+end
+return self
+end
function CONTROLLABLE:SetOptionRadarUsing(Option)
self:F2({self.ControllableName})
if self:IsAir()then
@@ -25778,6 +26343,9 @@ end
function CONTROLLABLE:RelocateGroundRandomInRadius(speed,radius,onroad,shortcut,formation,onland)
self:F2({self.ControllableName})
local _coord=self:GetCoordinate()
+if not _coord then
+return self
+end
local _radius=radius or 500
local _speed=speed or 20
local _tocoord=_coord:GetRandomCoordinateInRadius(_radius,100)
@@ -26720,68 +27288,99 @@ end
return self
end
function CONTROLLABLE:NewIRMarker(EnableImmediately,Runtime)
-if self.ClassName=="GROUP"then
+self:T2("NewIRMarker")
+if self:IsInstanceOf("GROUP")then
+if self.IRMarkerGroup==true then return end
self.IRMarkerGroup=true
self.IRMarkerUnit=false
-elseif self.ClassName=="UNIT"then
+elseif self:IsInstanceOf("UNIT")then
+if self.IRMarkerUnit==true then return end
self.IRMarkerGroup=false
self.IRMarkerUnit=true
end
-self.spot=nil
-self.timer=nil
-self.stoptimer=nil
+self.Runtime=Runtime or 60
if EnableImmediately and EnableImmediately==true then
self:EnableIRMarker(Runtime)
end
return self
end
function CONTROLLABLE:EnableIRMarker(Runtime)
+self:T2("EnableIRMarker")
if self.IRMarkerGroup==nil then
self:NewIRMarker(true,Runtime)
return
end
-if(self.IRMarkerGroup==true)then
-self:EnableIRMarkerForGroup()
+if self:IsInstanceOf("GROUP")then
+self:EnableIRMarkerForGroup(Runtime)
return
end
+if self.timer and self.timer:IsRunning()then return self end
+local Runtime=Runtime or self.Runtime
self.timer=TIMER:New(CONTROLLABLE._MarkerBlink,self)
self.timer:Start(nil,1-math.random(1,5)/10/2,Runtime)
+self.IRMarkerUnit=true
return self
end
function CONTROLLABLE:DisableIRMarker()
-if(self.IRMarkerGroup==true)then
+self:T2("DisableIRMarker")
+if self:IsInstanceOf("GROUP")then
self:DisableIRMarkerForGroup()
return
end
if self.spot then
-self.spot:destroy()
self.spot=nil
+end
if self.timer and self.timer:IsRunning()then
self.timer:Stop()
self.timer=nil
end
+if self:IsInstanceOf("GROUP")then
+self.IRMarkerGroup=nil
+elseif self:IsInstanceOf("UNIT")then
+self.IRMarkerUnit=nil
end
return self
end
-function CONTROLLABLE:EnableIRMarkerForGroup()
-if self.ClassName=="GROUP"then
+function CONTROLLABLE:EnableIRMarkerForGroup(Runtime)
+self:T2("EnableIRMarkerForGroup")
+if self:IsInstanceOf("GROUP")
+then
local units=self:GetUnits()or{}
for _,_unit in pairs(units)do
-_unit:EnableIRMarker()
+_unit:EnableIRMarker(Runtime)
end
+self.IRMarkerGroup=true
end
return self
end
function CONTROLLABLE:DisableIRMarkerForGroup()
-if self.ClassName=="GROUP"then
+self:T2("DisableIRMarkerForGroup")
+if self:IsInstanceOf("GROUP")then
local units=self:GetUnits()or{}
for _,_unit in pairs(units)do
_unit:DisableIRMarker()
end
+self.IRMarkerGroup=nil
end
return self
end
+function CONTROLLABLE:HasIRMarker()
+self:T2("HasIRMarker")
+if self:IsInstanceOf("GROUP")then
+local units=self:GetUnits()or{}
+for _,_unit in pairs(units)do
+if _unit.timer and _unit.timer:IsRunning()then return true end
+end
+elseif self.timer and self.timer:IsRunning()then return true end
+return false
+end
+function CONTROLLABLE._StopSpot(spot)
+if spot then
+spot:destroy()
+end
+end
function CONTROLLABLE:_MarkerBlink()
+self:T2("_MarkerBlink")
if self:IsAlive()~=true then
self:DisableIRMarker()
return
@@ -26789,13 +27388,18 @@ end
self.timer.dT=1-(math.random(1,2)/10/2)
local _,_,unitBBHeight,_=self:GetObjectSize()
local unitPos=self:GetPositionVec3()
-self.spot=Spot.createInfraRed(
+if self.timer:IsRunning()then
+self:T2("Create Spot")
+local spot=Spot.createInfraRed(
self.DCSUnit,
{x=0,y=(unitBBHeight+1),z=0},
{x=unitPos.x,y=(unitPos.y+unitBBHeight),z=unitPos.z}
)
-local offTimer=TIMER:New(function()if self.spot then self.spot:destroy()end end)
+self.spot=spot
+local offTimer=nil
+local offTimer=TIMER:New(CONTROLLABLE._StopSpot,spot)
offTimer:Start(0.5)
+end
return self
end
GROUP={
@@ -26834,6 +27438,8 @@ GROUND_TRAIN="Ground_Train",
GROUND_EWR="Ground_EWR",
GROUND_AAA="Ground_AAA",
GROUND_SAM="Ground_SAM",
+GROUND_SHORAD="Ground_SHORAD",
+GROUND_BALLISTICMISSILE="Ground_BallisticMissile",
GROUND_OTHER="Ground_OtherGround",
NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier",
NAVAL_WARSHIP="Naval_WarShip",
@@ -27095,7 +27701,11 @@ end
return nil
end
function GROUP:IsPlayer()
-return self:GetUnit(1):IsPlayer()
+local unit=self:GetUnit(1)
+if unit then
+return unit:IsPlayer()
+end
+return false
end
function GROUP:GetUnit(UnitNumber)
local DCSGroup=self:GetDCSObject()
@@ -27186,13 +27796,16 @@ function GROUP:GetVelocityVec3()
local DCSGroup=self:GetDCSObject()
if DCSGroup and DCSGroup:isExist()then
local GroupUnits=DCSGroup:getUnits()
-local GroupCount=#GroupUnits
+local GroupCount=0
local VelocityVec3={x=0,y=0,z=0}
for _,DCSUnit in pairs(GroupUnits)do
+if DCSUnit:isExist()and DCSUnit:isActive()then
local UnitVelocityVec3=DCSUnit:getVelocity()
VelocityVec3.x=VelocityVec3.x+UnitVelocityVec3.x
VelocityVec3.y=VelocityVec3.y+UnitVelocityVec3.y
VelocityVec3.z=VelocityVec3.z+UnitVelocityVec3.z
+GroupCount=GroupCount+1
+end
end
VelocityVec3.x=VelocityVec3.x/GroupCount
VelocityVec3.y=VelocityVec3.y/GroupCount
@@ -27333,7 +27946,7 @@ if FirstUnit then
local FirstUnitPointVec2=FirstUnit:GetPointVec2()
return FirstUnitPointVec2
end
-BASE:E({"Cannot GetPointVec2",Group=self,Alive=self:IsAlive()})
+BASE:E({"Cannot get COORDINATE",Group=self,Alive=self:IsAlive()})
return nil
end
function GROUP:GetAverageCoordinate()
@@ -27354,13 +27967,20 @@ end
end
end
function GROUP:GetCoordinate()
+local vec3=self:GetVec3()
+local coord
+if vec3 then
+coord=COORDINATE:NewFromVec3(vec3)
+coord.Heading=self:GetHeading()or 0
+return coord
+end
local Units=self:GetUnits()or{}
for _,_unit in pairs(Units)do
local FirstUnit=_unit
if FirstUnit and FirstUnit:IsAlive()then
local FirstUnitCoordinate=FirstUnit:GetCoordinate()
if FirstUnitCoordinate then
-local Heading=self:GetHeading()
+local Heading=self:GetHeading()or 0
FirstUnitCoordinate.Heading=Heading
return FirstUnitCoordinate
end
@@ -27375,6 +27995,11 @@ local position=_unit:getPosition()
local point=position.p~=nil and position.p or _unit:GetPoint()
if point then
local coord=COORDINATE:NewFromVec3(point)
+coord.Heading=0
+local munit=UNIT:Find(_unit)
+if munit then
+coord.Heading=munit:GetHeading()or 0
+end
return coord
end
end
@@ -27626,12 +28251,14 @@ local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupVelocityMax=0
for Index,UnitData in pairs(DCSGroup:getUnits())do
+if UnitData:isExist()and UnitData:isActive()then
local UnitVelocityVec3=UnitData:getVelocity()
local UnitVelocity=math.abs(UnitVelocityVec3.x)+math.abs(UnitVelocityVec3.y)+math.abs(UnitVelocityVec3.z)
if UnitVelocity>GroupVelocityMax then
GroupVelocityMax=UnitVelocity
end
end
+end
return GroupVelocityMax
end
return nil
@@ -27676,7 +28303,10 @@ return nil
end
function GROUP:GetTemplateRoutePoints()
local GroupName=self:GetName()
-return UTILS.DeepCopy(_DATABASE:GetGroupTemplate(GroupName).route.points)
+local template=_DATABASE:GetGroupTemplate(GroupName)
+if template and template.route and template.route.points then
+return UTILS.DeepCopy(template.route.points)
+end
end
function GROUP:SetTemplateControlled(Template,Controlled)
Template.uncontrolled=not Controlled
@@ -27755,12 +28385,23 @@ h=-math.rad(360-course)
end
return h
end
+local function TransFormRoute(Template,OldPos,NewPos)
+if Template.route and Template.route.points then
+for _,_point in ipairs(Template.route.points)do
+_point.x=_point.x-OldPos.x+NewPos.x
+_point.y=_point.y-OldPos.y+NewPos.y
+end
+end
+return Template
+end
if self:IsAlive()then
+local OldPos=self:GetVec2()
local Zone=self.InitRespawnZone
local Vec3=Zone and Zone:GetVec3()or self:GetVec3()
local From={x=Template.x,y=Template.y}
Template.x=Vec3.x
Template.y=Vec3.z
+local NewPos={x=Vec3.x,y=Vec3.z}
if Reset==true then
for UnitID,UnitData in pairs(self:GetUnits())do
local GroupUnit=UnitData
@@ -27771,7 +28412,7 @@ if self.InitRespawnRandomizePositionZone then
GroupUnitVec3=Zone:GetRandomVec3()
else
if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then
-GroupUnitVec3=POINT_VEC3:NewFromVec2(From):GetRandomPointVec3InRadius(self.InitRespawnRandomizePositionsOuter,self.InitRespawnRandomizePositionsInner)
+GroupUnitVec3=COORDINATE:NewFromVec3(From):GetRandomVec3InRadius(self.InitRespawnRandomizePositionsOuter,self.InitRespawnRandomizePositionsInner)
else
GroupUnitVec3=Zone:GetVec3()
end
@@ -27792,6 +28433,7 @@ Template.units[UnitID].heading=_Heading(self.InitRespawnHeading and self.InitRes
Template.units[UnitID].psi=-Template.units[UnitID].heading
end
end
+Template=TransFormRoute(Template,OldPos,NewPos)
elseif Reset==false then
for UnitID,TemplateUnitData in pairs(Template.units)do
local GroupUnitVec3={x=TemplateUnitData.x,y=TemplateUnitData.alt,z=TemplateUnitData.y}
@@ -27800,7 +28442,7 @@ if self.InitRespawnRandomizePositionZone then
GroupUnitVec3=Zone:GetRandomVec3()
else
if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then
-GroupUnitVec3=POINT_VEC3:NewFromVec2(From):GetRandomPointVec3InRadius(self.InitRespawnRandomizePositionsOuter,self.InitRespawnRandomizePositionsInner)
+GroupUnitVec3=COORDINATE:NewFromVec2(From):GetRandomPointVec3InRadius(self.InitRespawnRandomizePositionsOuter,self.InitRespawnRandomizePositionsInner)
else
GroupUnitVec3=Zone:GetVec3()
end
@@ -27814,6 +28456,7 @@ Template.units[UnitID].x=(Template.units[UnitID].x-From.x)+GroupUnitVec3.x
Template.units[UnitID].y=(Template.units[UnitID].y-From.y)+GroupUnitVec3.z
Template.units[UnitID].heading=self.InitRespawnHeading and self.InitRespawnHeading or TemplateUnitData.heading
end
+Template=TransFormRoute(Template,OldPos,NewPos)
else
local units=self:GetUnits()
for UnitID,Unit in pairs(Template.units)do
@@ -27847,10 +28490,17 @@ if self.InitRespawnModu then
Template.modulation=self.InitRespawnModu
end
self:Destroy(false)
-_DATABASE:Spawn(Template)
+if self.ValidateAndRepositionGroundUnits then
+UTILS.ValidateAndRepositionGroundUnits(Template.units)
+end
+self:ScheduleOnce(0.1,_DATABASE.Spawn,_DATABASE,Template)
self:ResetEvents()
return self
end
+function GROUP:Teleport(Coordinate)
+self:InitZone(Coordinate)
+return self:Respawn(nil,false)
+end
function GROUP:RespawnAtCurrentAirbase(SpawnTemplate,Takeoff,Uncontrolled)
if self and self:IsAlive()then
local airbase=self:GetCoordinate():GetClosestAirbase()
@@ -28047,6 +28697,8 @@ local infantry=self:HasAttribute("Infantry")
local artillery=self:HasAttribute("Artillery")
local tank=self:HasAttribute("Old Tanks")or self:HasAttribute("Modern Tanks")or self:HasAttribute("Tanks")
local aaa=self:HasAttribute("AAA")and(not self:HasAttribute("SAM elements"))
+local ballisticMissile=artillery and self:HasAttribute("SS_missile")
+local shorad=self:HasAttribute("SR SAM")
local ewr=self:HasAttribute("EWR")
local ifv=self:HasAttribute("IFV")
local sam=self:HasAttribute("SAM elements")or self:HasAttribute("Optical Tracker")
@@ -28077,6 +28729,8 @@ elseif sam then
attribute=GROUP.Attribute.GROUND_SAM
elseif aaa then
attribute=GROUP.Attribute.GROUND_AAA
+elseif artillery and ballisticMissile then
+attribute=GROUP.Attribute.GROUND_BALLISTICMISSILE
elseif artillery then
attribute=GROUP.Attribute.GROUND_ARTILLERY
elseif tank then
@@ -28148,7 +28802,7 @@ return self
end
function GROUP:ResetEvents()
self:EventDispatcher():Reset(self)
-for UnitID,UnitData in pairs(self:GetUnits())do
+for UnitID,UnitData in pairs(self:GetUnits()or{})do
UnitData:ResetEvents()
end
return self
@@ -28241,6 +28895,7 @@ end
return nil,nil
end
function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations,CustomFunction,...)
+self:T("GetCustomCallSign")
local callsign="Ghost 1"
if self:IsAlive()then
local IsPlayer=self:IsPlayer()
@@ -28251,7 +28906,9 @@ local callnumber=string.match(shortcallsign,"(%d+)$")or"91"
local callnumbermajor=string.char(string.byte(callnumber,1))
local callnumberminor=string.char(string.byte(callnumber,2))
local personalized=false
-local playername=IsPlayer==true and self:GetPlayerName()or shortcallsign
+local playername=shortcallsign
+if IsPlayer then playername=self:GetPlayerName()end
+self:T2("GetCustomCallSign outcome = "..playername)
if CustomFunction and IsPlayer then
local arguments=arg or{}
local callsign=CustomFunction(groupname,playername,unpack(arguments))
@@ -28334,7 +28991,7 @@ local issam=false
local units=self:GetUnits()
for _,_unit in pairs(units or{})do
local unit=_unit
-if unit:HasSEAD()and unit:IsGround()and(not unit:HasAttribute("Mobile AAA"))then
+if unit:IsSAM()then
issam=true
break
end
@@ -28342,18 +28999,54 @@ end
return issam
end
function GROUP:IsAAA()
-local issam=false
+local isAAA=false
local units=self:GetUnits()
for _,_unit in pairs(units or{})do
local unit=_unit
-local desc=unit:GetDesc()or{}
-local attr=desc.attributes or{}
-if unit:HasSEAD()then return false end
-if attr["AAA"]or attr["SAM related"]then
-issam=true
+if unit:IsAAA()then
+isAAA=true
+break
end
end
-return issam
+return isAAA
+end
+function GROUP:SetValidateAndRepositionGroundUnits(Enabled)
+self.ValidateAndRepositionGroundUnits=Enabled
+end
+function GROUP:GetBoundingBox()
+local bbox={min={x=math.huge,y=math.huge,z=math.huge},
+max={x=-math.huge,y=-math.huge,z=-math.huge}
+}
+local Units=self:GetUnits()or{}
+if#Units==0 then
+return nil
+end
+for _,unit in pairs(Units)do
+if unit and unit:IsAlive()then
+local ubox=unit:GetBoundingBox()
+if ubox then
+if ubox.min.xbbox.max.x then
+bbox.max.x=ubox.max.x
+end
+if ubox.max.y>bbox.max.y then
+bbox.max.y=ubox.max.y
+end
+if ubox.max.z>bbox.max.z then
+bbox.max.z=ubox.max.z
+end
+end
+end
+end
+return bbox
end
UNIT={
ClassName="UNIT",
@@ -28369,6 +29062,7 @@ if unit then
local group=unit:getGroup()
if group then
self.GroupName=group:getName()
+self.groupId=group:getID()
end
self.DCSUnit=unit
end
@@ -28491,6 +29185,9 @@ i=i+1
end
end
SpawnGroupTemplate.groupId=nil
+if self.ValidateAndRepositionGroundUnits then
+UTILS.ValidateAndRepositionGroundUnits(SpawnGroupTemplate.units)
+end
_DATABASE:Spawn(SpawnGroupTemplate)
end
function UNIT:IsActive()
@@ -28533,12 +29230,18 @@ return nil
end
function UNIT:IsPlayer()
local group=self:GetGroup()
-if not group then return false end
+if not group then
+return false
+end
local template=group:GetTemplate()
if(template==nil)or(template.units==nil)then
local DCSObject=self:GetDCSObject()
if DCSObject then
-if DCSObject:getPlayerName()~=nil then return true else return false end
+if DCSObject:getPlayerName()~=nil then
+return true
+else
+return false
+end
else
return false
end
@@ -28587,7 +29290,7 @@ end
function UNIT:GetSpeedMax()
local Desc=self:GetDesc()
if Desc then
-local SpeedMax=Desc.speedMax
+local SpeedMax=Desc.speedMax or 0
return SpeedMax*3.6
end
return 0
@@ -28733,7 +29436,7 @@ end
if ammotable[w].desc.typeName and string.find(ammotable[w].desc.typeName,"_AP",1,true)then
nAPshells=nAPshells+Nammo
end
-if ammotable[w].desc.typeName and string.find(ammotable[w].desc.typeName,"_HE",1,true)then
+if ammotable[w].desc.typeName and(string.find(ammotable[w].desc.typeName,"_HE",1,true)or string.find(ammotable[w].desc.typeName,"HESH",1,true))then
nHEshells=nHEshells+Nammo
end
elseif Category==Weapon.Category.ROCKET then
@@ -28762,7 +29465,11 @@ return nammo,nshells,nrockets,nbombs,nmissiles,narti,nAPshells,nHEshells
end
function UNIT:HasAPShells()
local _,_,_,_,_,_,shells=self:GetAmmunition()
-if shells>0 then return true else return false end
+if shells>0 then
+return true
+else
+return false
+end
end
function UNIT:GetAPShells()
local _,_,_,_,_,_,shells=self:GetAmmunition()
@@ -28774,11 +29481,19 @@ return shells or 0
end
function UNIT:HasHEShells()
local _,_,_,_,_,_,_,shells=self:GetAmmunition()
-if shells>0 then return true else return false end
+if shells>0 then
+return true
+else
+return false
+end
end
function UNIT:HasArtiShells()
local _,_,_,_,_,shells=self:GetAmmunition()
-if shells>0 then return true else return false end
+if shells>0 then
+return true
+else
+return false
+end
end
function UNIT:GetArtiShells()
local _,_,_,_,_,shells=self:GetAmmunition()
@@ -28836,7 +29551,6 @@ local DCSUnit=self:GetDCSObject()
local Units={}
if DCSUnit then
Units[1]=UNIT:Find(DCSUnit)
--self:T3(Units)
return Units
end
return nil
@@ -28921,20 +29635,30 @@ local ThreatLevels={
[10]="MR SAMs",
[11]="LR SAMs"
}
-if Attributes["LR SAM"]then ThreatLevel=10
-elseif Attributes["MR SAM"]then ThreatLevel=9
+if Attributes["LR SAM"]then
+ThreatLevel=10
+elseif Attributes["MR SAM"]then
+ThreatLevel=9
elseif Attributes["SR SAM"]and
-not Attributes["IR Guided SAM"]then ThreatLevel=8
+not Attributes["IR Guided SAM"]then
+ThreatLevel=8
elseif(Attributes["SR SAM"]or Attributes["MANPADS"])and
-Attributes["IR Guided SAM"]then ThreatLevel=7
-elseif Attributes["AAA"]then ThreatLevel=6
-elseif Attributes["Modern Tanks"]then ThreatLevel=5
+Attributes["IR Guided SAM"]then
+ThreatLevel=7
+elseif Attributes["AAA"]then
+ThreatLevel=6
+elseif Attributes["Modern Tanks"]then
+ThreatLevel=5
elseif(Attributes["Tanks"]or Attributes["IFV"])and
-Attributes["ATGM"]then ThreatLevel=4
+Attributes["ATGM"]then
+ThreatLevel=4
elseif(Attributes["Tanks"]or Attributes["IFV"])and
-not Attributes["ATGM"]then ThreatLevel=3
-elseif Attributes["Old Tanks"]or Attributes["APC"]or Attributes["Artillery"]then ThreatLevel=2
-elseif Attributes["Infantry"]or Attributes["EWR"]then ThreatLevel=1
+not Attributes["ATGM"]then
+ThreatLevel=3
+elseif Attributes["Old Tanks"]or Attributes["APC"]or Attributes["Artillery"]then
+ThreatLevel=2
+elseif Attributes["Infantry"]or Attributes["EWR"]then
+ThreatLevel=1
end
ThreatText=ThreatLevels[ThreatLevel+1]
end
@@ -28952,18 +29676,30 @@ local ThreatLevels={
[10]="Multirole Fighter",
[11]="Fighter"
}
-if Attributes["Fighters"]then ThreatLevel=10
-elseif Attributes["Multirole fighters"]then ThreatLevel=9
-elseif Attributes["Interceptors"]then ThreatLevel=9
-elseif Attributes["Battleplanes"]then ThreatLevel=8
-elseif Attributes["Battle airplanes"]then ThreatLevel=8
-elseif Attributes["Attack helicopters"]then ThreatLevel=7
-elseif Attributes["Strategic bombers"]then ThreatLevel=6
-elseif Attributes["Bombers"]then ThreatLevel=5
-elseif Attributes["UAVs"]then ThreatLevel=4
-elseif Attributes["Transport helicopters"]then ThreatLevel=3
-elseif Attributes["AWACS"]then ThreatLevel=2
-elseif Attributes["Tankers"]then ThreatLevel=1
+if Attributes["Fighters"]then
+ThreatLevel=10
+elseif Attributes["Multirole fighters"]then
+ThreatLevel=9
+elseif Attributes["Interceptors"]then
+ThreatLevel=9
+elseif Attributes["Battleplanes"]then
+ThreatLevel=8
+elseif Attributes["Battle airplanes"]then
+ThreatLevel=8
+elseif Attributes["Attack helicopters"]then
+ThreatLevel=7
+elseif Attributes["Strategic bombers"]then
+ThreatLevel=6
+elseif Attributes["Bombers"]then
+ThreatLevel=5
+elseif Attributes["UAVs"]then
+ThreatLevel=4
+elseif Attributes["Transport helicopters"]then
+ThreatLevel=3
+elseif Attributes["AWACS"]then
+ThreatLevel=2
+elseif Attributes["Tankers"]then
+ThreatLevel=1
end
ThreatText=ThreatLevels[ThreatLevel+1]
end
@@ -28981,12 +29717,18 @@ local ThreatLevels={
[10]="",
[11]="Aircraft Carrier"
}
-if Attributes["Aircraft Carriers"]then ThreatLevel=10
-elseif Attributes["Destroyers"]then ThreatLevel=8
-elseif Attributes["Cruisers"]then ThreatLevel=6
-elseif Attributes["Frigates"]then ThreatLevel=4
-elseif Attributes["Corvettes"]then ThreatLevel=2
-elseif Attributes["Light armed ships"]then ThreatLevel=1
+if Attributes["Aircraft Carriers"]then
+ThreatLevel=10
+elseif Attributes["Destroyers"]then
+ThreatLevel=8
+elseif Attributes["Cruisers"]then
+ThreatLevel=6
+elseif Attributes["Frigates"]then
+ThreatLevel=4
+elseif Attributes["Corvettes"]then
+ThreatLevel=2
+elseif Attributes["Light armed ships"]then
+ThreatLevel=1
end
ThreatText=ThreatLevels[ThreatLevel+1]
end
@@ -29157,6 +29899,7 @@ local VCL=nil
local VCN=nil
local FGL=false
local template=self:GetTemplate()
+if template then
if template.AddPropAircraft then
if template.AddPropAircraft.STN_L16 then
STN=template.AddPropAircraft.STN_L16
@@ -29172,8 +29915,65 @@ end
if template.datalinks and template.datalinks.SADL and template.datalinks.SADL.settings then
FGL=template.datalinks.SADL.settings.flightLead
end
+end
return STN,VCL,VCN,FGL
end
+do
+function UNIT:SetAIOnOff(AIOnOff)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local DCSController=DCSUnit:getController()
+if DCSController then
+DCSController:setOnOff(AIOnOff)
+return self
+end
+end
+return nil
+end
+function UNIT:SetAIOn()
+return self:SetAIOnOff(true)
+end
+function UNIT:SetAIOff()
+return self:SetAIOnOff(false)
+end
+end
+function UNIT:IsSAM()
+if self:HasSEAD()and self:IsGround()and(not self:HasAttribute("Mobile AAA"))then
+return true
+end
+return false
+end
+function UNIT:IsEWR()
+if self:IsGround()then
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local attrs=DCSUnit:getDesc().attributes
+return attrs["EWR"]==true
+end
+end
+return false
+end
+function UNIT:IsAAA()
+local unit=self
+local desc=unit:GetDesc()or{}
+local attr=desc.attributes or{}
+if unit:HasSEAD()then
+return false
+end
+if attr["AAA"]or attr["SAM related"]then
+return true
+end
+return false
+end
+function UNIT:SetLife(Percent)
+net.dostring_in("mission",string.format("a_unit_set_life_percentage(%d, %f)",self:GetID(),Percent))
+end
+function UNIT:SetCarrierIlluminationMode(Mode)
+UTILS.SetCarrierIlluminationMode(self:GetID(),Mode)
+end
+function UNIT:SetValidateAndRepositionGroundUnits(Enabled)
+self.ValidateAndRepositionGroundUnits=Enabled
+end
CLIENT={
ClassName="CLIENT",
ClientName=nil,
@@ -29478,6 +30278,8 @@ local DCSStatic=StaticObject.getByName(self.StaticName)
if DCSStatic then
local Life0=DCSStatic:getLife()or 1
self.Life0=Life0
+else
+self:E(string.format("Static object %s does not exist!",tostring(self.StaticName)))
end
return self
end
@@ -29799,7 +30601,6 @@ AIRBASE.Syria={
["Al_Dumayr"]="Al-Dumayr",
["Al_Qusayr"]="Al Qusayr",
["Aleppo"]="Aleppo",
-["Amman"]="Amman",
["An_Nasiriyah"]="An Nasiriyah",
["At_Tanf"]="At Tanf",
["Bassel_Al_Assad"]="Bassel Al-Assad",
@@ -29831,6 +30632,7 @@ AIRBASE.Syria={
["Kuweires"]="Kuweires",
["Lakatamia"]="Lakatamia",
["Larnaca"]="Larnaca",
+["Marka"]="Marka",
["Marj_Ruhayyil"]="Marj Ruhayyil",
["Marj_as_Sultan_North"]="Marj as Sultan North",
["Marj_as_Sultan_South"]="Marj as Sultan South",
@@ -29861,7 +30663,7 @@ AIRBASE.Syria={
["Wujah_Al_Hajar"]="Wujah Al Hajar",
["Ben_Gurion"]="Ben Gurion",
["Hatzor"]="Hatzor",
-["Palmashim"]="Palmashim",
+["Palmachim"]="Palmachim",
["Tel_Nof"]="Tel Nof",
}
AIRBASE.MarianaIslands={
@@ -29874,6 +30676,20 @@ AIRBASE.MarianaIslands={
["Saipan_Intl"]="Saipan Intl",
["Tinian_Intl"]="Tinian Intl",
}
+AIRBASE.MarianaIslandsWWII=
+{
+["Agana"]="Agana",
+["Airfield_3"]="Airfield 3",
+["Charon_Kanoa"]="Charon Kanoa",
+["Gurguan_Point"]="Gurguan Point",
+["Isley"]="Isley",
+["Kagman"]="Kagman",
+["Marpi"]="Marpi",
+["Orote"]="Orote",
+["Pagan"]="Pagan",
+["Rota"]="Rota",
+["Ushi"]="Ushi",
+}
AIRBASE.SouthAtlantic={
["Almirante_Schroeders"]="Almirante Schroeders",
["Comandante_Luis_Piedrabuena"]="Comandante Luis Piedrabuena",
@@ -29919,9 +30735,10 @@ AIRBASE.Sinai={
["Bilbeis_Air_Base"]="Bilbeis Air Base",
["Bir_Hasanah"]="Bir Hasanah",
["Birma_Air_Base"]="Birma Air Base",
-["Borj_El_Arab_International_Airport"]="Borj El Arab International Airport",
+["Borg_El_Arab_International_Airport"]="Borg El Arab International Airport",
["Cairo_International_Airport"]="Cairo International Airport",
["Cairo_West"]="Cairo West",
+["Damascus_Intl"]="Damascus Intl",
["Difarsuwar_Airfield"]="Difarsuwar Airfield",
["El_Arish"]="El Arish",
["El_Gora"]="El Gora",
@@ -29937,15 +30754,19 @@ AIRBASE.Sinai={
["Kibrit_Air_Base"]="Kibrit Air Base",
["Kom_Awshim"]="Kom Awshim",
["Melez"]="Melez",
+["Mezzeh_Air_Base"]="Mezzeh Air Base",
["Nevatim"]="Nevatim",
["Ovda"]="Ovda",
["Palmachim"]="Palmachim",
["Quwaysina"]="Quwaysina",
+["Rafic_Hariri_Intl"]="Rafic Hariri Intl",
+["Ramat_David"]="Ramat David",
["Ramon_Airbase"]="Ramon Airbase",
["Ramon_International_Airport"]="Ramon International Airport",
["Sde_Dov"]="Sde Dov",
["Sharm_El_Sheikh_International_Airport"]="Sharm El Sheikh International Airport",
["St_Catherine"]="St Catherine",
+["Tabuk"]="Tabuk",
["Tel_Nof"]="Tel Nof",
["Wadi_Abu_Rish"]="Wadi Abu Rish",
["Wadi_al_Jandali"]="Wadi al Jandali",
@@ -29970,24 +30791,271 @@ AIRBASE.Kola={
["Vidsel"]="Vidsel",
["Vuojarvi"]="Vuojarvi",
["Andoya"]="Andoya",
-["Alakourtti"]="Alakourtti",
+["Alakurtti"]="Alakurtti",
+["Kittila"]="Kittila",
+["Bardufoss"]="Bardufoss",
+["Alta"]="Alta",
+["Sodankyla"]="Sodankyla",
+["Enontekio"]="Enontekio",
+["Evenes"]="Evenes",
+["Hosio"]="Hosio",
+["Kilpyavr"]="Kilpyavr",
+["Afrikanda"]="Afrikanda",
+["Kalevala"]="Kalevala",
+["Koshka_Yavr"]="Koshka Yavr",
+["Poduzhemye"]="Poduzhemye",
+["Luostari_Pechenga"]="Luostari Pechenga",
}
AIRBASE.Afghanistan={
+["Bagram"]="Bagram",
+["Bamyan"]="Bamyan",
["Bost"]="Bost",
["Camp_Bastion"]="Camp Bastion",
["Camp_Bastion_Heliport"]="Camp Bastion Heliport",
["Chaghcharan"]="Chaghcharan",
["Dwyer"]="Dwyer",
["Farah"]="Farah",
+["Gardez"]="Gardez",
+["Ghazni_Heliport"]="Ghazni Heliport",
["Herat"]="Herat",
+["Jalalabad"]="Jalalabad",
+["Kabul"]="Kabul",
["Kandahar"]="Kandahar",
["Kandahar_Heliport"]="Kandahar Heliport",
+["Khost"]="Khost",
+["Khost_Heliport"]="Khost Heliport",
["Maymana_Zahiraddin_Faryabi"]="Maymana Zahiraddin Faryabi",
["Nimroz"]="Nimroz",
["Qala_i_Naw"]="Qala i Naw",
+["Sharana"]="Sharana",
["Shindand"]="Shindand",
["Shindand_Heliport"]="Shindand Heliport",
["Tarinkot"]="Tarinkot",
+["Urgoon_Heliport"]="Urgoon Heliport",
+}
+AIRBASE.Iraq={
+["Al_Asad_Airbase"]="Al-Asad Airbase",
+["Al_Kut_Airport"]="Al-Kut Airport",
+["Al_Sahra_Airport"]="Al-Sahra Airport",
+["Al_Salam_Airbase"]="Al-Salam Airbase",
+["Al_Taji_Airport"]="Al-Taji Airport",
+["Al_Taquddum_Airport"]="Al-Taquddum Airport",
+["Baghdad_International_Airport"]="Baghdad International Airport",
+["Balad_Airbase"]="Balad Airbase",
+["Bashur_Airport"]="Bashur Airport",
+["Erbil_International_Airport"]="Erbil International Airport",
+["H2_Airbase"]="H-2 Airbase",
+["H3_Main_Airbase"]="H-3 Main Airbase",
+["H3_Northwest_Airbase"]="H-3 Northwest Airbase",
+["H3_Southwest_Airbase"]="H-3 Southwest Airbase",
+["K1_Base"]="K1 Base",
+["Kirkuk_International_Airport"]="Kirkuk International Airport",
+["Mosul_International_Airport"]="Mosul International Airport",
+["Qayyarah_Airfield_West"]="Qayyarah Airfield West",
+["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport",
+}
+AIRBASE.GermanyCW={
+["Airracing_Frankfurt"]="Airracing Frankfurt",
+["Airracing_Koblenz"]="Airracing Koblenz",
+["Airracing_Luebeck"]="Airracing Lubeck",
+["Allstedt"]="Allstedt",
+["Altes_Lager"]="Altes Lager",
+["Bad_Duerkheim"]="Bad Durkheim",
+["Barth"]="Barth",
+["Bienenfarm"]="Bienenfarm",
+["Bindersleben"]="Bindersleben",
+["Bitburg"]="Bitburg",
+["Braunschweig"]="Braunschweig",
+["Bremen"]="Bremen",
+["Briest"]="Briest",
+["Buechel"]="Buchel",
+["Bueckeburg"]="Buckeburg",
+["Celle"]="Celle",
+["Cochstedt"]="Cochstedt",
+["Damgarten"]="Damgarten",
+["Dedelow"]="Dedelow",
+["Dessau"]="Dessau",
+["Fassberg"]="Fassberg",
+["Finow"]="Finow",
+["Frankfurt"]="Frankfurt",
+["Fritzlar"]="Fritzlar",
+["Fulda"]="Fulda",
+["Gardelegen"]="Gardelegen",
+["Garz"]="Garz",
+["Gatow"]="Gatow",
+["Gelnhausen"]="Gelnhausen",
+["Giebelstadt"]="Giebelstadt",
+["Glindbruchkippe"]="Glindbruchkippe ",
+["Gross_Mohrdorf"]="Gross Mohrdorf",
+["Grosse_Wiese"]="Grosse Wiese",
+["Guetersloh"]="Gutersloh",
+["H_FRG_01"]="H FRG 01",
+["H_FRG_02"]="H FRG 02",
+["H_FRG_03"]="H FRG 03",
+["H_FRG_04"]="H FRG 04",
+["H_FRG_05"]="H FRG 05",
+["H_FRG_06"]="H FRG 06",
+["H_FRG_07"]="H FRG 07",
+["H_FRG_08"]="H FRG 08",
+["H_FRG_09"]="H FRG 09",
+["H_FRG_10"]="H FRG 10",
+["H_FRG_11"]="H FRG 11",
+["H_FRG_12"]="H FRG 12",
+["H_FRG_13"]="H FRG 13",
+["H_FRG_14"]="H FRG 14",
+["H_FRG_15"]="H FRG 15",
+["H_FRG_16"]="H FRG 16",
+["H_FRG_17"]="H FRG 17",
+["H_FRG_18"]="H FRG 18",
+["H_FRG_19"]="H FRG 19",
+["H_FRG_20"]="H FRG 20",
+["H_FRG_21"]="H FRG 21",
+["H_FRG_23"]="H FRG 23",
+["H_FRG_25"]="H FRG 25",
+["H_FRG_27"]="H FRG 27",
+["H_FRG_30"]="H FRG 30",
+["H_FRG_31"]="H FRG 31",
+["H_FRG_32"]="H FRG 32",
+["H_FRG_34"]="H FRG 34",
+["H_FRG_38"]="H FRG 38",
+["H_FRG_39"]="H FRG 39",
+["H_FRG_40"]="H FRG 40",
+["H_FRG_41"]="H FRG 41",
+["H_FRG_42"]="H FRG 42",
+["H_FRG_43"]="H FRG 43",
+["H_FRG_44"]="H FRG 44",
+["H_FRG_45"]="H FRG 45",
+["H_FRG_46"]="H FRG 46",
+["H_FRG_47"]="H FRG 47",
+["H_FRG_48"]="H FRG 48",
+["H_FRG_49"]="H FRG 49",
+["H_FRG_50"]="H FRG 50",
+["H_FRG_51"]="H FRG 51",
+["H_GDR_01"]="H GDR 01",
+["H_GDR_02"]="H GDR 02",
+["H_GDR_03"]="H GDR 03",
+["H_GDR_04"]="H GDR 04",
+["H_GDR_05"]="H GDR 05",
+["H_GDR_06"]="H GDR 06",
+["H_GDR_07"]="H GDR 07",
+["H_GDR_08"]="H GDR 08",
+["H_GDR_09"]="H GDR 09",
+["H_GDR_10"]="H GDR 10",
+["H_GDR_11"]="H GDR 11",
+["H_GDR_12"]="H GDR 12",
+["H_GDR_13"]="H GDR 13",
+["H_GDR_14"]="H GDR 14",
+["H_GDR_15"]="H GDR 15",
+["H_GDR_16"]="H GDR 16",
+["H_GDR_17"]="H GDR 17",
+["H_GDR_18"]="H GDR 18",
+["H_GDR_19"]="H GDR 19",
+["H_GDR_21"]="H GDR 21",
+["H_GDR_22"]="H GDR 22",
+["H_GDR_24"]="H GDR 24",
+["H_GDR_25"]="H GDR 25",
+["H_GDR_26"]="H GDR 26",
+["H_GDR_30"]="H GDR 30",
+["H_GDR_31"]="H GDR 31",
+["H_GDR_32"]="H GDR 32",
+["H_GDR_33"]="H GDR 33",
+["H_GDR_34"]="H GDR 34",
+["H_Med_FRG_01"]="H Med FRG 01",
+["H_Med_FRG_02"]="H Med FRG 02",
+["H_Med_FRG_04"]="H Med FRG 04",
+["H_Med_FRG_06"]="H Med FRG 06",
+["H_Med_FRG_11"]="H Med FRG 11",
+["H_Med_FRG_12"]="H Med FRG 12",
+["H_Med_FRG_13"]="H Med FRG 13",
+["H_Med_FRG_14"]="H Med FRG 14",
+["H_Med_FRG_15"]="H Med FRG 15",
+["H_Med_FRG_16"]="H Med FRG 16",
+["H_Med_FRG_17"]="H Med FRG 17",
+["H_Med_FRG_21"]="H Med FRG 21",
+["H_Med_FRG_24"]="H Med FRG 24",
+["H_Med_FRG_26"]="H Med FRG 26",
+["H_Med_FRG_27"]="H Med FRG 27",
+["H_Med_FRG_29"]="H Med FRG 29",
+["H_Med_GDR_01"]="H Med GDR 01",
+["H_Med_GDR_02"]="H Med GDR 02",
+["H_Med_GDR_03"]="H Med GDR 03",
+["H_Med_GDR_08"]="H Med GDR 08",
+["H_Med_GDR_09"]="H Med GDR 09",
+["H_Med_GDR_10"]="H Med GDR 10",
+["H_Med_GDR_11"]="H Med GDR 11",
+["H_Med_GDR_12"]="H Med GDR 12",
+["H_Med_GDR_13"]="H Med GDR 13",
+["H_Med_GDR_14"]="H Med GDR 14",
+["H_Med_GDR_16"]="H Med GDR 16",
+["H_Radar_FRG_02"]="H Radar FRG 02",
+["H_Radar_GDR_01"]="H Radar GDR 01",
+["H_Radar_GDR_02"]="H Radar GDR 02",
+["H_Radar_GDR_03"]="H Radar GDR 03",
+["H_Radar_GDR_04"]="H Radar GDR 04",
+["H_Radar_GDR_05"]="H Radar GDR 05",
+["H_Radar_GDR_06"]="H Radar GDR 06",
+["H_Radar_GDR_07"]="H Radar GDR 07",
+["H_Radar_GDR_08"]="H Radar GDR 08",
+["H_Radar_GDR_09"]="H Radar GDR 09",
+["Hahn"]="Hahn",
+["Haina"]="Haina",
+["Hamburg"]="Hamburg",
+["Hamburg_Finkenwerder"]="Hamburg Finkenwerder",
+["Hannover"]="Hannover",
+["Hasselfelde"]="Hasselfelde",
+["Herrenteich"]="Herrenteich",
+["Hildesheim"]="Hildesheim",
+["Hockenheim"]="Hockenheim",
+["Holzdorf"]="Holzdorf",
+["Kammermark"]="Kammermark",
+["Koethen"]="Kothen",
+["Laage"]="Laage",
+["Langenselbold"]="Langenselbold",
+["Laerz"]="Larz",
+["Leipzig_Halle"]="Leipzig Halle",
+["Leipzig_Mockau"]="Leipzig Mockau",
+["Luebeck"]="Lubeck",
+["Lueneburg"]="Luneburg",
+["Mahlwinkel"]="Mahlwinkel",
+["Mendig"]="Mendig",
+["Merseburg"]="Merseburg",
+["Neubrandenburg"]="Neubrandenburg",
+["Neuruppin"]="Neuruppin",
+["Northeim"]="Northeim",
+["Ober_Moerlen"]="Ober-Morlen",
+["Obermehler_Schlotheim"]="Obermehler Schlotheim",
+["Parchim"]="Parchim",
+["Peenemuende"]="Peenemunde",
+["Pferdsfeld"]="Pferdsfeld",
+["Pinnow"]="Pinnow",
+["Pottschutthoehe"]="Pottschutthohe",
+["Ramstein"]="Ramstein",
+["Rinteln"]="Rinteln",
+["Schoenefeld"]="Schonefeld",
+["Schweinfurt"]="Schweinfurt",
+["Sembach"]="Sembach",
+["Spangdahlem"]="Spangdahlem",
+["Sperenberg"]="Sperenberg",
+["Stendal"]="Stendal",
+["Tegel"]="Tegel",
+["Tempelhof"]="Tempelhof",
+["Templin"]="Templin",
+["Tutow"]="Tutow",
+["Uelzen"]="Uelzen",
+["Uetersen"]="Uetersen",
+["Ummern"]="Ummern",
+["Verden_Scharnhorst"]="Verden-Scharnhorst",
+["Walldorf"]="Walldorf",
+["Waren_Vielist"]="Waren Vielist",
+["Werneuchen"]="Werneuchen",
+["Weser_Wuemme"]="Weser Wumme",
+["Wiesbaden"]="Wiesbaden",
+["Wismar"]="Wismar",
+["Wittstock"]="Wittstock",
+["Worms"]="Worms",
+["Wunstorf"]="Wunstorf",
+["Zerbst"]="Zerbst",
+["Zweibruecken"]="Zweibrucken",
}
AIRBASE.TerminalType={
Runway=16,
@@ -29999,6 +31067,7 @@ OpenBig=104,
OpenMedOrBig=176,
HelicopterUsable=216,
FighterAircraft=244,
+FighterAircraftSmall=344,
}
AIRBASE.SpotStatus={
FREE="Free",
@@ -30013,8 +31082,9 @@ self.descriptors=self:GetDesc()
self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME
if self.category==Airbase.Category.AIRDROME then
self.isAirdrome=true
-elseif self.category==Airbase.Category.HELIPAD then
+elseif self.category==Airbase.Category.HELIPAD or self.descriptors.typeName=="FARP_SINGLE_01"then
self.isHelipad=true
+self.category=Airbase.Category.HELIPAD
elseif self.category==Airbase.Category.SHIP then
self.isShip=true
if self.descriptors.typeName=="Oil rig"or self.descriptors.typeName=="Ga"then
@@ -30027,10 +31097,16 @@ else
self:E("ERROR: Unknown airbase category!")
end
self:_InitRunways()
-if self.isAirdrome then
+local Nrunways=#self.runways
+if Nrunways>0 then
self:SetActiveRunway()
end
self:_InitParkingSpots()
+if self.category==Airbase.Category.AIRDROME and(Nrunways==0 or self.NparkingTotal==self.NparkingTerminal[AIRBASE.TerminalType.HelicopterOnly])then
+self.category=Airbase.Category.HELIPAD
+self.isAirdrome=true
+self.isHelipad=true
+end
local vec2=self:GetVec2()
self:GetCoordinate()
self.storage=_DATABASE:AddStorage(AirbaseName)
@@ -30049,6 +31125,35 @@ end
self:T2(string.format("Registered airbase %s",tostring(self.AirbaseName)))
return self
end
+function AIRBASE:_GetCategory()
+local name=self.AirbaseName
+local static=StaticObject.getByName(name)
+local airbase=Airbase.getByName(name)
+local unit=Unit.getByName(name)
+local text=string.format("\n=====================================================")
+text=text..string.format("\nAirbase %s:",name)
+if static then
+local oc,uc=static:getCategory()
+local ex=static:getCategoryEx()
+text=text..string.format("\nSTATIC: oc=%d, uc=%d, ex=%d",oc,uc,ex)
+text=text..string.format("\n--------------------------------------------------")
+end
+if unit then
+local oc,uc=unit:getCategory()
+local ex=unit:getCategoryEx()
+text=text..string.format("\nUNIT: oc=%d, uc=%d, ex=%d",oc,uc,ex)
+text=text..string.format("\n--------------------------------------------------")
+end
+if airbase then
+local oc,uc=airbase:getCategory()
+local ex=airbase:getCategoryEx()
+text=text..string.format("\nAIRBASE: oc=%d, uc=%d, ex=%d",oc,uc,ex)
+text=text..string.format("\n--------------------------------------------------")
+text=text..UTILS.PrintTableToLog(airbase:getDesc(),nil,true)
+end
+text=text..string.format("\n=====================================================")
+env.info(text)
+end
function AIRBASE:Find(DCSAirbase)
local AirbaseName=DCSAirbase:getName()
local AirbaseFound=_DATABASE:FindAirbase(AirbaseName)
@@ -30302,13 +31407,14 @@ park.ClientSpot,park.ClientName=isClient(park.Coordinate)
park.AirbaseName=self.AirbaseName
self.NparkingTotal=self.NparkingTotal+1
for _,terminalType in pairs(AIRBASE.TerminalType)do
-if self._CheckTerminalType(terminalType,park.TerminalType)then
+if self._CheckTerminalType(park.TerminalType,terminalType)then
self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1
end
end
self.parkingByID[park.TerminalID]=park
table.insert(self.parking,park)
end
+self.NparkingTotal=self.NparkingTotal-self.NparkingTerminal[AIRBASE.TerminalType.Runway]
return self
end
function AIRBASE:_GetParkingSpotByID(TerminalID)
@@ -30553,6 +31659,10 @@ if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.Op
match=true
end
elseif termtype==AIRBASE.TerminalType.FighterAircraft then
+if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter then
+match=true
+end
+elseif termtype==AIRBASE.TerminalType.FighterAircraftSmall then
if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter or Term_Type==AIRBASE.TerminalType.SmallSizeFighter then
match=true
end
@@ -30570,6 +31680,7 @@ if Name then
for _,_runway in pairs(self.runways)do
local runway=_runway
local name=self:GetRunwayName(runway)
+self:T("Check Runway Name: "..name)
if name==Name:upper()then
return runway
end
@@ -30583,11 +31694,8 @@ if IncludeInverse==nil then
IncludeInverse=true
end
local Runways={}
-if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then
-self.runways={}
-return{}
-end
local function _createRunway(name,course,width,length,center)
+self:T("Create Runway: name = "..name)
local bearing=-1*course
local heading=math.deg(bearing)
local runway={}
@@ -30598,6 +31706,7 @@ else
runway.name=string.format("%02d",tonumber(name))
end
runway.magheading=tonumber(runway.name)*10
+runway.idx=runway.magheading
runway.heading=heading
runway.width=width or 0
runway.length=length or 0
@@ -30635,7 +31744,7 @@ local airbase=self:GetDCSObject()
if airbase then
local runways=airbase:getRunways()
self:T2(runways)
-if runways then
+if runways and#runways>0 then
for _,rwy in pairs(runways)do
self:T(rwy)
local runway=_createRunway(rwy.Name,rwy.course,rwy.width,rwy.length,rwy.position)
@@ -30650,6 +31759,9 @@ local runway=_createRunway(name2,rwy.course-math.pi,rwy.width,rwy.length,rwy.pos
table.insert(Runways,runway)
end
end
+else
+self.runways={}
+return{}
end
end
local rpairs={}
@@ -30777,14 +31889,47 @@ local idx=string.format("%02d",UTILS.Round((hdg-magvar)/10,0))
local runway={}
runway.heading=hdg
runway.idx=idx
+runway.magheading=idx
runway.length=c1:Get2DDistance(c2)
runway.position=c1
runway.endpoint=c2
+self:T(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m i=%d j=%d",self:GetName(),runway.idx,runway.heading,runway.length,i,j))
if mark then
runway.position:MarkToAll(string.format("Runway %s: true heading=%03d (magvar=%d), length=%d m, i=%d, j=%d",runway.idx,runway.heading,magvar,runway.length,i,j))
end
table.insert(runways,runway)
end
+local rpairs={}
+for i,_ri in pairs(runways)do
+local ri=_ri
+for j,_rj in pairs(runways)do
+local rj=_rj
+if i0
+end
+for i,j in pairs(rpairs)do
+local ri=runways[i]
+local rj=runways[j]
+local c0=ri.position
+local a=UTILS.VecTranslate(c0,1000,ri.heading)
+local b=UTILS.VecSubstract(rj.position,ri.position)
+b=UTILS.VecAdd(ri.position,b)
+local left=isLeft(c0,a,b)
+if left then
+ri.isLeft=false
+rj.isLeft=true
+else
+ri.isLeft=true
+rj.isLeft=false
+end
+end
return runways
end
function AIRBASE:SetActiveRunway(Name,PreferLeft)
@@ -30921,8 +32066,8 @@ function SCENERY:Register(SceneryName,SceneryObject)
local self=BASE:Inherit(self,POSITIONABLE:New(SceneryName))
self.SceneryName=tostring(SceneryName)
self.SceneryObject=SceneryObject
-if self.SceneryObject then
-self.Life0=self.SceneryObject:getLife()
+if self.SceneryObject and self.SceneryObject.getLife then
+self.Life0=self.SceneryObject:getLife()or 0
else
self.Life0=0
end
@@ -30932,6 +32077,9 @@ end
function SCENERY:GetProperty(PropertyName)
return self.Properties[PropertyName]
end
+function SCENERY:HasProperty(PropertyName)
+return self.Properties[PropertyName]~=nil and true or false
+end
function SCENERY:GetAllProperties()
return self.Properties
end
@@ -30947,7 +32095,7 @@ return self.SceneryObject
end
function SCENERY:GetLife()
local life=0
-if self.SceneryObject then
+if self.SceneryObject and self.SceneryObject.getLife then
life=self.SceneryObject:getLife()
if life>self.Life0 then
self.Life0=math.floor(life*1.2)
@@ -30975,6 +32123,7 @@ end
function SCENERY:GetRelativeLife()
local life=self:GetLife()
local life0=self:GetLife0()
+if life==0 or life0==0 then return 0 end
local rlife=math.floor((life/life0)*100)
return rlife
end
@@ -31417,7 +32566,8 @@ local object=self.weapon:getTarget()
if object then
local category=Object.getCategory(object)
local name=object:getName()
-self:T(self.lid..string.format("Got Target Object %s, category=%d",object:getName(),category))
+if name then
+self:T(self.lid..string.format("Got Target Object %s, category=%d",name,category))
if category==Object.Category.UNIT then
target=UNIT:FindByName(name)
elseif category==Object.Category.STATIC then
@@ -31429,6 +32579,7 @@ self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet
end
end
end
+end
return target
end
function WEAPON:GetTargetDistance(ConversionFunction)
@@ -31733,9 +32884,8 @@ local ucid=self:GetPlayerUCID(nil,name)or"none"
local PlayerID=self:GetPlayerIDByName(name)or"none"
local PlayerSide,PlayerSlot=self:GetSlot(data.IniUnit)
if not PlayerSide then PlayerSide=EventData.IniCoalition end
-if not PlayerSlot then PlayerSlot=EventData.IniUnit:GetID()end
+if not PlayerSlot then PlayerSlot=EventData.IniUnit:GetID()or-1 end
local TNow=timer.getTime()
-self:T(self.lid.."Event for: "..name.." | UCID: "..ucid.." | ID/SIDE/SLOT "..PlayerID.."/"..PlayerSide.."/"..PlayerSlot)
if data.id==EVENTS.PlayerEnterUnit or data.id==EVENTS.PlayerEnterAircraft then
self:T(self.lid.."Pilot Joining: "..name.." | UCID: "..ucid.." | Event ID: "..data.id)
local blocked=self:IsAnyBlocked(ucid,name,PlayerID,PlayerSide,PlayerSlot)
@@ -32119,14 +33269,14 @@ WEAPONS="weapons",
LIQUIDS="liquids",
AIRCRAFT="aircrafts",
}
-STORAGE.version="0.0.3"
+STORAGE.version="0.1.5"
function STORAGE:New(AirbaseName)
local self=BASE:Inherit(self,BASE:New())
self.airbase=Airbase.getByName(AirbaseName)
-if Airbase.getWarehouse then
+if Airbase.getWarehouse and self.airbase then
self.warehouse=self.airbase:getWarehouse()
end
-self.lid=string.format("STORAGE %s",AirbaseName)
+self.lid=string.format("STORAGE %s | ",AirbaseName)
return self
end
function STORAGE:NewFromStaticCargo(StaticCargoName)
@@ -32135,16 +33285,16 @@ self.airbase=StaticObject.getByName(StaticCargoName)
if Airbase.getWarehouse then
self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase)
end
-self.lid=string.format("STORAGE %s",StaticCargoName)
+self.lid=string.format("STORAGE %s | ",StaticCargoName)
return self
end
function STORAGE:NewFromDynamicCargo(DynamicCargoName)
local self=BASE:Inherit(self,BASE:New())
-self.airbase=Unit.getByName(DynamicCargoName)
+self.airbase=Unit.getByName(DynamicCargoName)or StaticObject.getByName(DynamicCargoName)
if Airbase.getWarehouse then
self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase)
end
-self.lid=string.format("STORAGE %s",DynamicCargoName)
+self.lid=string.format("STORAGE %s | ",DynamicCargoName)
return self
end
function STORAGE:FindByName(AirbaseName)
@@ -32153,6 +33303,10 @@ return storage
end
function STORAGE:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
+if self.verbose>1 then
+BASE:TraceOn()
+BASE:TraceClass("STORAGE")
+end
return self
end
function STORAGE:AddItem(Name,Amount)
@@ -32251,7 +33405,7 @@ unlimited=unlimited or n>2^29 or n==N
if not unlimited then
self:AddAmount(Type,1)
end
-self:I(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)",tostring(Type),tostring(unlimited),N,n))
+self:T(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)",tostring(Type),tostring(unlimited),N,n))
end
return unlimited
end
@@ -32287,6 +33441,186 @@ function STORAGE:GetInventory(Item)
local inventory=self.warehouse:getInventory(Item)
return inventory.aircraft,inventory.liquids,inventory.weapon
end
+function STORAGE:SaveToFile(Path,Filename)
+if not io then
+BASE:E("ERROR: io not desanitized. Can't save the files.")
+return false
+end
+if Path==nil and not lfs then
+BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your given path.")
+end
+local ac,lq,wp=self:GetInventory()
+local DataAircraft=""
+local DataLiquids=""
+local DataWeapons=""
+if#lq>0 then
+DataLiquids=DataLiquids.."Liquids in Storage:\n"
+for key,amount in pairs(lq)do
+DataLiquids=DataLiquids..tostring(key).."="..tostring(amount).."\n"
+end
+UTILS.SaveToFile(Path,Filename.."_Liquids.csv",DataLiquids)
+if self.verbose and self.verbose>0 then
+self:I(self.lid.."Saving Liquids to "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv")
+end
+end
+if UTILS.TableLength(ac)>0 then
+DataAircraft=DataAircraft.."Aircraft in Storage:\n"
+for key,amount in pairs(ac)do
+DataAircraft=DataAircraft..tostring(key).."="..tostring(amount).."\n"
+end
+UTILS.SaveToFile(Path,Filename.."_Aircraft.csv",DataAircraft)
+if self.verbose and self.verbose>0 then
+self:I(self.lid.."Saving Aircraft to "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv")
+end
+end
+if UTILS.TableLength(wp)>0 then
+DataWeapons=DataWeapons.."Weapons and Materiel in Storage:\n"
+for _,_category in pairs(ENUMS.Storage.weapons)do
+for _,_key in pairs(_category)do
+local amount=self:GetAmount(_key)
+if type(_key)=="table"then
+_key="{"..table.concat(_key,",").."}"
+end
+DataWeapons=DataWeapons..tostring(_key).."="..tostring(amount).."\n"
+end
+end
+for key,amount in pairs(ENUMS.Storage.weapons.Gazelle)do
+amount=self:GetItemAmount(ENUMS.Storage.weapons.Gazelle[key])
+DataWeapons=DataWeapons.."ENUMS.Storage.weapons.Gazelle."..tostring(key).."="..tostring(amount).."\n"
+end
+for key,amount in pairs(ENUMS.Storage.weapons.CH47)do
+amount=self:GetItemAmount(ENUMS.Storage.weapons.CH47[key])
+DataWeapons=DataWeapons.."ENUMS.Storage.weapons.CH47."..tostring(key).."="..tostring(amount).."\n"
+end
+for key,amount in pairs(ENUMS.Storage.weapons.UH1H)do
+amount=self:GetItemAmount(ENUMS.Storage.weapons.UH1H[key])
+DataWeapons=DataWeapons.."ENUMS.Storage.weapons.UH1H."..tostring(key).."="..tostring(amount).."\n"
+end
+for key,amount in pairs(ENUMS.Storage.weapons.OH58)do
+amount=self:GetItemAmount(ENUMS.Storage.weapons.OH58[key])
+DataWeapons=DataWeapons.."ENUMS.Storage.weapons.OH58."..tostring(key).."="..tostring(amount).."\n"
+end
+for key,amount in pairs(ENUMS.Storage.weapons.AH64D)do
+amount=self:GetItemAmount(ENUMS.Storage.weapons.AH64D[key])
+DataWeapons=DataWeapons.."ENUMS.Storage.weapons.AH64D."..tostring(key).."="..tostring(amount).."\n"
+end
+UTILS.SaveToFile(Path,Filename.."_Weapons.csv",DataWeapons)
+if self.verbose and self.verbose>0 then
+self:I(self.lid.."Saving Weapons to "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv")
+end
+end
+return self
+end
+function STORAGE:LoadFromFile(Path,Filename)
+if not io then
+BASE:E("ERROR: io not desanitized. Can't read the files.")
+return false
+end
+if Path==nil and not lfs then
+BASE:E("WARNING: lfs not desanitized. File will be read from DCS installation root directory rather than your give path.")
+end
+if self:IsLimitedLiquids()then
+local Ok,Liquids=UTILS.LoadFromFile(Path,Filename.."_Liquids.csv")
+if Ok then
+if self.verbose and self.verbose>0 then
+self:I(self.lid.."Loading Liquids from "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv")
+end
+for _id,_line in pairs(Liquids)do
+if string.find(_line,"Storage")==nil then
+local tbl=UTILS.Split(_line,"=")
+local lqno=tonumber(tbl[1])
+local lqam=tonumber(tbl[2])
+self:SetLiquid(lqno,lqam)
+end
+end
+else
+self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv")
+end
+end
+if self:IsLimitedAircraft()then
+local Ok,Aircraft=UTILS.LoadFromFile(Path,Filename.."_Aircraft.csv")
+if Ok then
+if self.verbose and self.verbose>0 then
+self:I(self.lid.."Loading Aircraft from "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv")
+end
+for _id,_line in pairs(Aircraft)do
+if string.find(_line,"Storage")==nil then
+local tbl=UTILS.Split(_line,"=")
+local acname=tbl[1]
+local acnumber=tonumber(tbl[2])
+self:SetAmount(acname,acnumber)
+end
+end
+else
+self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv")
+end
+end
+if self:IsLimitedWeapons()then
+local Ok,Weapons=UTILS.LoadFromFile(Path,Filename.."_Weapons.csv")
+if Ok then
+if self.verbose and self.verbose>0 then
+self:I(self.lid.."Loading Weapons from "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv")
+end
+for _id,_line in pairs(Weapons)do
+if string.find(_line,"Storage")==nil then
+local tbl=UTILS.Split(_line,"=")
+local wpname=tbl[1]
+local wpnumber=tonumber(tbl[2])
+if string.find(wpname,"{")==1 then
+wpname=string.gsub(wpname,"{","")
+wpname=string.gsub(wpname,"}","")
+local tbl=UTILS.Split(wpname,",")
+local wptbl={}
+for _id,_key in ipairs(tbl)do
+table.insert(wptbl,_id,_key)
+end
+self:SetAmount(wptbl,wpnumber)
+else
+self:SetAmount(wpname,wpnumber)
+end
+end
+end
+else
+self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv")
+end
+end
+return self
+end
+function STORAGE:StartAutoSave(Path,Filename,Interval,LoadOnce)
+if LoadOnce~=false then
+self:LoadFromFile(Path,Filename)
+end
+local interval=Interval or 300
+self.SaverTimer=TIMER:New(STORAGE.SaveToFile,self,Path,Filename)
+self.SaverTimer:Start(interval,interval)
+return self
+end
+function STORAGE:StopAutoSave()
+if self.SaverTimer and self.SaverTimer:IsRunning()then
+self.SaverTimer:Stop()
+self.SaverTimer=nil
+end
+return self
+end
+function STORAGE:FindSyriaHHelipadWarehouse(ZoneName)
+local findzone=ZONE:New(ZoneName)
+local base=world.getAirbases()
+for i=1,#base do
+local info={}
+info.callsign=Airbase.getCallsign(base[i])
+info.id=Airbase.getID(base[i])
+info.point=Airbase.getPoint(base[i])
+info.coordinate=COORDINATE:NewFromVec3(info.point)
+info.DCSObject=base[i]
+if info.callsign=="H"and findzone:IsCoordinateInZone(info.coordinate)then
+info.warehouse=info.DCSObject:getWarehouse()
+info.Storage=STORAGE:New(info.callsign..info.id)
+info.Storage.airbase=info.DCSObject
+info.Storage.warehouse=info.warehouse
+return info.Storage
+end
+end
+end
DYNAMICCARGO={
ClassName="DYNAMICCARGO",
verbose=0,
@@ -32327,7 +33661,7 @@ DYNAMICCARGO.AircraftDimensions={
["ropelength"]=30,
},
}
-DYNAMICCARGO.version="0.0.5"
+DYNAMICCARGO.version="0.0.7"
function DYNAMICCARGO:Register(CargoName)
local self=BASE:Inherit(self,POSITIONABLE:New(CargoName))
self.StaticName=CargoName
@@ -32353,7 +33687,7 @@ end
return self
end
function DYNAMICCARGO:GetDCSObject()
-local DCSStatic=Unit.getByName(self.StaticName)
+local DCSStatic=StaticObject.getByName(self.StaticName)or Unit.getByName(self.StaticName)
if DCSStatic then
return DCSStatic
end
@@ -32377,14 +33711,14 @@ return false
end
end
function DYNAMICCARGO:IsUnloaded()
-if self.CargoState and self.CargoState==DYNAMICCARGO.State.REMOVED then
+if self.CargoState and self.CargoState==DYNAMICCARGO.State.UNLOADED then
return true
else
return false
end
end
function DYNAMICCARGO:IsRemoved()
-if self.CargoState and self.CargoState==DYNAMICCARGO.State.UNLOADED then
+if self.CargoState and self.CargoState==DYNAMICCARGO.State.REMOVED then
return true
else
return false
@@ -32449,6 +33783,27 @@ else
return self.StaticName
end
end
+function DYNAMICCARGO:_HeloHovering(Unit,ropelength)
+local DCSUnit=Unit:GetDCSObject()
+local hovering=false
+local Height=0
+if DCSUnit then
+local UnitInAir=DCSUnit:inAir()
+local UnitCategory=DCSUnit:getDesc().category
+if UnitInAir==true and UnitCategory==1 then
+local VelocityVec3=DCSUnit:getVelocity()
+local Velocity=UTILS.VecNorm(VelocityVec3)
+local Coordinate=DCSUnit:getPoint()
+local LandHeight=land.getHeight({x=Coordinate.x,y=Coordinate.z})
+Height=Coordinate.y-LandHeight
+if Velocity<1 and Height<=ropelength and Height>6 then
+hovering=true
+end
+end
+return hovering,Height
+end
+return false
+end
function DYNAMICCARGO:_GetPossibleHeloNearby(pos,loading)
local set=_DYNAMICCARGO_HELOS:GetAliveSet()
local success=false
@@ -32459,28 +33814,33 @@ local helo=_helo
local name=helo:GetPlayerName()or _DATABASE:_FindPlayerNameByUnitName(helo:GetName())or"None"
self:T(self.lid.." Checking: "..name)
local hpos=helo:GetCoordinate()
-local inair=helo:InAir()
-self:T(self.lid.." InAir: AGL/InAir: "..hpos.y-hpos:GetLandHeight().."/"..tostring(inair))
local typename=helo:GetTypeName()
-if hpos and typename and inair==false then
local dimensions=DYNAMICCARGO.AircraftDimensions[typename]
-if dimensions then
+local hovering,height=self:_HeloHovering(helo,dimensions.ropelength)
+local helolanded=not helo:InAir()
+self:T(self.lid.." InAir: AGL/Hovering: "..hpos.y-hpos:GetLandHeight().."/"..tostring(hovering))
+if hpos and typename and dimensions then
local delta2D=hpos:Get2DDistance(pos)
local delta3D=hpos:Get3DDistance(pos)
if self.testing then
self:T(string.format("Cargo relative position: 2D %dm | 3D %dm",delta2D,delta3D))
self:T(string.format("Helo dimension: length %dm | width %dm | rope %dm",dimensions.length,dimensions.width,dimensions.ropelength))
+self:T(string.format("Helo hovering: %s at %dm",tostring(hovering),height))
end
-if loading~=true and delta2D>dimensions.length or delta2D>dimensions.width or delta3D>dimensions.ropelength then
+if loading~=true and(delta2D>dimensions.length or delta2D>dimensions.width)and helolanded then
success=true
Helo=helo
Playername=name
end
-if loading==true and delta2Ddimensions.ropelength then
success=true
Helo=helo
Playername=name
end
+if loading==true and((delta2D0.5 then
-if self.CargoState==DYNAMICCARGO.State.NEW then
+if self.CargoState==DYNAMICCARGO.State.NEW or self.CargoState==DYNAMICCARGO.State.UNLOADED then
local isloaded,client,playername=self:_GetPossibleHeloNearby(pos,true)
self:T(self.lid.." moved! NEW -> LOADED by "..tostring(playername))
self.CargoState=DYNAMICCARGO.State.LOADED
self.Owner=playername
_DATABASE:CreateEventDynamicCargoLoaded(self)
+end
elseif self.CargoState==DYNAMICCARGO.State.LOADED then
local count=_DYNAMICCARGO_HELOS:CountAlive()
local landheight=pos:GetLandHeight()
@@ -32510,26 +33871,18 @@ self:T(self.lid.." AGL: "..agl or-1)
local isunloaded=true
local client
local playername=self.Owner
-if count>0 and(agl>0 or self.testing)then
+if count>0 then
self:T(self.lid.." Possible alive helos: "..count or-1)
-if agl~=0 or self.testing then
isunloaded,client,playername=self:_GetPossibleHeloNearby(pos,false)
-end
if isunloaded then
self:T(self.lid.." moved! LOADED -> UNLOADED by "..tostring(playername))
self.CargoState=DYNAMICCARGO.State.UNLOADED
self.Owner=playername
_DATABASE:CreateEventDynamicCargoUnloaded(self)
end
-elseif count>0 and agl==0 then
-self:T(self.lid.." moved! LOADED -> UNLOADED by "..tostring(playername))
-self.CargoState=DYNAMICCARGO.State.UNLOADED
-self.Owner=playername
-_DATABASE:CreateEventDynamicCargoUnloaded(self)
end
end
self.LastPosition=pos
-end
else
if self.timer and self.timer:IsRunning()then self.timer:Stop()end
self:T(self.lid.." dead! "..self.CargoState.."-> REMOVED")
@@ -34260,6 +35613,7 @@ local TargetType=nil
local TargetUnitCoalition=nil
local TargetUnitCategory=nil
local TargetUnitType=nil
+local TargetIsScenery=false
if Event.IniDCSUnit then
InitUnit=Event.IniDCSUnit
InitUNIT=Event.IniUnit
@@ -34285,6 +35639,10 @@ TargetPlayerName=Event.TgtPlayerName
TargetCoalition=Event.TgtCoalition
TargetCategory=Event.TgtCategory
TargetType=Event.TgtTypeName
+if(not TargetCategory)and TargetUNIT~=nil and TargetUnit:IsInstanceOf("SCENERY")then
+TargetCategory=Unit.Category.STRUCTURE
+TargetIsScenery=true
+end
TargetUnitCoalition=_SCORINGCoalition[TargetCoalition]
TargetUnitCategory=_SCORINGCategory[TargetCategory]
TargetUnitType=TargetType
@@ -34354,17 +35712,22 @@ MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit en
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
-else
+elseif TargetIsScenery~=true then
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit enemy target "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.ScoreHit.." times. "..
"Score: "..PlayerHit.Score..". Score Total:"..Player.Score-Player.Penalty,
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
+elseif TargetIsScenery==true then
+MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit scenery object.".." Score: "..PlayerHit.Score..". Score Total:"..Player.Score-Player.Penalty,
+MESSAGE.Type.Update)
+:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
+:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
end
self:ScoreCSV(InitPlayerName,TargetPlayerName,"HIT_SCORE",1,1,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
end
else
-MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit scenery object.",
+MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit nothing special.",
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
@@ -35125,14 +36488,14 @@ self:T({"CleanUp: Add to CleanUpList: ",CleanUpGroup:GetName(),CleanUpUnitName})
end
function CLEANUP_AIRBASE.__:EventAddForCleanUp(Event)
self:F({Event})
-if Event.IniDCSUnit and Event.IniCategory==Object.Category.UNIT then
+if Event.IniDCSUnit and Event.IniUnit and Event.IniCategory==Object.Category.UNIT then
if self.CleanUpList[Event.IniDCSUnitName]==nil then
if self:IsInAirbase(Event.IniUnit:GetVec2())then
self:AddForCleanUp(Event.IniUnit,Event.IniDCSUnitName)
end
end
end
-if Event.TgtDCSUnit and Event.TgtCategory==Object.Category.UNIT then
+if Event.TgtDCSUnit and Event.TgtUnit and Event.TgtCategory==Object.Category.UNIT then
if self.CleanUpList[Event.TgtDCSUnitName]==nil then
if self:IsInAirbase(Event.TgtUnit:GetVec2())then
self:AddForCleanUp(Event.TgtUnit,Event.TgtDCSUnitName)
@@ -35146,7 +36509,7 @@ for CleanUpUnitName,CleanUpListData in pairs(self.CleanUpList)do
CleanUpCount=CleanUpCount+1
local CleanUpUnit=CleanUpListData.CleanUpUnit
local CleanUpGroupName=CleanUpListData.CleanUpGroupName
-if CleanUpUnit:IsAlive()~=nil then
+if CleanUpUnit and CleanUpUnit:IsAlive()~=nil then
if self:IsInAirbase(CleanUpUnit:GetVec2())then
if _DATABASE:GetStatusGroup(CleanUpGroupName)~="ReSpawn"then
local CleanUpCoordinate=CleanUpUnit:GetCoordinate()
@@ -35166,7 +36529,7 @@ self:DestroyUnit(CleanUpUnit)
end
end
end
-if CleanUpUnit and not CleanUpUnit:GetPlayerName()then
+if CleanUpUnit and(CleanUpUnit.GetPlayerName==nil or not CleanUpUnit:GetPlayerName())then
local CleanUpUnitVelocity=CleanUpUnit:GetVelocityKMH()
if CleanUpUnitVelocity<1 then
if CleanUpListData.CleanUpMoved then
@@ -35297,6 +36660,7 @@ SEAD.Harms={
["AGM_122"]="AGM_122",
["AGM_84"]="AGM_84",
["AGM_45"]="AGM_45",
+["AGM_65"]="AGM_65",
["ALARM"]="ALARM",
["LD-10"]="LD-10",
["X_58"]="X_58",
@@ -35312,6 +36676,7 @@ SEAD.Harms={
SEAD.HarmData={
["AGM_88"]={150,3},
["AGM_45"]={12,2},
+["AGM_65"]={16,0.9},
["AGM_122"]={16.5,2.3},
["AGM_84"]={280,0.8},
["ALARM"]={45,2},
@@ -35347,7 +36712,7 @@ self:HandleEvent(EVENTS.Shot,self.HandleEventShot)
self:SetStartState("Running")
self:AddTransition("*","ManageEvasion","*")
self:AddTransition("*","CalculateHitZone","*")
-self:I("*** SEAD - Started Version 0.4.8")
+self:I("*** SEAD - Started Version 0.4.9")
return self
end
function SEAD:UpdateSet(SEADGroupPrefixes)
@@ -35585,7 +36950,7 @@ local _target=EventData.Weapon:getTarget()
if not _target or self.debug then
self:E("***** SEAD - No target data for "..(SEADWeaponName or"None"))
if string.find(SEADWeaponName,"AGM_88",1,true)or string.find(SEADWeaponName,"AGM_154",1,true)then
-self:I("**** Tracking AGM-88/154 with no target data.")
+self:T("**** Tracking AGM-88/154 with no target data.")
local pos0=SEADPlane:GetCoordinate()
local fheight=SEADPlane:GetHeight()
self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName)
@@ -36266,7 +37631,6 @@ end
function ESCORT:_ReportTargetsScheduler()
self:F(self.EscortGroup:GetName())
if self.EscortGroup:IsAlive()and self.EscortClient:IsAlive()then
-if true then
local EscortGroupName=self.EscortGroup:GetName()
self.EscortMenuAttackNearbyTargets:RemoveSubMenus()
if self.EscortMenuTargetAssistance then
@@ -36317,8 +37681,6 @@ else
self.EscortGroup:MessageToClient("No targets detected.",10,self.EscortClient)
end
return true
-else
-end
end
return false
end
@@ -36935,7 +38297,7 @@ return true
end
ATC_GROUND_UNIVERSAL={
ClassName="ATC_GROUND_UNIVERSAL",
-Version="0.0.1",
+Version="0.0.2",
SetClient=nil,
Airbases=nil,
AirbaseList=nil,
@@ -36945,14 +38307,21 @@ function ATC_GROUND_UNIVERSAL:New(AirbaseList)
local self=BASE:Inherit(self,BASE:New())
self:T({self.ClassName})
self.Airbases={}
-for _name,_ in pairs(_DATABASE.AIRBASES)do
-self.Airbases[_name]={}
-end
self.AirbaseList=AirbaseList
if not self.AirbaseList then
self.AirbaseList={}
-for _name,_ in pairs(_DATABASE.AIRBASES)do
+for _name,_base in pairs(_DATABASE.AIRBASES)do
+if _base and _base.isAirdrome==true then
self.AirbaseList[_name]=_name
+self.Airbases[_name]={}
+end
+end
+else
+for _,_name in pairs(AirbaseList)do
+local airbase=_DATABASE:FindAirbase(_name)
+if airbase and(airbase.isAirdrome==true)then
+self.Airbases[_name]={}
+end
end
end
self.SetClient=SET_CLIENT:New():FilterCategories("plane"):FilterStart()
@@ -41495,11 +42864,16 @@ local status=ratcraft.status
local active=ratcraft.active
local Nunits=ratcraft.nunits
local N0units=group:GetInitialSize()
-local Pnow=coords
-local Dtravel=Pnow:Get2DDistance(ratcraft.Pnow)
-ratcraft.Pnow=Pnow
+local Dtravel=0
+if coords and ratcraft.Pnow then
+local Dtravel=coords:Get2DDistance(ratcraft.Pnow)
+ratcraft.Pnow=coords
+end
ratcraft.Distance=ratcraft.Distance+Dtravel
-local Ddestination=Pnow:Get2DDistance(ratcraft.destination:GetCoordinate())
+local Ddestination=-1
+if ratcraft.Pnow then
+Ddestination=ratcraft.Pnow:Get2DDistance(ratcraft.destination:GetCoordinate())
+end
if(forID and spawnindex==forID)or(not forID)then
local text=string.format("ID %i of flight %s",spawnindex,prefix)
if N0units>1 then
@@ -42885,6 +44259,8 @@ targetsheet=nil,
targetpath=nil,
targetprefix=nil,
Coalition=nil,
+ceilingaltitude=20000,
+ceilingenabled=false,
}
RANGE.Defaults={
goodhitrange=25,
@@ -42954,7 +44330,7 @@ IRExitRange={filename="IR-ExitRange.ogg",duration=3.10},
RANGE.Names={}
RANGE.MenuF10={}
RANGE.MenuF10Root=nil
-RANGE.version="2.8.0"
+RANGE.version="2.8.1"
function RANGE:New(RangeName,Coalition)
local self=BASE:Inherit(self,FSM:New())
self.rangename=RangeName or"Practice Range"
@@ -43137,6 +44513,26 @@ end
self.rangezone=zone
return self
end
+function RANGE:SetRangeCeiling(altitude)
+self:T(self.lid.."SetRangeCeiling")
+if altitude and type(altitude)=="number"then
+self.ceilingaltitude=altitude
+else
+self:E(self.lid.."Altitude either not provided or is not a number, using default setting (20000).")
+self.ceilingaltitude=20000
+end
+return self
+end
+function RANGE:EnableRangeCeiling(enabled)
+self:T(self.lid.."EnableRangeCeiling")
+if enabled and type(enabled)=="boolean"then
+self.ceilingenabled=enabled
+else
+self:E(self.lid.."Enabled either not provide or is not a boolean, using default setting (false).")
+self.ceilingenabled=false
+end
+return self
+end
function RANGE:SetBombTargetSmokeColor(colorid)
self.BombSmokeColor=colorid or SMOKECOLOR.Red
return self
@@ -43581,7 +44977,7 @@ local _unitID=_unit:GetID()
local target=EventData.TgtUnit
local targetname=EventData.TgtUnitName
local _currentTarget=self.strafeStatus[_unitID]
-if _currentTarget and target:IsAlive()then
+if _currentTarget and target and target:IsAlive()then
local playerPos=_unit:GetCoordinate()
local targetPos=target:GetCoordinate()
for _,_target in pairs(_currentTarget.zone.targets)do
@@ -43632,10 +45028,10 @@ local _unit=playerData.unit
local impactcoord=weapon:GetImpactCoordinate()
local insidezone=self.rangezone:IsCoordinateInZone(impactcoord)
if playerData and playerData.smokebombimpact and insidezone then
-if playerData and playerData.delaysmoke then
-timer.scheduleFunction(self._DelayedSmoke,{coord=impactcoord,color=playerData.smokecolor},timer.getTime()+self.TdelaySmoke)
+if playerData.delaysmoke then
+impactcoord:Smoke(playerData.smokecolor,30,self.TdelaySmoke)
else
-impactcoord:Smoke(playerData.smokecolor)
+impactcoord:Smoke(playerData.smokecolor,30)
end
end
for _,_bombtarget in pairs(self.bombingTargets)do
@@ -43686,7 +45082,12 @@ result.rangename=self.rangename
result.attackHdg=attackHdg
result.attackVel=attackVel
result.attackAlt=attackAlt
-result.date=os and os.date()or"n/a"
+if os and os.date then
+result.date=os.date()
+else
+self:E(self.lid.."os or os.date() not available")
+result.date="n/a"
+end
table.insert(_results,result)
self:Impact(result,playerData)
elseif insidezone then
@@ -43984,9 +45385,6 @@ end
data=data..string.format("%s,%s,%d,%d,%s,%s,%s,%s",_playername,target,roundsFired,roundsHit,strafeResult,airframe,time,date)
_savefile(filename,data)
end
-function RANGE._DelayedSmoke(_args)
-_args.coord:Smoke(_args.color)
-end
function RANGE:_DisplayMyStrafePitResults(_unitName)
self:F(_unitName)
local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitName)
@@ -44211,7 +45609,7 @@ local ca2g=coord:ToStringA2G(_unit,_settings)
_text=_text..string.format("\n- %s:\n%s @ %s",bombtarget.name or"unknown",ca2g,eltxt)
end
end
-self:_DisplayMessageToGroup(_unit,_text,120,true,true,_multiplayer)
+self:_DisplayMessageToGroup(_unit,_text,150,true,true,_multiplayer)
end
end
function RANGE:_DisplayStrafePits(_unitname)
@@ -44279,7 +45677,9 @@ local playersettings=_playersettings
local unitname=playersettings.unitname
local unit=UNIT:FindByName(unitname)
if unit and unit:IsAlive()then
-if unit:IsInZone(self.rangezone)then
+local unitalt=unit:GetAltitude(false)
+local unitaltinfeet=UTILS.MetersToFeet(unitalt)
+if unit:IsInZone(self.rangezone)and(not self.ceilingenabled or unitaltinfeet1 then
+if self.SpeedMax>3.6 then
self.ismobile=true
else
self.ismobile=false
@@ -45857,7 +47334,7 @@ else
self.Nsmoke=0
self.Nsmoke0=0
end
-local _dbproperties=self:_CheckDB(self.DisplayName)
+local _dbproperties=self:_CheckDB(self.Type)
self:T({dbproperties=_dbproperties})
if _dbproperties~=nil then
for property,value in pairs(_dbproperties)do
@@ -45889,8 +47366,8 @@ text=text..string.format("Artillery attribute = %s\n",tostring(self.IsArtillery)
text=text..string.format("Type = %s\n",self.Type)
text=text..string.format("Display Name = %s\n",self.DisplayName)
text=text..string.format("Number of units = %d\n",self.IniGroupStrength)
-text=text..string.format("Speed max = %d km/h\n",self.SpeedMax)
-text=text..string.format("Speed default = %d km/h\n",self.Speed)
+text=text..string.format("Speed max = %.1f km/h\n",self.SpeedMax)
+text=text..string.format("Speed default = %.1f km/h\n",self.Speed)
text=text..string.format("Is mobile = %s\n",tostring(self.ismobile))
text=text..string.format("Is cargo = %s\n",tostring(self.iscargo))
text=text..string.format("Min range = %.1f km\n",self.minrange/1000)
@@ -46526,7 +48003,7 @@ local Nammo,Nshells,Nrockets,Nmissiles,Narty=self:GetAmmo()
local nfire=Narty
local _type="shots"
if target.weapontype==ARTY.WeaponType.Auto then
-nfire=Narty
+nfire=Nammo
_type="shots"
elseif target.weapontype==ARTY.WeaponType.Cannon then
nfire=Narty
@@ -49487,7 +50964,7 @@ return _nstock
end
end
function WAREHOUSE:GetCoordinate()
-return self.warehouse:GetCoordinate()
+return self.warehouse:GetCoord()
end
function WAREHOUSE:GetVec3()
local vec3=self.warehouse:GetVec3()
@@ -50073,6 +51550,9 @@ self:I(self.lid..text)
self:T({DCSdesc=asset.DCSdesc})
self:T3({Template=asset.template})
end
+function WAREHOUSE:SetValidateAndRepositionGroundUnits(Enabled)
+self.ValidateAndRepositionGroundUnits=Enabled
+end
function WAREHOUSE:onafterNewAsset(From,Event,To,asset,assignment)
self:T(self.lid..string.format("New asset %s id=%d with assignment %s.",tostring(asset.templatename),asset.uid,tostring(assignment)))
end
@@ -50888,6 +52368,9 @@ template.route.points[1].y=coord.z
template.x=coord.x
template.y=coord.z
template.alt=coord.y
+if self.ValidateAndRepositionGroundUnits then
+UTILS.ValidateAndRepositionGroundUnits(template.units)
+end
local group=_DATABASE:Spawn(template)
return group
end
@@ -50927,7 +52410,7 @@ local AirbaseID=self.airbase:GetID()
local AirbaseCategory=self:GetAirbaseCategory()
if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then
else
-if#parking<#template.units and not airstart then
+if parking and#parking<#template.units and not airstart then
local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.",#parking,#template.units)
self:_DebugMessage(text)
return nil
@@ -50954,7 +52437,7 @@ else
coord=parking[i].Coordinate
terminal=parking[i].TerminalID
end
-if self.Debug then
+if self.Debug and terminal then
local text=string.format("Spawnplace unit %s terminal %d.",unit.name,terminal)
coord:MarkToAll(text)
env.info(text)
@@ -51330,7 +52813,7 @@ local CountryNeutral=nil
if gotunits then
for _,_unit in pairs(units)do
local unit=_unit
-local distance=coord:Get2DDistance(unit:GetCoordinate())
+local distance=coord:Get2DDistance(unit:GetCoord())
if unit:IsGround()and unit:IsAlive()and distance<=radius then
local _coalition=unit:GetCoalition()
local _country=unit:GetCountry()
@@ -51994,9 +53477,11 @@ break
else
if self.Debug then
local coord=problem.coord
+if coord then
local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.",problem.name,problem.type,_termid,problem.size,problem.dist)
self:I(self.lid..text)
-coord:MarkToAll(string.format(text))
+coord:MarkToAll(text)
+end
else
self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!",_termid))
end
@@ -52274,7 +53759,6 @@ end
end
end
function WAREHOUSE:_DeleteQueueItem(qitem,queue)
-self:F({qitem=qitem,queue=queue})
for i=1,#queue do
local _item=queue[i]
if _item.uid==qitem.uid then
@@ -53074,7 +54558,7 @@ weapon.tracking=false
end
function FOX:onafterMissileLaunch(From,Event,To,missile)
local text=string.format("FOX: Tracking missile %s(%s) - target %s - shooter %s",missile.missileType,missile.missileName,tostring(missile.targetName),missile.shooterName)
-self:I(FOX.lid..text)
+self:T(FOX.lid..text)
MESSAGE:New(text,10):ToAllIf(self.Debug)
for _,_player in pairs(self.players)do
local player=_player
@@ -53473,6 +54957,7 @@ end
MANTIS={
ClassName="MANTIS",
name="mymantis",
+version="0.9.34",
SAM_Templates_Prefix="",
SAM_Group=nil,
EWR_Templates_Prefix="",
@@ -53484,6 +54969,7 @@ SAM_Table={},
SAM_Table_Long={},
SAM_Table_Medium={},
SAM_Table_Short={},
+SAM_Table_PointDef={},
lid="",
Detection=nil,
AWACS_Detection=nil,
@@ -53517,6 +55003,11 @@ automode=true,
autoshorad=true,
ShoradGroupSet=nil,
checkforfriendlies=false,
+SmokeDecoy=false,
+SmokeDecoyColor=SMOKECOLOR.White,
+checkcounter=1,
+DLinkCacheTime=120,
+logsamstatus=false,
}
MANTIS.AdvancedState={
GREEN=0,
@@ -53527,11 +55018,17 @@ MANTIS.SamType={
SHORT="Short",
MEDIUM="Medium",
LONG="Long",
+POINT="Point",
}
+MANTIS.radiusscale={}
+MANTIS.radiusscale[MANTIS.SamType.LONG]=1.1
+MANTIS.radiusscale[MANTIS.SamType.MEDIUM]=1.2
+MANTIS.radiusscale[MANTIS.SamType.SHORT]=1.75
+MANTIS.radiusscale[MANTIS.SamType.POINT]=3
MANTIS.SamData={
["Hawk"]={Range=35,Blindspot=0,Height=12,Type="Medium",Radar="Hawk"},
["NASAMS"]={Range=14,Blindspot=0,Height=7,Type="Short",Radar="NSAMS"},
-["Patriot"]={Range=99,Blindspot=0,Height=25,Type="Long",Radar="Patriot"},
+["Patriot"]={Range=99,Blindspot=0,Height=25,Type="Long",Radar="Patriot str"},
["Rapier"]={Range=10,Blindspot=0,Height=3,Type="Short",Radar="rapier"},
["SA-2"]={Range=40,Blindspot=7,Height=25,Type="Medium",Radar="S_75M_Volhov"},
["SA-3"]={Range=18,Blindspot=6,Height=18,Type="Short",Radar="5p73 s-125 ln"},
@@ -53539,68 +55036,103 @@ MANTIS.SamData={
["SA-6"]={Range=25,Blindspot=0,Height=8,Type="Medium",Radar="1S91"},
["SA-10"]={Range=119,Blindspot=0,Height=18,Type="Long",Radar="S-300PS 4"},
["SA-11"]={Range=35,Blindspot=0,Height=20,Type="Medium",Radar="SA-11"},
-["Roland"]={Range=5,Blindspot=0,Height=5,Type="Short",Radar="Roland"},
+["Roland"]={Range=6,Blindspot=0,Height=5,Type="Short",Radar="Roland"},
+["Gepard"]={Range=5,Blindspot=0,Height=4,Type="Point",Radar="Gepard"},
["HQ-7"]={Range=12,Blindspot=0,Height=3,Type="Short",Radar="HQ-7"},
-["SA-9"]={Range=4,Blindspot=0,Height=3,Type="Short",Radar="Strela"},
+["SA-9"]={Range=4,Blindspot=0,Height=3,Type="Point",Radar="Strela",Point="true"},
["SA-8"]={Range=10,Blindspot=0,Height=5,Type="Short",Radar="Osa 9A33"},
["SA-19"]={Range=8,Blindspot=0,Height=3,Type="Short",Radar="Tunguska"},
-["SA-15"]={Range=11,Blindspot=0,Height=6,Type="Short",Radar="Tor 9A331"},
-["SA-13"]={Range=5,Blindspot=0,Height=3,Type="Short",Radar="Strela"},
+["SA-15"]={Range=11,Blindspot=0,Height=6,Type="Point",Radar="Tor 9A331",Point="true"},
+["SA-13"]={Range=5,Blindspot=0,Height=3,Type="Point",Radar="Strela",Point="true"},
["Avenger"]={Range=4,Blindspot=0,Height=3,Type="Short",Radar="Avenger"},
["Chaparral"]={Range=8,Blindspot=0,Height=3,Type="Short",Radar="Chaparral"},
-["Linebacker"]={Range=4,Blindspot=0,Height=3,Type="Short",Radar="Linebacker"},
+["Linebacker"]={Range=4,Blindspot=0,Height=3,Type="Point",Radar="Linebacker",Point="true"},
["Silkworm"]={Range=90,Blindspot=1,Height=0.2,Type="Long",Radar="Silkworm"},
+["C-RAM"]={Range=2,Blindspot=0,Height=2,Type="Point",Radar="HEMTT_C-RAM_Phalanx",Point="true"},
["SA-10B"]={Range=75,Blindspot=0,Height=18,Type="Medium",Radar="SA-10B"},
-["SA-17"]={Range=50,Blindspot=3,Height=30,Type="Medium",Radar="SA-17"},
+["SA-17"]={Range=50,Blindspot=3,Height=50,Type="Medium",Radar="SA-17"},
["SA-20A"]={Range=150,Blindspot=5,Height=27,Type="Long",Radar="S-300PMU1"},
["SA-20B"]={Range=200,Blindspot=4,Height=27,Type="Long",Radar="S-300PMU2"},
["HQ-2"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"},
-["SHORAD"]={Range=3,Blindspot=0,Height=3,Type="Short",Radar="Igla"},
["TAMIR IDFA"]={Range=20,Blindspot=0.6,Height=12.3,Type="Short",Radar="IRON_DOME_LN"},
["STUNNER IDFA"]={Range=250,Blindspot=1,Height=45,Type="Long",Radar="DAVID_SLING_LN"},
+["NIKE"]={Range=155,Blindspot=6,Height=30,Type="Long",Radar="HIPAR"},
+["Dog Ear"]={Range=11,Blindspot=0,Height=9,Type="Point",Radar="Dog Ear",Point="true"},
+["Pantsir S1"]={Range=20,Blindspot=1.2,Height=15,Type="Point",Radar="PantsirS1",Point="true"},
+["Tor M2"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2",Point="true"},
+["IRIS-T SLM"]={Range=40,Blindspot=0.5,Height=20,Type="Medium",Radar="CH_IRIST_SLM"},
}
MANTIS.SamDataHDS={
["SA-2 HDS"]={Range=56,Blindspot=7,Height=30,Type="Medium",Radar="V759"},
["SA-3 HDS"]={Range=20,Blindspot=6,Height=30,Type="Short",Radar="V-601P"},
-["SA-10C HDS 2"]={Range=90,Blindspot=5,Height=25,Type="Long",Radar="5P85DE ln"},
-["SA-10C HDS 1"]={Range=90,Blindspot=5,Height=25,Type="Long",Radar="5P85CE ln"},
-["SA-12 HDS 2"]={Range=100,Blindspot=10,Height=25,Type="Long",Radar="S-300V 9A82 l"},
-["SA-12 HDS 1"]={Range=75,Blindspot=1,Height=25,Type="Long",Radar="S-300V 9A83 l"},
+["SA-10B HDS"]={Range=90,Blindspot=5,Height=25,Type="Long",Radar="5P85CE ln"},
+["SA-10C HDS"]={Range=75,Blindspot=5,Height=25,Type="Long",Radar="5P85SE ln"},
+["SA-17 HDS"]={Range=50,Blindspot=3,Height=50,Type="Medium",Radar="SA-17 "},
+["SA-12 HDS 2"]={Range=100,Blindspot=13,Height=30,Type="Long",Radar="S-300V 9A82 l"},
+["SA-12 HDS 1"]={Range=75,Blindspot=6,Height=25,Type="Long",Radar="S-300V 9A83 l"},
["SA-23 HDS 2"]={Range=200,Blindspot=5,Height=37,Type="Long",Radar="S-300VM 9A82ME"},
["SA-23 HDS 1"]={Range=100,Blindspot=1,Height=50,Type="Long",Radar="S-300VM 9A83ME"},
["HQ-2 HDS"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"},
+["SAMPT Block 1 HDS"]={Range=120,Blindspot=1,Height=20,Type="long",Radar="SAMPT_MLT_Blk1"},
+["SAMPT Block 1INT HDS"]={Range=150,Blindspot=1,Height=25,Type="long",Radar="SAMPT_MLT_Blk1NT"},
+["SAMPT Block 2 HDS"]={Range=200,Blindspot=10,Height=70,Type="long",Radar="SAMPT_MLT_Blk2"},
}
MANTIS.SamDataSMA={
-["RBS98M SMA"]={Range=20,Blindspot=0,Height=8,Type="Short",Radar="RBS-98"},
-["RBS70 SMA"]={Range=8,Blindspot=0,Height=5.5,Type="Short",Radar="RBS-70"},
-["RBS70M SMA"]={Range=8,Blindspot=0,Height=5.5,Type="Short",Radar="BV410_RBS70"},
-["RBS90 SMA"]={Range=8,Blindspot=0,Height=5.5,Type="Short",Radar="RBS-90"},
-["RBS90M SMA"]={Range=8,Blindspot=0,Height=5.5,Type="Short",Radar="BV410_RBS90"},
-["RBS103A SMA"]={Range=150,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_Rb103A"},
-["RBS103B SMA"]={Range=35,Blindspot=0,Height=36,Type="Medium",Radar="LvS-103_Lavett103_Rb103B"},
-["RBS103AM SMA"]={Range=150,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_HX_Rb103A"},
-["RBS103BM SMA"]={Range=35,Blindspot=0,Height=36,Type="Medium",Radar="LvS-103_Lavett103_HX_Rb103B"},
-["Lvkv9040M SMA"]={Range=4,Blindspot=0,Height=2.5,Type="Short",Radar="LvKv9040"},
+["RBS98M SMA"]={Range=20,Blindspot=0.2,Height=8,Type="Short",Radar="RBS-98"},
+["RBS70 SMA"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="RBS-70"},
+["RBS70M SMA"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="BV410_RBS70"},
+["RBS90 SMA"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="RBS-90"},
+["RBS90M SMA"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="BV410_RBS90"},
+["RBS103A SMA"]={Range=160,Blindspot=1,Height=36,Type="Long",Radar="LvS-103_Lavett103_Rb103A"},
+["RBS103B SMA"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_Rb103B"},
+["RBS103AM SMA"]={Range=160,Blindspot=1,Height=36,Type="Long",Radar="LvS-103_Lavett103_HX_Rb103A"},
+["RBS103BM SMA"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_HX_Rb103B"},
+["Lvkv9040M SMA"]={Range=2,Blindspot=0.1,Height=1.2,Type="Point",Radar="LvKv9040",Point="true"},
}
MANTIS.SamDataCH={
-["2S38 CH"]={Range=8,Blindspot=0.5,Height=6,Type="Short",Radar="2S38"},
-["PantsirS1 CH"]={Range=20,Blindspot=1.2,Height=15,Type="Short",Radar="PantsirS1"},
-["PantsirS2 CH"]={Range=30,Blindspot=1.2,Height=18,Type="Medium",Radar="PantsirS2"},
-["PGL-625 CH"]={Range=10,Blindspot=0.5,Height=5,Type="Short",Radar="PGL_625"},
-["HQ-17A CH"]={Range=20,Blindspot=1.5,Height=10,Type="Short",Radar="HQ17A"},
-["M903PAC2 CH"]={Range=160,Blindspot=3,Height=24.5,Type="Long",Radar="MIM104_M903_PAC2"},
-["M903PAC3 CH"]={Range=120,Blindspot=1,Height=40,Type="Long",Radar="MIM104_M903_PAC3"},
-["TorM2 CH"]={Range=12,Blindspot=1,Height=10,Type="Short",Radar="TorM2"},
-["TorM2K CH"]={Range=12,Blindspot=1,Height=10,Type="Short",Radar="TorM2K"},
-["TorM2M CH"]={Range=16,Blindspot=1,Height=10,Type="Short",Radar="TorM2M"},
-["NASAMS3-AMRAAMER CH"]={Range=50,Blindspot=2,Height=35.7,Type="Medium",Radar="CH_NASAMS3_LN_AMRAAM_ER"},
-["NASAMS3-AIM9X2 CH"]={Range=20,Blindspot=0.2,Height=18,Type="Short",Radar="CH_NASAMS3_LN_AIM9X2"},
-["C-RAM CH"]={Range=2,Blindspot=0,Height=2,Type="Short",Radar="CH_Centurion_C_RAM"},
-["PGZ-09 CH"]={Range=4,Blindspot=0,Height=3,Type="Short",Radar="CH_PGZ09"},
-["S350-9M100 CH"]={Range=15,Blindspot=1.5,Height=8,Type="Short",Radar="CH_S350_50P6_9M100"},
-["S350-9M96D CH"]={Range=150,Blindspot=2.5,Height=30,Type="Long",Radar="CH_S350_50P6_9M96D"},
-["LAV-AD CH"]={Range=8,Blindspot=0.2,Height=4.8,Type="Short",Radar="CH_LAVAD"},
-["HQ-22 CH"]={Range=170,Blindspot=5,Height=27,Type="Long",Radar="CH_HQ22_LN"},
+["2S38 CHM"]={Range=6,Blindspot=0.1,Height=4.5,Type="Short",Radar="2S38"},
+["PantsirS1 CHM"]={Range=20,Blindspot=1.2,Height=15,Type="Point",Radar="PantsirS1",Point="true"},
+["PantsirS2 CHM"]={Range=30,Blindspot=1.2,Height=18,Type="Medium",Radar="PantsirS2"},
+["PGL-625 CHM"]={Range=10,Blindspot=1,Height=5,Type="Short",Radar="PGL_625"},
+["HQ-17A CHM"]={Range=15,Blindspot=1.5,Height=10,Type="Short",Radar="HQ17A"},
+["M903PAC2 CHM"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="MIM104_M903_PAC2"},
+["M903PAC3 CHM"]={Range=160,Blindspot=1,Height=40,Type="Long",Radar="MIM104_M903_PAC3"},
+["TorM2 CHM"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2",Point="true"},
+["TorM2K CHM"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2K",Point="true"},
+["TorM2M CHM"]={Range=16,Blindspot=1,Height=10,Type="Point",Radar="TorM2M",Point="true"},
+["NASAMS3-AMRAAMER CHM"]={Range=50,Blindspot=2,Height=35.7,Type="Medium",Radar="CH_NASAMS3_LN_AMRAAM_ER"},
+["NASAMS3-AIM9X2 CHM"]={Range=20,Blindspot=0.2,Height=18,Type="Short",Radar="CH_NASAMS3_LN_AIM9X2"},
+["C-RAM CHM"]={Range=2,Blindspot=0,Height=2,Type="Point",Radar="CH_Centurion_C_RAM",Point="true"},
+["PGZ-09 CHM"]={Range=4,Blindspot=0.5,Height=3,Type="Point",Radar="CH_PGZ09",Point="true"},
+["S350-9M100 CHM"]={Range=15,Blindspot=1,Height=8,Type="Short",Radar="CH_S350_50P6_9M100"},
+["S350-9M96D CHM"]={Range=150,Blindspot=2.5,Height=30,Type="Long",Radar="CH_S350_50P6_9M96D"},
+["LAV-AD CHM"]={Range=8,Blindspot=0.16,Height=4.8,Type="Short",Radar="CH_LAVAD"},
+["HQ-22 CHM"]={Range=170,Blindspot=5,Height=27,Type="Long",Radar="CH_HQ22_LN"},
+["PGZ-95 CHM"]={Range=2.5,Blindspot=0.5,Height=2,Type="Point",Radar="CH_PGZ95",Point="true"},
+["LD-3000 CHM"]={Range=2.5,Blindspot=0.1,Height=3,Type="Point",Radar="CH_LD3000_stationary",Point="true"},
+["LD-3000M CHM"]={Range=2.5,Blindspot=0.1,Height=3,Type="Point",Radar="CH_LD3000",Point="true"},
+["FlaRakRad CHM"]={Range=8,Blindspot=1.5,Height=6,Type="Short",Radar="CH_FlaRakRad"},
+["IRIS-T SLM CHM"]={Range=40,Blindspot=0.5,Height=20,Type="Medium",Radar="CH_IRIST_SLM"},
+["M903PAC2KAT1 CHM"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="CH_MIM104_M903_PAC2_KAT1"},
+["Skynex CHM"]={Range=3.5,Blindspot=0.1,Height=3.5,Type="Point",Radar="CH_SkynexHX",Point="true"},
+["Skyshield CHM"]={Range=3.5,Blindspot=0.1,Height=3.5,Type="Point",Radar="CH_Skyshield_Gun",Point="true"},
+["WieselOzelot CHM"]={Range=8,Blindspot=0.16,Height=4.8,Type="Short",Radar="CH_Wiesel2Ozelot"},
+["BukM3-9M317M CHM"]={Range=70,Blindspot=0.25,Height=35,Type="Medium",Radar="CH_BukM3_9A317M"},
+["BukM3-9M317MA CHM"]={Range=70,Blindspot=0.25,Height=35,Type="Medium",Radar="CH_BukM3_9A317MA"},
+["SkySabre CHM"]={Range=30,Blindspot=0.5,Height=10,Type="Medium",Radar="CH_SkySabreLN"},
+["Stormer CHM"]={Range=7.5,Blindspot=0.3,Height=7,Type="Short",Radar="CH_StormerHVM"},
+["THAAD CHM"]={Range=200,Blindspot=40,Height=150,Type="Long",Radar="CH_THAAD_M1120"},
+["USInfantryFIM92K CHM"]={Range=8,Blindspot=0.16,Height=4.8,Type="Short",Radar="CH_USInfantry_FIM92"},
+["RBS98M CHM"]={Range=20,Blindspot=0.2,Height=8,Type="Short",Radar="RBS-98"},
+["RBS70 CHM"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="RBS-70"},
+["RBS70M CHM"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="BV410_RBS70"},
+["RBS90 CHM"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="RBS-90"},
+["RBS90M CHM"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="BV410_RBS90"},
+["RBS103A CHM"]={Range=160,Blindspot=1,Height=36,Type="Long",Radar="LvS-103_Lavett103_Rb103A"},
+["RBS103B CHM"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_Rb103B"},
+["RBS103AM CHM"]={Range=160,Blindspot=1,Height=36,Type="Long",Radar="LvS-103_Lavett103_HX_Rb103A"},
+["RBS103BM CHM"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_HX_Rb103B"},
+["Lvkv9040M CHM"]={Range=2,Blindspot=0.1,Height=1.2,Type="Point",Radar="LvKv9040",Point="true"},
}
do
function MANTIS:New(name,samprefix,ewrprefix,hq,coalition,dynamic,awacs,EmOnOff,Padding,Zones)
@@ -53614,6 +55146,7 @@ self.SAM_Table={}
self.SAM_Table_Long={}
self.SAM_Table_Medium={}
self.SAM_Table_Short={}
+self.SAM_Table_PointDef={}
self.dynamic=dynamic or false
self.checkradius=25000
self.grouping=5000
@@ -53641,10 +55174,6 @@ self.DLink=false
self.Padding=Padding or 10
self.SuppressedGroups={}
self.automode=true
-self.radiusscale={}
-self.radiusscale[MANTIS.SamType.LONG]=1.1
-self.radiusscale[MANTIS.SamType.MEDIUM]=1.2
-self.radiusscale[MANTIS.SamType.SHORT]=1.3
self.usezones=false
self.AcceptZones={}
self.RejectZones={}
@@ -53652,6 +55181,7 @@ self.ConflictZones={}
self.maxlongrange=1
self.maxmidrange=2
self.maxshortrange=2
+self.maxpointdefrange=6
self.maxclassic=6
self.autoshorad=true
self.ShoradGroupSet=SET_GROUP:New()
@@ -53659,6 +55189,8 @@ self.FilterZones=Zones
self.SkateZones=nil
self.SkateNumber=3
self.shootandscoot=false
+self.SmokeDecoy=false
+self.SmokeDecoyColor=SMOKECOLOR.White
self.UseEmOnOff=true
if EmOnOff==false then
self.UseEmOnOff=false
@@ -53668,6 +55200,7 @@ self.advAwacs=true
else
self.advAwacs=false
end
+self:SetDLinkCacheTime()
self.lid=string.format("MANTIS %s | ",self.name)
if self.debug then
BASE:TraceOnOff(true)
@@ -53690,6 +55223,7 @@ end
if self.advAwacs then
table.insert(self.ewr_templates,awacs)
end
+self.logsamstatus=false
self:T({self.ewr_templates})
self.SAM_Group=SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition)
self.EWR_Group=SET_GROUP:New():FilterPrefixes(self.ewr_templates):FilterCoalitions(self.Coalition)
@@ -53706,7 +55240,7 @@ end
if self.HQ_Template_CC then
self.HQ_CC=GROUP:FindByName(self.HQ_Template_CC)
end
-self.version="0.8.18"
+self.checkcounter=1
self:I(string.format("***** Starting MANTIS Version %s *****",self.version))
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
@@ -53751,7 +55285,11 @@ self:T(self.lid.."AddZones")
self.AcceptZones=AcceptZones or{}
self.RejectZones=RejectZones or{}
self.ConflictZones=ConflictZones or{}
-if#AcceptZones>0 or#RejectZones>0 or#ConflictZones>0 then
+self.AcceptZonesNo=UTILS.TableLength(self.AcceptZones)
+self.RejectZonesNo=UTILS.TableLength(self.RejectZones)
+self.ConflictZonesNo=UTILS.TableLength(self.ConflictZones)
+self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo))
+if self.AcceptZonesNo>0 or self.RejectZonesNo>0 or self.ConflictZonesNo>0 then
self.usezones=true
end
return self
@@ -53775,12 +55313,18 @@ end
self.engagerange=range
return self
end
-function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic)
+function MANTIS:SetSmokeDecoy(Onoff,Color)
+self.SmokeDecoy=Onoff
+self.SmokeDecoyColor=Color or SMOKECOLOR.White
+return self
+end
+function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic,Point)
self:T(self.lid.."SetMaxActiveSAMs")
self.maxclassic=Classic or 6
self.maxlongrange=Long or 1
self.maxmidrange=Mid or 2
self.maxshortrange=Short or 2
+self.maxpointdefrange=Point or 6
return self
end
function MANTIS:SetNewSAMRangeWhileRunning(range)
@@ -53845,6 +55389,11 @@ end
end
return self
end
+function MANTIS:SetDLinkCacheTime(seconds)
+self.DLinkCacheTime=math.abs(seconds or 120)
+if self.DLinkCacheTime<5 then self.DLinkCacheTime=5 end
+return self
+end
function MANTIS:SetDetectInterval(interval)
self:T(self.lid.."SetDetectInterval")
local interval=interval or 30
@@ -53922,6 +55471,20 @@ end
end
return self
end
+function MANTIS:_CheckAnyEWRAlive()
+self:T(self.lid.."_CheckAnyEWRAlive")
+local alive=false
+if self.EWR_Group:CountAlive()>0 then
+alive=true
+end
+if not alive and self.AWACS_Prefix then
+local awacs=GROUP:FindByName(self.AWACS_Prefix)
+if awacs and awacs:IsAlive()then
+alive=true
+end
+end
+return alive
+end
function MANTIS:_CalcAdvState()
self:T(self.lid.."CalcAdvState")
local m=MESSAGE:New(self.lid.." Calculating Advanced State",10,"MANTIS"):ToAllIf(self.debug)
@@ -53987,7 +55550,8 @@ end
function MANTIS:_CheckCoordinateInZones(coord)
self:T(self.lid.."_CheckCoordinateInZones")
local inzone=false
-if#self.AcceptZones>0 then
+self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo))
+if self.AcceptZonesNo>0 then
for _,_zone in pairs(self.AcceptZones)do
local zone=_zone
if zone:IsCoordinateInZone(coord)then
@@ -53997,7 +55561,7 @@ break
end
end
end
-if#self.RejectZones>0 and inzone then
+if self.RejectZonesNo>0 then
for _,_zone in pairs(self.RejectZones)do
local zone=_zone
if zone:IsCoordinateInZone(coord)then
@@ -54007,7 +55571,7 @@ break
end
end
end
-if#self.ConflictZones>0 and not inzone then
+if self.ConflictZonesNo>0 then
for _,_zone in pairs(self.ConflictZones)do
local zone=_zone
if zone:IsCoordinateInZone(coord)then
@@ -54043,9 +55607,8 @@ local set=dectset
if dlink then
set=self:_PreFilterHeight(height)
end
-local friendlyset
-if self.checkforfriendlies==true then
-friendlyset=SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp)if grp and grp:InAir()then return true else return false end end):FilterOnce()
+if self.checkforfriendlies==true and self.friendlyset==nil then
+self.friendlyset=SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp)if grp and grp:InAir()then return true else return false end end):FilterStart()
end
for _,_coord in pairs(set)do
local coord=_coord
@@ -54054,23 +55617,24 @@ if not targetdistance then
targetdistance=samcoordinate:Get2DDistance(coord)
end
local zonecheck=true
+self:T("self.usezones = "..tostring(self.usezones))
if self.usezones then
zonecheck=self:_CheckCoordinateInZones(coord)
end
if self.verbose and self.debug then
-local dectstring=coord:ToStringLLDMS()
-local samstring=samcoordinate:ToStringLLDMS()
+local samstring=samcoordinate:ToStringMGRS({MGRS_Accuracy=0})
+samstring=string.gsub(samstring,"%s","")
local inrange="false"
if targetdistance<=rad then
inrange="true"
end
-local text=string.format("Checking SAM at %s | Targetdist %d | Rad %d | Inrange %s",samstring,targetdistance,rad,inrange)
+local text=string.format("Checking SAM at %s | Tgtdist %.1fkm | Rad %.1fkm | Inrange %s",samstring,targetdistance/1000,rad/1000,inrange)
local m=MESSAGE:New(text,10,"Check"):ToAllIf(self.debug)
self:T(self.lid..text)
end
local nofriendlies=true
if self.checkforfriendlies==true then
-local closestfriend,distance=friendlyset:GetClosestGroup(samcoordinate)
+local closestfriend,distance=self.friendlyset:GetClosestGroup(samcoordinate)
if closestfriend and distance and distance65 then
+if self.checkcounter%3==0 then
self:_RefreshSAMTable()
end
+self.checkcounter=self.checkcounter+1
+local instatusred=0
+local instatusgreen=0
+local activeshorads=0
if self.automode then
local samset=self.SAM_Table_Long
-self:_CheckLoop(samset,detset,dlink,self.maxlongrange)
+local instatusredl,instatusgreenl,activeshoradsl=self:_CheckLoop(samset,detset,dlink,self.maxlongrange)
local samset=self.SAM_Table_Medium
-self:_CheckLoop(samset,detset,dlink,self.maxmidrange)
+local instatusredm,instatusgreenm,activeshoradsm=self:_CheckLoop(samset,detset,dlink,self.maxmidrange)
local samset=self.SAM_Table_Short
-self:_CheckLoop(samset,detset,dlink,self.maxshortrange)
+local instatusreds,instatusgreens,activeshoradss=self:_CheckLoop(samset,detset,dlink,self.maxshortrange)
+local samset=self.SAM_Table_PointDef
+instatusred,instatusgreen,activeshorads=self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange)
else
local samset=self:_GetSAMTable()
-self:_CheckLoop(samset,detset,dlink,self.maxclassic)
+instatusred,instatusgreen,activeshorads=self:_CheckLoop(samset,detset,dlink,self.maxclassic)
+end
+local function GetReport()
+local statusreport=REPORT:New("\nMANTIS Status "..self.name)
+statusreport:Add("+-----------------------------+")
+statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred))
+statusreport:Add(string.format("+ SAM in GREEN State: %2d",instatusgreen))
+if self.Shorad then
+statusreport:Add(string.format("+ SHORAD active: %2d",activeshorads))
+end
+statusreport:Add("+-----------------------------+")
+return statusreport
+end
+if self.debug or self.verbose then
+local statusreport=GetReport()
+MESSAGE:New(statusreport:Text(),10):ToAll():ToLog()
+elseif reporttolog==true then
+local statusreport=GetReport()
+MESSAGE:New(statusreport:Text(),10):ToLog()
end
return self
end
@@ -54448,7 +56110,7 @@ else
self.Detection=self:StartIntelDetection()
end
if self.autoshorad then
-self.Shorad=SHORAD:New(self.name.."-SHORAD",self.name.."-SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.coalition,self.UseEmOnOff)
+self.Shorad=SHORAD:New(self.name.."-SHORAD","SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.coalition,self.UseEmOnOff)
self.Shorad:SetDefenseLimits(80,95)
self.ShoradLink=true
self.Shorad.Groupset=self.ShoradGroupSet
@@ -54463,7 +56125,31 @@ end
function MANTIS:onbeforeStatus(From,Event,To)
self:T({From,Event,To})
if not self.state2flag then
-self:_Check(self.Detection,self.DLink)
+self:_Check(self.Detection,self.DLink,self.logsamstatus)
+end
+local EWRAlive=self:_CheckAnyEWRAlive()
+local function FindSAMSRTR()
+for i=1,1000 do
+local randomsam=self.SAM_Group:GetRandom()
+if randomsam and randomsam:IsAlive()then
+if randomsam:IsSAM()then return randomsam end
+end
+end
+end
+if not EWRAlive then
+local randomsam=FindSAMSRTR()
+if randomsam and randomsam:IsAlive()then
+if self.UseEmOnOff then
+randomsam:EnableEmission(true)
+else
+randomsam:OptionAlarmStateRed()
+end
+local name=randomsam:GetName()
+if self.SamStateTracker[name]~="RED"then
+self:__RedState(1,randomsam)
+self.SamStateTracker[name]="RED"
+end
+end
end
if self.autorelocate then
local relointerval=self.relointerval
@@ -54489,7 +56175,7 @@ self:T({From,Event,To})
if self.debug and self.verbose then
self:I(self.lid.."Status Report")
for _name,_state in pairs(self.SamStateTracker)do
-self:I(string.format("Site %s\tStatus %s",_name,_state))
+self:I(string.format("Site %s | Status %s",_name,_state))
end
end
local interval=self.detectinterval*-1
@@ -54527,7 +56213,7 @@ if self.ShoradLink then
local Shorad=self.Shorad
local radius=self.checkradius
local ontime=self.ShoradTime
-Shorad:WakeUpShorad(Name,radius,ontime)
+Shorad:WakeUpShorad(Name,radius,ontime,nil,true)
self:__ShoradActivated(1,Name,radius,ontime)
end
return self
@@ -54563,7 +56249,7 @@ shootandscoot=false,
SkateNumber=3,
SkateZones=nil,
minscootdist=100,
-minscootdist=3000,
+maxscootdist=3000,
scootrandomcoord=false,
}
do
@@ -54779,9 +56465,11 @@ local returnname=false
for _,_groups in pairs(shoradset)do
local groupname=_groups:GetName()
if string.find(groupname,tgtgrp,1,true)then
+if _groups:IsSAM()then
returnname=true
end
end
+end
return returnname
end
function SHORAD:_ShotIsDetected()
@@ -54795,7 +56483,7 @@ IsDetected=true
end
return IsDetected
end
-function SHORAD:onafterWakeUpShorad(From,Event,To,TargetGroup,Radius,ActiveTimer,TargetCat)
+function SHORAD:onafterWakeUpShorad(From,Event,To,TargetGroup,Radius,ActiveTimer,TargetCat,ShotAt)
self:T(self.lid.." WakeUpShorad")
self:T({TargetGroup,Radius,ActiveTimer,TargetCat})
local targetcat=TargetCat or Object.Category.UNIT
@@ -54832,7 +56520,20 @@ end
end
local TDiff=4
for _,_group in pairs(shoradset)do
-if _group:IsAnyInZone(targetzone)then
+local groupname=_group:GetName()
+if groupname==TargetGroup and ShotAt==true then
+if self.UseEmOnOff then
+_group:EnableEmission(false)
+end
+_group:OptionAlarmStateGreen()
+self.ActiveGroups[groupname]=nil
+local text=string.format("Shot at SHORAD %s! Evading!",_group:GetName())
+self:T(text)
+local m=MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
+if self.shootandscoot then
+self:__ShootAndScoot(1,_group)
+end
+elseif _group:IsAnyInZone(targetzone)or groupname==TargetGroup then
local text=string.format("Waking up SHORAD %s",_group:GetName())
self:T(text)
local m=MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
@@ -54840,7 +56541,6 @@ if self.UseEmOnOff then
_group:EnableEmission(true)
end
_group:OptionAlarmStateRed()
-local groupname=_group:GetName()
if self.ActiveGroups[groupname]==nil then
self.ActiveGroups[groupname]={Timing=ActiveTimer}
local endtime=timer.getTime()+(ActiveTimer*math.random(75,100)/100)
@@ -54898,7 +56598,7 @@ _targetgroup=tgtgrp
_targetgroupname=tgtgrp:GetName()
_targetskill=tgtgrp:GetUnit(1):GetSkill()
self:T("*** Found Target = ".._targetgroupname)
-self:WakeUpShorad(_targetgroupname,self.Radius,self.ActiveTimer,Object.Category.UNIT)
+self:WakeUpShorad(_targetgroupname,self.Radius,self.ActiveTimer,Object.Category.UNIT,true)
end
end
end
@@ -55004,7 +56704,7 @@ local shotatus=self:_CheckShotAtShorad(targetgroupname)
local shotatsams=self:_CheckShotAtSams(targetgroupname)
if shotatsams or shotatus then
self:T({shotatsams=shotatsams,shotatus=shotatus})
-self:WakeUpShorad(targetgroupname,self.Radius,self.ActiveTimer,targetcat)
+self:WakeUpShorad(targetgroupname,self.Radius,self.ActiveTimer,targetcat,true)
end
end
end
@@ -55014,7 +56714,7 @@ end
end
AICSAR={
ClassName="AICSAR",
-version="0.1.16",
+version="0.1.18",
lid="",
coalition=coalition.side.BLUE,
template="",
@@ -55059,6 +56759,8 @@ Speed=100,
Altitude=1500,
UseEventEject=false,
Delay=100,
+UseRescueZone=false,
+RescueZone=nil,
}
AICSAR.Messages={
EN={
@@ -55102,7 +56804,7 @@ PILOTRESCUED=3.5,
PILOTINHELO=2.6,
},
}
-function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone)
+function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone,Helonumber)
local self=BASE:Inherit(self,FSM:New())
if Coalition and type(Coalition)=="string"then
if Coalition=="blue"then
@@ -55157,7 +56859,7 @@ self.DCSRadioGroup=nil
self.DCSRadioQueue=nil
self.MGRS_Accuracy=2
self.limithelos=true
-self.helonumber=3
+self.helonumber=Helonumber or 3
self:InitLocalization()
self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown")
self.PilotStore=FIFO:New()
@@ -55199,12 +56901,17 @@ self.gettext:AddEntry("de","PILOTRESCUED",AICSAR.Messages.DE.PILOTRESCUED,AICSAR
self.locale="en"
return self
end
+function AICSAR:SetUsingRescueZone(Zone)
+self.UseRescueZone=true
+self.RescueZone=Zone
+return self
+end
function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port)
self:T(self.lid.."SetSRSRadio")
self.SRSRadio=OnOff and true
self.SRSTTSRadio=false
self.SRSFrequency=Frequency or 243
-self.SRSPath=Path or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
+self.SRSPath=Path or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.SRS:SetLabel("ACSR")
self.SRS:SetCoalition(self.coalition)
self.SRSModulation=Modulation or radio.modulation.AM
@@ -55222,7 +56929,7 @@ self:T(self.lid.."SetSRSTTSRadio")
self.SRSTTSRadio=OnOff and true
self.SRSRadio=false
self.SRSFrequency=Frequency or 243
-self.SRSPath=Path or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
+self.SRSPath=Path or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.SRSModulation=Modulation or radio.modulation.AM
self.SRSPort=Port or MSRS.port or 5002
if OnOff then
@@ -55308,7 +57015,6 @@ if self.UseEventEject then
local _LandingPos=COORDINATE:NewFromVec3(_event.initiator:getPosition().p)
local _country=_event.initiator:getCountry()
local _coalition=coalition.getCountryCoalition(_country)
-local data=UTILS.DeepCopy(EventData)
Unit.destroy(_event.initiator)
self:ScheduleOnce(self.Delay,self._DelayedSpawnPilot,self,_LandingPos,_coalition)
end
@@ -55317,6 +57023,13 @@ return self
end
function AICSAR:_DelayedSpawnPilot(_LandingPos,_coalition)
local distancetofarp=_LandingPos:Get2DDistance(self.farp:GetCoordinate())
+if self.UseRescueZone==true and self.RescueZone~=nil then
+if self.RescueZone:IsCoordinateInZone(_LandingPos)then
+distancetofarp=self.maxdistance-10
+else
+distancetofarp=self.maxdistance+10
+end
+end
local Text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTDOWN",self.locale)
local text=""
local setting={}
@@ -55383,6 +57096,13 @@ local _LandingPos=COORDINATE:NewFromVec3(_event.initiator:getPosition().p)
local _country=_event.initiator:getCountry()
local _coalition=coalition.getCountryCoalition(_country)
local distancetofarp=_LandingPos:Get2DDistance(self.farp:GetCoordinate())
+if self.UseRescueZone==true and self.RescueZone~=nil then
+if self.RescueZone:IsCoordinateInZone(_LandingPos)then
+distancetofarp=self.maxdistance-10
+else
+distancetofarp=self.maxdistance+10
+end
+end
local Text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTDOWN",self.locale)
local text=""
local setting={}
@@ -55444,6 +57164,7 @@ local newhelo=SPAWN:NewWithAlias(self.helotemplate,self.helotemplate..math.rando
:InitUnControlled(true)
:OnSpawnGroup(
function(Group)
+Group:OptionPreferVerticalLanding()
self:__HeloOnDuty(1,Group)
end
)
@@ -55482,6 +57203,9 @@ function helo:OnAfterUnloaded(From,Event,To,OpsGroupCargo)
AICHeloUnloaded(helo,OpsGroupCargo)
helo:__UnloadingDone(5)
end
+function helo:OnAfterLandAtAirbase(From,Event,To,airbase)
+helo:Despawn(2)
+end
self.helos[Index]=helo
return self
end
@@ -55512,7 +57236,8 @@ local state=helo:GetState()
local name=helo:GetName()
self:T("Helo group "..name.." in state "..state)
if state=="Arrived"then
-helo:__Stop(5)
+helo.OnAfterDead=nil
+helo:Despawn(35)
self.helos[_index]=nil
end
else
@@ -55541,7 +57266,6 @@ local flightgroup=self.helos[_index]
if self:_CheckInMashZone(_pilot)then
self:T("Pilot".._pilot.GroupName.." rescued!")
if OpsGroup then
-OpsGroup:Despawn(10)
else
_pilot:Destroy(true,10)
end
@@ -56228,8 +57952,11 @@ verbose=0,
alias="",
debug=false,
smokemenu=true,
+RoundingPrecision=0,
+increasegroundawareness=false,
+MonitorFrequency=30,
}
-AUTOLASE.version="0.1.25"
+AUTOLASE.version="0.1.31"
function AUTOLASE:New(RecceSet,Coalition,Alias,PilotSet)
BASE:T({RecceSet,Coalition,Alias,PilotSet})
local self=BASE:Inherit(self,BASE:New())
@@ -56282,6 +58009,7 @@ self.reporttimeshort=10
self.reporttimelong=30
self.smoketargets=false
self.smokecolor=SMOKECOLOR.Red
+self.smokeoffset=nil
self.notifypilots=true
self.targetsperrecce={}
self.RecceUnits={}
@@ -56298,6 +58026,10 @@ self:SetLaserCodes({1688,1130,4785,6547,1465,4578})
self.playermenus={}
self.smokemenu=true
self.threatmenu=true
+self.RoundingPrecision=0
+self.increasegroundawareness=false
+self.MonitorFrequency=30
+self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)})
self.lid=string.format("AUTOLASE %s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown")
self:AddTransition("*","Monitor","*")
self:AddTransition("*","Lasing","*")
@@ -56316,10 +58048,22 @@ self:__Start(2)
self:__Monitor(math.random(5,10))
return self
end
+function AUTOLASE:SetMonitorFrequency(Seconds)
+self.MonitorFrequency=Seconds or 30
+return self
+end
function AUTOLASE:SetLaserCodes(LaserCodes)
self.LaserCodes=(type(LaserCodes)=="table")and LaserCodes or{LaserCodes}
return self
end
+function AUTOLASE:EnableImproveGroundUnitsDetection()
+self.increasegroundawareness=true
+return self
+end
+function AUTOLASE:DisableImproveGroundUnitsDetection()
+self.increasegroundawareness=false
+return self
+end
function AUTOLASE:SetPilotMenu()
if self.usepilotset then
local pilottable=self.pilotset:GetSetObjects()or{}
@@ -56415,7 +58159,7 @@ end
function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation,Label,Gender,Culture,Port,Voice,Volume,PathToGoogleKey)
if OnOff then
self.useSRS=true
-self.SRSPath=Path or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
+self.SRSPath=Path or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.SRSFreq=Frequency or 271
self.SRSMod=Modulation or radio.modulation.AM
self.Gender=Gender or MSRS.gender or"male"
@@ -56496,12 +58240,22 @@ local Message="Smoking targets is now "..smktxt.."!"
self:NotifyPilots(Message,10)
return self
end
-function AUTOLASE:EnableSmokeMenu()
+function AUTOLASE:SetRoundingPrecsion(IDP)
+self.RoundingPrecision=IDP or 0
+return self
+end
+function AUTOLASE:EnableSmokeMenu(Offset)
self.smokemenu=true
+if Offset then
+self.smokeoffset={}
+self.smokeoffset.Distance=Offset.Distance or math.random(10,20)
+self.smokeoffset.Angle=Offset.Angle or math.random(0,359)
+end
return self
end
function AUTOLASE:DisableSmokeMenu()
self.smokemenu=false
+self.smokeoffset=nil
return self
end
function AUTOLASE:EnableThreatLevelMenu()
@@ -56541,7 +58295,8 @@ if recce and recce:IsAlive()then
local unit=recce:GetUnit(1)
local name=unit:GetName()
if not self.RecceUnits[name]then
-self.RecceUnits[name]={name=name,unit=unit,cooldown=false,timestamp=timer.getAbsTime()}
+local isground=(unit and unit.IsGround)and unit:IsGround()or false
+self.RecceUnits[name]={name=name,unit=unit,cooldown=false,timestamp=timer.getAbsTime(),isground=isground}
end
end
end
@@ -56614,9 +58369,11 @@ name=string.match(name,"^(.*)#")
end
local code=self:GetLaserCode(unit:GetName())
report:Add(string.format("Recce %s has code %d",name,code))
+report:Add("---------------")
end
end
report:Add(string.format("Lasing min threat level %d",self.minthreatlevel))
+report:Add("---------------")
local lines=0
for _ind,_entry in pairs(self.CurrentLasing)do
local entry=_entry
@@ -56636,22 +58393,27 @@ end
if playername then
local settings=_DATABASE:GetPlayerSettings(playername)
if settings then
-self:I("Get Settings ok!")
+self:T("Get Settings ok!")
if settings:IsA2G_MGRS()then
locationstring=entry.coordinate:ToStringMGRS(settings)
elseif settings:IsA2G_LL_DMS()then
locationstring=entry.coordinate:ToStringLLDMS(settings)
+elseif settings:IsA2G_LL_DDM()then
+locationstring=entry.coordinate:ToStringLLDDM(settings)
elseif settings:IsA2G_BR()then
-locationstring=entry.coordinate:ToStringBR(Group:GetCoordinate()or Unit:GetCoordinate(),settings)
+local startcoordinate=Unit:GetCoordinate()or Group:GetCoordinate()
+locationstring=entry.coordinate:ToStringBR(startcoordinate,settings,false,self.RoundingPrecision)
end
end
end
-local text=string.format("%s lasing %s code %d\nat %s",reccename,typename,code,locationstring)
+local text=string.format("+ %s lasing %s code %d\nat %s",reccename,typename,code,locationstring)
report:Add(text)
+report:Add("---------------")
lines=lines+1
end
if lines==0 then
report:Add("No targets!")
+report:Add("---------------")
end
local reporttime=self.reporttimelong
if lines==0 then reporttime=self.reporttimeshort end
@@ -56736,8 +58498,57 @@ end
end
return canlase
end
+function AUTOLASE:_Prescient()
+for _,_data in pairs(self.RecceUnits)do
+if _data.isground and _data.unit and _data.unit:IsAlive()then
+local unit=_data.unit
+local position=unit:GetCoordinate()
+local needsinit=false
+if position then
+local lastposition=unit:GetProperty("lastposition")
+if not lastposition then
+unit:SetProperty("lastposition",position)
+lastposition=position
+needsinit=true
+end
+local dist=position:Get2DDistance(lastposition)
+local TNow=timer.getAbsTime()
+if dist>10 or needsinit==true or TNow-_data.timestamp>29 then
+local hasunits,hasstatics,_,Units,Statics=position:ScanObjects(self.LaseDistance,true,true,false)
+if hasunits then
+self:T(self.lid.."Checking possibly visible UNITs for Recce "..unit:GetName())
+for _,_target in pairs(Units)do
+local target=_target
+if target and target:GetCoalition()~=self.coalition then
+if unit:IsLOS(target)and(not target:IsUnitDetected(unit))then
+unit:KnowUnit(target,true,true)
+end
+end
+end
+end
+if hasstatics then
+self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName())
+for _,_static in pairs(Statics)do
+local static=STATIC:Find(_static)
+if static and static:GetCoalition()~=self.coalition and static:GetCoordinate()then
+local IsLOS=position:IsLOS(static:GetCoordinate())
+if IsLOS then
+unit:KnowUnit(static,true,true)
+end
+end
+end
+end
+end
+end
+end
+end
+return self
+end
function AUTOLASE:onbeforeMonitor(From,Event,To)
self:T({From,Event,To})
+if self.increasegroundawareness then
+self:_Prescient()
+end
self:UpdateIntel()
return self
end
@@ -56863,6 +58674,9 @@ coordinate=unit:GetCoordinate(),
}
if self.smoketargets then
local coord=unit:GetCoordinate()
+if self.smokeoffset then
+coord:Translate(self.smokeoffset.Distance,self.smokeoffset.Angle,true,true)
+end
local color=self:GetSmokeColor(reccename)
coord:Smoke(color)
end
@@ -56872,7 +58686,8 @@ self:__Lasing(2,laserspot)
end
end
end
-self:__Monitor(-30)
+local nextloop=-self.MonitorFrequency or-30
+self:__Monitor(nextloop)
return self
end
function AUTOLASE:onbeforeRecceKIA(From,Event,To,RecceName)
@@ -57067,8 +58882,8 @@ end
end
TIRESIAS={
ClassName="TIRESIAS",
-debug=false,
-version="0.0.5",
+debug=true,
+version=" 0.0.7-OPT",
Interval=20,
GroundSet=nil,
VehicleSet=nil,
@@ -57079,6 +58894,8 @@ AAARange=60,
HeloSwitchRange=10,
PlaneSwitchRange=25,
SwitchAAA=true,
+_cached_zones={},
+_cached_groupsets={},
}
function TIRESIAS:New()
local self=BASE:Inherit(self,FSM:New())
@@ -57086,9 +58903,10 @@ self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","Stop","Stopped")
-self.ExceptionSet=SET_GROUP:New():Clear(false)
+self.ExceptionSet=nil
+self._cached_zones={}
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
-self.lid=string.format("TIRESIAS %s | ",self.version)
+self.lid="TIRESIAS "..self.version.." | "
self:I(self.lid.."Managing ground groups!")
self:__Start(1)
return self
@@ -57096,6 +58914,7 @@ end
function TIRESIAS:SetActivationRanges(HeloMiles,PlaneMiles)
self.HeloSwitchRange=HeloMiles or 10
self.PlaneSwitchRange=PlaneMiles or 25
+self._cached_zones={}
return self
end
function TIRESIAS:SetAAARanges(FiringRange,SwitchAAA)
@@ -57104,87 +58923,106 @@ self.SwitchAAA=(SwitchAAA==false)and false or true
return self
end
function TIRESIAS:AddExceptionSet(Set)
-self:T(self.lid.."AddExceptionSet")
+self:T(self.lid.." AddExceptionSet")
+if not self.ExceptionSet then
+self.ExceptionSet=SET_GROUP:New()
+end
local exceptions=self.ExceptionSet
-Set:ForEachGroupAlive(
-function(grp)
-if not grp.Tiresias then
-grp.Tiresias={
-type="Exception",
+local exception_data={
+type=" Exception",
exception=true,
}
+Set:ForEachGroupAlive(
+function(grp)
+local inAAASet=self.AAASet:IsIncludeObject(grp)
+local inVehSet=self.VehicleSet:IsIncludeObject(grp)
+local inSAMSet=self.SAMSet:IsIncludeObject(grp)
+if grp:IsGround()and(not grp.Tiresias)and(not inAAASet)and(not inVehSet)and(not inSAMSet)then
+grp.Tiresias=exception_data
exceptions:AddGroup(grp,true)
+BASE:T(" TIRESIAS: Added exception group: "..grp:GetName())
end
-BASE:T("TIRESIAS: Added exception group: "..grp:GetName())
end
)
return self
end
function TIRESIAS._FilterNotAAA(Group)
local grp=Group
-local isaaa=grp:IsAAA()
-if isaaa==true and grp:IsGround()and not grp:IsShip()then
-return false
-else
+local is_air=grp:IsAir()
+local is_ship=grp:IsShip()
+local is_AAA=grp:IsAAA()
+if is_air or grp:IsShip()then
return true
end
+return not is_AAA
end
function TIRESIAS._FilterNotSAM(Group)
local grp=Group
-local issam=grp:IsSAM()
-if issam==true and grp:IsGround()and not grp:IsShip()then
-return false
-else
+local is_air=grp:IsGround()
+local is_ship=grp:IsShip()
+local is_SAM=grp:IsSAM()
+if is_air or grp:IsShip()then
return true
end
+return not is_SAM
end
function TIRESIAS._FilterAAA(Group)
local grp=Group
-local isaaa=grp:IsAAA()
-if isaaa==true and grp:IsGround()and not grp:IsShip()then
-return true
-else
+local is_ground=grp:IsGround()
+if(not is_ground)or grp:IsShip()then
return false
end
+return grp:IsAAA()
end
function TIRESIAS._FilterSAM(Group)
local grp=Group
-local issam=grp:IsSAM()
-if issam==true and grp:IsGround()and not grp:IsShip()then
-return true
-else
+local is_ground=grp:IsGround()
+if(not is_ground)or grp:IsShip()then
return false
end
+return grp:IsSAM()
end
function TIRESIAS:_InitGroups()
-self:T(self.lid.."_InitGroups")
+self:T(self.lid.." _InitGroups")
local EngageRange=self.AAARange
local SwitchAAA=self.SwitchAAA
+local aaa_data_template={
+type=" AAA",
+invisible=true,
+range=EngageRange,
+exception=false,
+AIOff=SwitchAAA,
+}
+local vehicle_data_template={
+type=" Vehicle",
+invisible=true,
+AIOff=true,
+exception=false,
+}
+local sam_data_template={
+type=" SAM",
+invisible=true,
+exception=false,
+}
self.AAASet:ForEachGroupAlive(
function(grp)
-if not grp.Tiresias then
+local tiresias_data=grp.Tiresias
+if not tiresias_data then
grp:OptionEngageRange(EngageRange)
grp:SetCommandInvisible(true)
if SwitchAAA then
grp:SetAIOff()
grp:EnableEmission(false)
end
-grp.Tiresias={
-type="AAA",
-invisible=true,
-range=EngageRange,
-exception=false,
-AIOff=SwitchAAA,
-}
-end
-if grp.Tiresias and(not grp.Tiresias.exception==true)then
-if grp.Tiresias.invisible and grp.Tiresias.invisible==false then
+grp.Tiresias=aaa_data_template
+elseif not tiresias_data.exception==true then
+if not tiresias_data.invisible==true then
grp:SetCommandInvisible(true)
-grp.Tiresias.invisible=true
-if SwitchAAA then
+tiresias_data.invisible=true
+if SwitchAAA==true then
grp:SetAIOff()
grp:EnableEmission(false)
-grp.Tiresias.AIOff=true
+tiresias_data.AIOff=true
end
end
end
@@ -57192,39 +59030,31 @@ end
)
self.VehicleSet:ForEachGroupAlive(
function(grp)
-if not grp.Tiresias then
+local tiresias_data=grp.Tiresias
+if not tiresias_data then
grp:SetAIOff()
grp:SetCommandInvisible(true)
-grp.Tiresias={
-type="Vehicle",
-invisible=true,
-AIOff=true,
-exception=false,
-}
-end
-if grp.Tiresias and(not grp.Tiresias.exception==true)then
-if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible==false then
+grp.Tiresias=vehicle_data_template
+elseif not tiresias_data.exception==true then
+if not tiresias_data.invisible then
grp:SetCommandInvisible(true)
grp:SetAIOff()
-grp.Tiresias.invisible=true
+tiresias_data.invisible=true
+tiresias_data.AIOff=true
end
end
end
)
self.SAMSet:ForEachGroupAlive(
function(grp)
-if not grp.Tiresias then
+local tiresias_data=grp.Tiresias
+if not tiresias_data then
grp:SetCommandInvisible(true)
-grp.Tiresias={
-type="SAM",
-invisible=true,
-exception=false,
-}
-end
-if grp.Tiresias and(not grp.Tiresias.exception==true)then
-if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible==false then
+grp.Tiresias=sam_data_template
+elseif not tiresias_data.exception==true then
+if not tiresias_data.invisible then
grp:SetCommandInvisible(true)
-grp.Tiresias.invisible=true
+tiresias_data.invisible=true
end
end
end
@@ -57232,52 +59062,64 @@ end
return self
end
function TIRESIAS:_EventHandler(EventData)
-self:T(string.format("%s Event = %d",self.lid,EventData.id))
+self:T(string.format(" %s Event = %d",self.lid,EventData.id))
local event=EventData
if event.id==EVENTS.PlayerEnterAircraft or event.id==EVENTS.PlayerEnterUnit then
-local unitname=event.IniUnitName or"none"
-local _unit=event.IniUnit
local _group=event.IniGroup
if _group and _group:IsAlive()then
-local radius=self.PlaneSwitchRange
-if _group:IsHelicopter()then
-radius=self.HeloSwitchRange
-end
+local radius=_group:IsHelicopter()and self.HeloSwitchRange or self.PlaneSwitchRange
self:_SwitchOnGroups(_group,radius)
end
end
return self
end
function TIRESIAS:_SwitchOnGroups(group,radius)
-self:T(self.lid.."_SwitchOnGroups "..group:GetName().." Radius "..radius.." NM")
-local zone=ZONE_GROUP:New("Zone-"..group:GetName(),group,UTILS.NMToMeters(radius))
-local ground=SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce()
+self:T(self.lid.." _SwitchOnGroups "..group:GetName().." Radius "..radius.." NM")
+local group_name=group:GetName()
+local cache_key=group_name.." _"..radius
+local zone=self._cached_zones[cache_key]
+local ground=self._cached_groupsets[cache_key]
+if not zone then
+zone=ZONE_GROUP:New(" Zone-"..group_name,group,UTILS.NMToMeters(radius))
+self._cached_zones[cache_key]=zone
+else
+zone:UpdateFromGroup(group)
+end
+if not ground then
+ground=SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce()
+self._cached_groupsets[cache_key]=ground
+else
+ground:FilterZones({zone},true):FilterOnce()
+end
local count=ground:CountAlive()
if self.debug then
-local text=string.format("There are %d groups around this plane or helo!",count)
-self:I(text)
+self:I(string.format(" There are %d groups around this plane or helo!",count))
end
+if count>0 then
local SwitchAAA=self.SwitchAAA
-if ground:CountAlive()>0 then
+local group_coalition=group:GetCoalition()
ground:ForEachGroupAlive(
function(grp)
-local name=grp:GetName()
-if grp.Tiresias and grp.Tiresias.type and(not grp.Tiresias.exception==true)then
-if grp.Tiresias.invisible==true then
+local tiresias_data=grp.Tiresias
+if grp:GetCoalition()~=group_coalition
+and tiresias_data
+and tiresias_data.type
+and not tiresias_data.exception==true then
+if tiresias_data.invisible==true then
grp:SetCommandInvisible(false)
-grp.Tiresias.invisible=false
+tiresias_data.invisible=false
end
-if grp.Tiresias.type=="Vehicle"and grp.Tiresias.AIOff and grp.Tiresias.AIOff==true then
+local grp_type=tiresias_data.type
+if grp_type=="Vehicle"and tiresias_data.AIOff==true then
grp:SetAIOn()
-grp.Tiresias.AIOff=false
-end
-if SwitchAAA and grp.Tiresias.type=="AAA"and grp.Tiresias.AIOff and grp.Tiresias.AIOff==true then
+tiresias_data.AIOff=false
+elseif SwitchAAA==true and grp_type=="AAA"and tiresias_data.AIOff==true then
grp:SetAIOn()
grp:EnableEmission(true)
-grp.Tiresias.AIOff=false
+tiresias_data.AIOff=false
end
else
-BASE:T("TIRESIAS - This group "..tostring(name).." has not been initialized or is an exception!")
+BASE:T("TIRESIAS - This group "..tostring(grp:GetName()).." has not been initialized or is an exception!")
end
end
)
@@ -57290,17 +59132,37 @@ local VehicleSet=SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS.
local AAASet=SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterAAA):FilterStart()
local SAMSet=SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterSAM):FilterStart()
local OpsGroupSet=SET_OPSGROUP:New():FilterActive(true):FilterStart()
-self.FlightSet=SET_GROUP:New():FilterCategories({"plane","helicopter"}):FilterStart()
+self.FlightSet=SET_GROUP:New():FilterCategories({" plane"," helicopter"}):FilterStart()
local EngageRange=self.AAARange
+local SwitchAAA=self.SwitchAAA
local ExceptionSet=self.ExceptionSet
-if self.ExceptionSet then
-function ExceptionSet:OnAfterAdded(From,Event,To,ObjectName,Object)
-BASE:I("TIRESIAS: EXCEPTION Object Added: "..Object:GetName())
-if Object and Object:IsAlive()then
-Object.Tiresias={
-type="Exception",
+local exception_data={
+type=" Exception",
exception=true,
}
+local vehicle_data={
+type=" Vehicle",
+invisible=true,
+AIOff=true,
+exception=false,
+}
+local aaa_data={
+type=" AAA",
+invisible=true,
+range=EngageRange,
+exception=false,
+AIOff=SwitchAAA,
+}
+local sam_data={
+type=" SAM",
+invisible=true,
+exception=false,
+}
+if ExceptionSet then
+function ExceptionSet:OnAfterAdded(From,Event,To,ObjectName,Object)
+BASE:I(" TIRESIAS: EXCEPTION Object Added: "..Object:GetName())
+if Object and Object:IsAlive()then
+Object.Tiresias=exception_data
Object:SetAIOn()
Object:SetCommandInvisible(false)
Object:EnableEmission(true)
@@ -57318,46 +59180,30 @@ ExceptionSet:AddGroup(grp,true)
end
end
function VehicleSet:OnAfterAdded(From,Event,To,ObjectName,Object)
-BASE:I("TIRESIAS: VEHCILE Object Added: "..Object:GetName())
+BASE:T(" TIRESIAS: VEHICLE Object Added: "..Object:GetName())
if Object and Object:IsAlive()then
Object:SetAIOff()
Object:SetCommandInvisible(true)
-Object.Tiresias={
-type="Vehicle",
-invisible=true,
-AIOff=true,
-exception=false,
-}
+Object.Tiresias=vehicle_data
end
end
-local SwitchAAA=self.SwitchAAA
function AAASet:OnAfterAdded(From,Event,To,ObjectName,Object)
if Object and Object:IsAlive()then
-BASE:I("TIRESIAS: AAA Object Added: "..Object:GetName())
+BASE:I(" TIRESIAS: AAA Object Added: "..Object:GetName())
Object:OptionEngageRange(EngageRange)
Object:SetCommandInvisible(true)
if SwitchAAA then
Object:SetAIOff()
Object:EnableEmission(false)
end
-Object.Tiresias={
-type="AAA",
-invisible=true,
-range=EngageRange,
-exception=false,
-AIOff=SwitchAAA,
-}
+Object.Tiresias=aaa_data
end
end
function SAMSet:OnAfterAdded(From,Event,To,ObjectName,Object)
if Object and Object:IsAlive()then
-BASE:I("TIRESIAS: SAM Object Added: "..Object:GetName())
+BASE:T(" TIRESIAS: SAM Object Added: "..Object:GetName())
Object:SetCommandInvisible(true)
-Object.Tiresias={
-type="SAM",
-invisible=true,
-exception=false,
-}
+Object.Tiresias=sam_data
end
end
self.VehicleSet=VehicleSet
@@ -57370,10 +59216,7 @@ return self
end
function TIRESIAS:onbeforeStatus(From,Event,To)
self:T({From,Event,To})
-if self:GetState()=="Stopped"then
-return false
-end
-return self
+return self:GetState()~=" Stopped"
end
function TIRESIAS:onafterStatus(From,Event,To)
self:T({From,Event,To})
@@ -57381,22 +59224,22 @@ if self.debug then
local count=self.VehicleSet:CountAlive()
local AAAcount=self.AAASet:CountAlive()
local SAMcount=self.SAMSet:CountAlive()
-local text=string.format("Overall: %d | Vehicles: %d | AAA: %d | SAM: %d",count+AAAcount+SAMcount,count,AAAcount,SAMcount)
-self:I(text)
+self:I(string.format(" Overall: %d | Vehicles: %d | AAA: %d | SAM: %d",
+count+AAAcount+SAMcount,count,AAAcount,SAMcount))
end
self:_InitGroups()
-if self.FlightSet:CountAlive()>0 then
+local flight_count=self.FlightSet:CountAlive()
+if flight_count>0 then
local Set=self.FlightSet:GetAliveSet()
-for _,_plane in pairs(Set)do
+local helo_range=self.HeloSwitchRange
+local plane_range=self.PlaneSwitchRange
+for _,_plane in pairs(Set or{})do
local plane=_plane
-local radius=self.PlaneSwitchRange
-if plane:IsHelicopter()then
-radius=self.HeloSwitchRange
-end
-self:_SwitchOnGroups(_plane,radius)
+local radius=plane:IsHelicopter()and helo_range or plane_range
+self:_SwitchOnGroups(plane,radius)
end
end
-if self:GetState()~="Stopped"then
+if self:GetState()~=" Stopped"then
self:__Status(self.Interval)
end
return self
@@ -57404,6 +59247,7 @@ end
function TIRESIAS:onafterStop(From,Event,To)
self:T({From,Event,To})
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
+self._cached_zones={}
return self
end
STRATEGO={
@@ -58991,6 +60835,8 @@ C2A="C2A_Greyhound",
RHINOE="FA-18E",
RHINOF="FA-18F",
GROWLER="EA-18G",
+CORSAIR="F4U-1D",
+CORSAIR_CW="F4U-1D CW",
}
AIRBOSS.CarrierType={
ROOSEVELT="CVN_71",
@@ -58999,7 +60845,12 @@ WASHINGTON="CVN_73",
TRUMAN="CVN_75",
STENNIS="Stennis",
FORRESTAL="Forrestal",
+ENTERPRISE66="USS Enterprise 1966",
+ENTERPRISEMODERN="cvn-65",
VINSON="VINSON",
+ESSEX="Essex",
+BONHOMMERICHARD="USS Bon Homme Richard",
+ESSEXSCB125="essex_scb125",
HERMES="HERMES81",
INVINCIBLE="hms_invincible",
TARAWA="LHA_Tarawa",
@@ -59033,6 +60884,7 @@ GROOVE_IM="Groove In the Middle",
GROOVE_IC="Groove In Close",
GROOVE_AR="Groove At the Ramp",
GROOVE_IW="Groove In the Wires",
+GROOVE_IWs="Groove In the Wires stopped?",
GROOVE_AL="Groove Abeam Landing Spot",
GROOVE_LC="Groove Level Cross",
BOLTER="Bolter Pattern",
@@ -59056,7 +60908,7 @@ HARD="TOPGUN Graduate",
}
AIRBOSS.MenuF10={}
AIRBOSS.MenuF10Root=nil
-AIRBOSS.version="1.3.3"
+AIRBOSS.version="1.4.2"
function AIRBOSS:New(carriername,alias)
local self=BASE:Inherit(self,FSM:New())
self:F2({carriername=carriername,alias=alias})
@@ -59091,6 +60943,7 @@ self:SetBeaconRefresh()
self:SetMaxLandingPattern()
self:SetMaxMarshalStacks()
self:SetMaxSectionSize()
+self:SetMaxSectionDistance()
self:SetMaxFlightsPerStack()
self:SetHandleAION()
self:SetExtraVoiceOvers(false)
@@ -59134,8 +60987,18 @@ elseif self.carriertype==AIRBOSS.CarrierType.TRUMAN then
self:_InitNimitz()
elseif self.carriertype==AIRBOSS.CarrierType.FORRESTAL then
self:_InitForrestal()
+elseif self.carriertype==AIRBOSS.CarrierType.ENTERPRISE66 then
+self:_InitEnterprise()
+elseif self.carriertype==AIRBOSS.CarrierType.ENTERPRISEMODERN then
+self:_InitEnterprise()
elseif self.carriertype==AIRBOSS.CarrierType.VINSON then
self:_InitStennis()
+elseif self.carriertype==AIRBOSS.CarrierType.ESSEX then
+self:_InitEssex()
+elseif self.carriertype==AIRBOSS.CarrierType.BONHOMMERICHARD then
+self:_InitBonHommeRichard()
+elseif self.carriertype==AIRBOSS.CarrierType.ESSEXSCB125 then
+self:_InitEssexSCB125()
elseif self.carriertype==AIRBOSS.CarrierType.HERMES then
self:_InitHermes()
elseif self.carriertype==AIRBOSS.CarrierType.INVINCIBLE then
@@ -59236,6 +61099,10 @@ self:AddTransition("*","Save","*")
self:AddTransition("*","Stop","Stopped")
return self
end
+function AIRBOSS:SetCarrierIllumination(Mode)
+self.carrier:SetCarrierIlluminationMode(Mode)
+return self
+end
function AIRBOSS:SetWelcomePlayers(Switch)
self.welcome=Switch
return self
@@ -59461,7 +61328,12 @@ self.Tmessage=Duration or 10
return self
end
function AIRBOSS:SetGlideslopeErrorThresholds(_max,_min,High,HIGH,Low,LOW)
-if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
+if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or
+self.carriertype==AIRBOSS.CarrierType.HERMES or
+self.carriertype==AIRBOSS.CarrierType.TARAWA or
+self.carriertype==AIRBOSS.CarrierType.AMERICA or
+self.carriertype==AIRBOSS.CarrierType.JCARLOS or
+self.carriertype==AIRBOSS.CarrierType.CANBERRA then
self.gle._max=_max or 0.7
self.gle.High=High or 1.4
self.gle.HIGH=HIGH or 1.9
@@ -59579,19 +61451,19 @@ self.SRS:SetCoalition(self:GetCoalition())
self.SRS:SetCoordinate(self:GetCoordinate())
self.SRS:SetCulture(Culture or"en-US")
self.SRS:SetGender(Gender or"male")
-self.SRS:SetPath(PathToSRS)
-self.SRS:SetPort(Port or 5002)
+self.SRS:SetPort(Port or MSRS.port or 5002)
self.SRS:SetLabel(self.AirbossRadio.alias or"AIRBOSS")
self.SRS:SetCoordinate(self.carrier:GetCoordinate())
self.SRS:SetVolume(Volume or 1)
if GoogleCreds then
-self.SRS:SetProviderOptionsGoogle(GoogleCreds,GoogleCreds)
-self.SRS:SetProvider(MSRS.Provider.GOOGLE)
+self.SRS:SetGoogle(GoogleCreds)
end
if Voice then
self.SRS:SetVoice(Voice)
end
-self.SRS:SetVolume(Volume or 1.0)
+if(not Voice)and self.SRS and self.SRS:GetProvider()==MSRS.Provider.GOOGLE then
+self.SRS.voice=MSRS.poptions["gcloud"].voice or MSRS.Voices.Google.Standard.en_US_Standard_B
+end
self.SRSQ=MSRSQUEUE:New("AIRBOSS")
self.SRSQ:SetTransmitOnlyWithPlayers(true)
if not self.PilotRadio then
@@ -59721,6 +61593,17 @@ nmax=math.min(nmax,4)
self.NmaxSection=nmax-1
return self
end
+function AIRBOSS:SetMaxSectionDistance(dmax)
+if dmax then
+if dmax<10 then
+dmax=10
+elseif dmax>5000 then
+dmax=5000
+end
+end
+self.maxsectiondistance=dmax or 100
+return self
+end
function AIRBOSS:SetMaxFlightsPerStack(nmax)
nmax=nmax or 2
nmax=math.max(nmax,1)
@@ -59855,7 +61738,6 @@ self:HandleEvent(EVENTS.Ejection)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._PlayerLeft)
self:HandleEvent(EVENTS.MissionEnd)
self:HandleEvent(EVENTS.RemoveUnit)
-self:HandleEvent(EVENTS.UnitLost,self.OnEventRemoveUnit)
self.StatusTimer=TIMER:New(self._Status,self):Start(2,0.5)
self:__Status(1)
end
@@ -60256,7 +62138,7 @@ self.BreakEntry.LimitZmin=nil
self.BreakEntry.LimitZmax=nil
self.BreakEarly.name="Early Break"
self.BreakEarly.Xmin=-UTILS.NMToMeters(1)
-self.BreakEarly.Xmax=UTILS.NMToMeters(5)
+self.BreakEarly.Xmax=UTILS.NMToMeters(7)
self.BreakEarly.Zmin=-UTILS.NMToMeters(2)
self.BreakEarly.Zmax=UTILS.NMToMeters(1)
self.BreakEarly.LimitXmin=0
@@ -60265,7 +62147,7 @@ self.BreakEarly.LimitZmin=-UTILS.NMToMeters(0.2)
self.BreakEarly.LimitZmax=nil
self.BreakLate.name="Late Break"
self.BreakLate.Xmin=-UTILS.NMToMeters(1)
-self.BreakLate.Xmax=UTILS.NMToMeters(5)
+self.BreakLate.Xmax=UTILS.NMToMeters(7)
self.BreakLate.Zmin=-UTILS.NMToMeters(2)
self.BreakLate.Zmax=UTILS.NMToMeters(1)
self.BreakLate.LimitXmin=0
@@ -60274,7 +62156,7 @@ self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.8)
self.BreakLate.LimitZmax=nil
self.Abeam.name="Abeam Position"
self.Abeam.Xmin=-UTILS.NMToMeters(5)
-self.Abeam.Xmax=UTILS.NMToMeters(5)
+self.Abeam.Xmax=UTILS.NMToMeters(7)
self.Abeam.Zmin=-UTILS.NMToMeters(2)
self.Abeam.Zmax=500
self.Abeam.LimitXmin=-200
@@ -60331,7 +62213,7 @@ self.carrierparam.rwywidth=25
self.carrierparam.wire1=55
self.carrierparam.wire2=67
self.carrierparam.wire3=79
-self.carrierparam.wire4=92
+self.carrierparam.wire4=96
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3
end
function AIRBOSS:_InitForrestal()
@@ -60350,6 +62232,58 @@ self.carrierparam.wire3=64
self.carrierparam.wire4=74
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3
end
+function AIRBOSS:_InitEnterprise()
+self:_InitForrestal()
+self.carrierparam.sterndist=-164.30
+self.carrierparam.deckheight=19.52
+self.carrierparam.totlength=335
+self.carrierparam.rwylength=223
+self.carrierparam.wire1=57.7
+self.carrierparam.wire2=69.6
+self.carrierparam.wire3=79.5
+self.carrierparam.wire4=90.0
+end
+function AIRBOSS:_InitEssex()
+self:_InitNimitz()
+self.carrierparam.sterndist=-126
+self.carrierparam.deckheight=19.27
+self.carrierparam.totlength=268
+self.carrierparam.totwidthport=23
+self.carrierparam.totwidthstarboard=23
+self.carrierparam.rwyangle=0.0
+self.carrierparam.rwylength=265
+self.carrierparam.rwywidth=20
+self.carrierparam.wire1=21.9
+self.carrierparam.wire2=28.3
+self.carrierparam.wire3=34.7
+self.carrierparam.wire4=41.1
+self.carrierparam.wire5=47.4
+self.carrierparam.wire6=53.7
+self.carrierparam.wire7=59.0
+self.carrierparam.wire8=64.1
+self.carrierparam.wire9=72.7
+self.carrierparam.wire10=78.0
+self.carrierparam.wire11=85.5
+self.carrierparam.wire12=105.9
+self.carrierparam.wire13=113.3
+self.carrierparam.wire14=121.0
+self.carrierparam.wire15=128.5
+self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3
+end
+function AIRBOSS:_InitBonHommeRichard()
+self:_InitEssex()
+self.carrierparam.deckheight=16.95
+self.carrierparam.rwyangle=-11.4
+self.carrierparam.rwylength=97
+self.carrierparam.rwywidth=20
+self.carrierparam.wire1=40.4
+self.carrierparam.wire2=45
+self.carrierparam.wire3=51
+self.carrierparam.wire4=58.1
+end
+function AIRBOSS:_InitEssexSCB125()
+self:_InitBonHommeRichard()
+end
function AIRBOSS:_InitHermes()
self:_InitStennis()
self.carrierparam.sterndist=-105
@@ -60622,6 +62556,7 @@ self.LSOCall.N7.duration=0.51
self.LSOCall.N8.duration=0.38
self.LSOCall.N9.duration=0.34
self.LSOCall.PADDLESCONTACT.duration=0.91
+self.LSOCall.POWERsoft.duration=0.9
self.LSOCall.POWER.duration=0.45
self.LSOCall.RADIOCHECK.duration=0.90
self.LSOCall.RIGHTFORLINEUP.duration=0.70
@@ -60669,6 +62604,7 @@ self.LSOCall.N8.duration=0.37
self.LSOCall.N9.duration=0.40
self.LSOCall.PADDLESCONTACT.duration=1.00
self.LSOCall.POWER.duration=0.50
+self.LSOCall.POWERsoft.duration=0.9
self.LSOCall.RADIOCHECK.duration=1.10
self.LSOCall.RIGHTFORLINEUP.duration=0.80
self.LSOCall.ROGERBALL.duration=1.00
@@ -60741,6 +62677,7 @@ RIGHTFORLINEUP={file="LSO-RightForLineup",suffix="ogg",loud=true,subtitle="Right
HIGH={file="LSO-High",suffix="ogg",loud=true,subtitle="You're high",duration=0.65,subduration=1},
LOW={file="LSO-Low",suffix="ogg",loud=true,subtitle="You're low",duration=0.50,subduration=1},
POWER={file="LSO-Power",suffix="ogg",loud=true,subtitle="Power",duration=0.50,subduration=1},
+POWERsoft={file="LSO-Power-soft",suffix="ogg",loud=false,subtitle="Power-soft",duration=0.90,subduration=1},
SLOW={file="LSO-Slow",suffix="ogg",loud=true,subtitle="You're slow",duration=0.65,subduration=1},
FAST={file="LSO-Fast",suffix="ogg",loud=true,subtitle="You're fast",duration=0.70,subduration=1},
ROGERBALL={file="LSO-RogerBall",suffix="ogg",loud=false,subtitle="Roger ball",duration=1.00,subduration=2},
@@ -60854,6 +62791,7 @@ local goshawk=playerData.actype==AIRBOSS.AircraftCarrier.T45C
local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC
local harrier=playerData.actype==AIRBOSS.AircraftCarrier.AV8B
local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B
+local corsair=playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW
local aoa={}
if hornet then
aoa.SLOW=9.8
@@ -60864,13 +62802,13 @@ aoa.OnSpeedMin=7.4
aoa.Fast=6.9
aoa.FAST=6.3
elseif tomcat then
-aoa.SLOW=self:_AoAUnit2Deg(playerData,17.0)
-aoa.Slow=self:_AoAUnit2Deg(playerData,16.0)
-aoa.OnSpeedMax=self:_AoAUnit2Deg(playerData,15.5)
+aoa.SLOW=self:_AoAUnit2Deg(playerData,17.5)
+aoa.Slow=self:_AoAUnit2Deg(playerData,16.5)
+aoa.OnSpeedMax=self:_AoAUnit2Deg(playerData,16.0)
aoa.OnSpeed=self:_AoAUnit2Deg(playerData,15.0)
-aoa.OnSpeedMin=self:_AoAUnit2Deg(playerData,14.5)
-aoa.Fast=self:_AoAUnit2Deg(playerData,14.0)
-aoa.FAST=self:_AoAUnit2Deg(playerData,13.0)
+aoa.OnSpeedMin=self:_AoAUnit2Deg(playerData,14.0)
+aoa.Fast=self:_AoAUnit2Deg(playerData,13.5)
+aoa.FAST=self:_AoAUnit2Deg(playerData,12.5)
elseif goshawk then
aoa.SLOW=8.00
aoa.Slow=7.75
@@ -60880,13 +62818,13 @@ aoa.OnSpeedMin=6.75
aoa.Fast=6.25
aoa.FAST=6.00
elseif skyhawk then
-aoa.SLOW=9.50
-aoa.Slow=9.25
-aoa.OnSpeedMax=9.00
+aoa.SLOW=10.50
+aoa.Slow=9.50
+aoa.OnSpeedMax=9.25
aoa.OnSpeed=8.75
-aoa.OnSpeedMin=8.50
-aoa.Fast=8.25
-aoa.FAST=8.00
+aoa.OnSpeedMin=8.25
+aoa.Fast=8.00
+aoa.FAST=7.00
elseif harrier then
aoa.SLOW=16.0
aoa.Slow=13.5
@@ -60895,6 +62833,14 @@ aoa.OnSpeed=10.0
aoa.OnSpeedMin=9.5
aoa.Fast=8.0
aoa.FAST=7.5
+elseif corsair then
+aoa.SLOW=16.0
+aoa.Slow=13.5
+aoa.OnSpeedMax=12.5
+aoa.OnSpeed=10.0
+aoa.OnSpeedMin=9.5
+aoa.Fast=8.0
+aoa.FAST=7.5
end
return aoa
end
@@ -60928,6 +62874,7 @@ local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC
local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B
local harrier=playerData.actype==AIRBOSS.AircraftCarrier.AV8B
local goshawk=playerData.actype==AIRBOSS.AircraftCarrier.T45C
+local corsair=playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW
local alt
local aoa
local dist
@@ -60964,6 +62911,9 @@ speed=UTILS.KnotsToMps(250)
elseif goshawk then
alt=UTILS.FeetToMeters(800)
speed=UTILS.KnotsToMps(300)
+elseif corsair then
+alt=UTILS.FeetToMeters(300)
+speed=UTILS.KnotsToMps(120)
end
elseif step==AIRBOSS.PatternStep.BREAKENTRY then
if hornet or tomcat or harrier then
@@ -60975,24 +62925,36 @@ speed=UTILS.KnotsToMps(250)
elseif goshawk then
alt=UTILS.FeetToMeters(800)
speed=UTILS.KnotsToMps(300)
+elseif corsair then
+alt=UTILS.FeetToMeters(200)
+speed=UTILS.KnotsToMps(110)
end
elseif step==AIRBOSS.PatternStep.EARLYBREAK then
if hornet or tomcat or harrier or goshawk then
alt=UTILS.FeetToMeters(800)
elseif skyhawk then
alt=UTILS.FeetToMeters(600)
+elseif corsair then
+alt=UTILS.FeetToMeters(200)
+speed=UTILS.KnotsToMps(100)
end
elseif step==AIRBOSS.PatternStep.LATEBREAK then
if hornet or tomcat or harrier or goshawk then
alt=UTILS.FeetToMeters(800)
elseif skyhawk then
alt=UTILS.FeetToMeters(600)
+elseif corsair then
+alt=UTILS.FeetToMeters(150)
+speed=UTILS.KnotsToMps(100)
end
elseif step==AIRBOSS.PatternStep.ABEAM then
if hornet or tomcat or harrier or goshawk then
alt=UTILS.FeetToMeters(600)
elseif skyhawk then
alt=UTILS.FeetToMeters(500)
+elseif corsair then
+alt=UTILS.FeetToMeters(150)
+speed=UTILS.KnotsToMps(90)
end
aoa=aoaac.OnSpeed
if goshawk then
@@ -61011,6 +62973,9 @@ elseif skyhawk then
alt=UTILS.FeetToMeters(500)
elseif harrier then
alt=UTILS.FeetToMeters(425)
+elseif corsair then
+alt=UTILS.FeetToMeters(90)
+speed=UTILS.KnotsToMps(90)
end
aoa=aoaac.OnSpeed
elseif step==AIRBOSS.PatternStep.WAKE then
@@ -61020,6 +62985,8 @@ elseif tomcat then
alt=UTILS.FeetToMeters(430)
elseif skyhawk then
alt=UTILS.FeetToMeters(370)
+elseif corsair then
+alt=UTILS.FeetToMeters(80)
end
aoa=aoaac.OnSpeed
elseif step==AIRBOSS.PatternStep.FINAL then
@@ -61031,6 +62998,8 @@ elseif skyhawk then
alt=UTILS.FeetToMeters(300)
elseif harrier then
alt=UTILS.FeetToMeters(312)
+elseif corsair then
+alt=UTILS.FeetToMeters(80)
end
aoa=aoaac.OnSpeed
end
@@ -61421,6 +63390,8 @@ elseif flight.actype==AIRBOSS.AircraftCarrier.F14A_AI or flight.actype==AIRBOSS.
Speed=UTILS.KnotsToKmph(175)
elseif flight.actype==AIRBOSS.AircraftCarrier.S3B or flight.actype==AIRBOSS.AircraftCarrier.S3BTANKER then
Speed=UTILS.KnotsToKmph(140)
+elseif flight.actype==AIRBOSS.AircraftCarrier.CORSAIR or flight.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW then
+Speed=UTILS.KnotsToKmph(100)
end
local Carrier=self:GetCoordinate()
local hdg=self:GetHeading()
@@ -62147,6 +64118,28 @@ if playerData then
local unit=playerData.unit
if unit and unit:IsAlive()then
if unit:IsInZone(self.zoneCCA)then
+if playerData.step==AIRBOSS.PatternStep.WAKE then
+if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then
+playerData.wrappedUpAtWakeLittle=true
+elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then
+playerData.wrappedUpAtWakeFull=true
+elseif math.abs(playerData.unit:GetRoll())>45 then
+playerData.wrappedUpAtWakeUnderline=true
+elseif math.abs(playerData.unit:GetRoll())<20 and math.abs(playerData.unit:GetRoll())>=10 then
+playerData.AAatWakeLittle=true
+elseif math.abs(playerData.unit:GetRoll())<10 and math.abs(playerData.unit:GetRoll())>=2 then
+playerData.AAatWakeFull=true
+elseif math.abs(playerData.unit:GetRoll())<2 then
+playerData.AAatWakeUnderline=true
+else
+end
+if math.abs(playerData.unit:GetAoA())>=15 then
+playerData.AFU=true
+elseif math.abs(playerData.unit:GetAoA())<=5 then
+playerData.AFU=true
+else
+end
+end
if playerData.attitudemonitor then
self:_AttitudeMonitor(playerData)
end
@@ -62248,7 +64241,7 @@ end
end
function AIRBOSS:_SetTimeInGroove(playerData)
if playerData.TIG0 then
-playerData.Tgroove=timer.getTime()-playerData.TIG0
+playerData.Tgroove=timer.getTime()-playerData.TIG0-1.5
else
playerData.Tgroove=999
end
@@ -62528,13 +64521,13 @@ end
function AIRBOSS:OnEventRemoveUnit(EventData)
self:F3({eventland=EventData})
if EventData==nil then
-self:E(self.lid.."ERROR: EventData=nil in event REMOVEUNIT!")
-self:E(EventData)
+self:T(self.lid.."ERROR: EventData=nil in event REMOVEUNIT!")
+self:T(EventData)
return
end
if EventData.IniUnit==nil then
-self:E(self.lid.."ERROR: EventData.IniUnit=nil in event REMOVEUNIT!")
-self:E(EventData)
+self:T(self.lid.."ERROR: EventData.IniUnit=nil in event REMOVEUNIT!")
+self:T(EventData)
return
end
local _unitName=EventData.IniUnitName
@@ -62819,7 +64812,17 @@ function AIRBOSS:_DirtyUp(playerData)
self:_CheckCorridor(playerData)
local inzone=playerData.unit:IsInZone(self:_GetZoneDirtyUp(playerData.case))
if inzone then
-self:_PlayerHint(playerData)
+playerData.Tgroove=timer.getTime()-playerData.TIG0-1.5
+playerData.wrappedUpAtWakeLittle=false
+playerData.wrappedUpAtWakeFull=false
+playerData.wrappedUpAtWakeUnderline=false
+playerData.wrappedUpAtStartLittle=false
+playerData.wrappedUpAtStartFull=false
+playerData.wrappedUpAtStartUnderline=false
+playerData.AAatWakeLittle=false
+playerData.AAatWakeFull=false
+playerData.AAatWakeUnderline=false
+playerData.AFU=false
if playerData.actype==AIRBOSS.AircraftCarrier.HORNET
or playerData.actype==AIRBOSS.AircraftCarrier.F14A
or playerData.actype==AIRBOSS.AircraftCarrier.F14B
@@ -62879,9 +64882,77 @@ if self:_CheckAbort(X,Z,self.BreakEntry)then
self:_AbortPattern(playerData,X,Z,self.BreakEntry,true)
return
end
+local stern=self:_GetSternCoord()
+local coord=playerData.unit:GetCoordinate()
+local dist=coord:Get2DDistance(stern)
+local playerCallsign=playerData.unit:GetCallsign()
+local playerName=playerData.name
+local unit=playerData.unit
+local unitClient=Unit.getByName(unit:GetName())
+local hookArgument=unitClient:getDrawArgumentValue(25)
+local hookArgument_Tomcat=unitClient:getDrawArgumentValue(1305)
+local speedMPS=playerData.unit:GetVelocityMPS()
+local speedKTS=UTILS.MpsToKnots(speedMPS)
+local player_alt=playerData.unit:GetAltitude()
+player_alt_feet=player_alt*3.28
+player_alt_feet=player_alt_feet/10
+player_alt_feet=math.floor(player_alt_feet)*10
+local player_velocity_round=speedKTS*1.00
+player_velocity_round=player_velocity_round/10
+player_velocity_round=math.floor(player_velocity_round)*10
+local player_alt_feet=player_alt*3.28
+player_alt_feet=player_alt_feet/10
+player_alt_feet=math.floor(player_alt_feet)*10
+local Play_SH_Sound=USERSOUND:New("Airboss Soundfiles/GreatBallsOfFire.ogg")
+local Play_666SH_Sound=USERSOUND:New("Airboss Soundfiles/Runninwiththedevil.ogg")
+local playerType=playerData.actype
+if dist<1000 and clientSHBFlag==false then
+if speedKTS>450 and speedKTS<590 then
+if player_alt_feet<1500 then
+if hookArgument>0 or hookArgument_Tomcat>0 then
+playerData.shb=true
+trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10)
+local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**')
+HypeMan.sendBotMessage(sh_message_to_discord)
+Play_SH_Sound:ToAll()
+clientSHBFlag=true
+else
+playerData.shb=false
+end
+else
+end
+elseif speedKTS>589 then
+if player_alt_feet<625 and player_alt_feet>575 then
+if hookArgument>0 or hookArgument_Tomcat>0 then
+playerData.shb=true
+trigger.action.outText(playerName..' performing a 666 Sierra Hotel Break in a '..playerType,10)
+local sh_message_to_discord=('**'..playerName..' is performing a 666 Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**')
+HypeMan.sendBotMessage(sh_message_to_discord)
+Play_666SH_Sound:ToAll()
+clientSHBFlag=true
+else
+playerData.shb=false
+end
+else
+if hookArgument>0 or hookArgument_Tomcat>0 then
+playerData.shb=true
+trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10)
+local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**')
+HypeMan.sendBotMessage(sh_message_to_discord)
+Play_SH_Sound:ToAll()
+clientSHBFlag=true
+else
+playerData.shb=false
+end
+end
+else
+end
+else
+end
if self:_CheckLimits(X,Z,self.BreakEntry)then
self:_PlayerHint(playerData)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.EARLYBREAK)
+clientSHBFlag=false
end
end
function AIRBOSS:_Break(playerData,part)
@@ -62953,6 +65024,16 @@ self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.NINETY)
end
end
function AIRBOSS:_Ninety(playerData)
+playerData.wrappedUpAtWakeLittle=false
+playerData.wrappedUpAtWakeFull=false
+playerData.wrappedUpAtWakeUnderline=false
+playerData.wrappedUpAtStartLittle=false
+playerData.wrappedUpAtStartFull=false
+playerData.wrappedUpAtStartUnderline=false
+playerData.AAatWakeLittle=false
+playerData.AAatWakeFull=false
+playerData.AAatWakeUnderline=false
+playerData.AFU=false
local X,Z=self:_GetDistances(playerData.unit)
if self:_CheckAbort(X,Z,self.Ninety)then
self:_AbortPattern(playerData,X,Z,self.Ninety,true)
@@ -63005,6 +65086,9 @@ groovedata.Alt=self:_GetAltCarrier(playerData.unit)
groovedata.AoA=playerData.unit:GetAoA()
groovedata.GSE=self:_Glideslope(playerData.unit)
groovedata.LUE=self:_Lineup(playerData.unit,true)
+groovedata.LUEwire=self:_LineupWIRE(playerData.unit,true)
+groovedata.LeftNozzle=self:_NozzleArgumentLeft(playerData.unit)
+groovedata.RightNozzle=self:_NozzleArgumentRight(playerData.unit)
groovedata.Roll=playerData.unit:GetRoll()
groovedata.Pitch=playerData.unit:GetPitch()
groovedata.Yaw=playerData.unit:GetYaw()
@@ -63043,6 +65127,7 @@ local RXX=UTILS.NMToMeters(0.750)
local RIM=UTILS.NMToMeters(0.500)
local RIC=UTILS.NMToMeters(0.250)
local RAR=UTILS.NMToMeters(0.040)
+local RIW=UTILS.NMToMeters(-0.020)
local groovedata=self:_GetGrooveData(playerData)
table.insert(playerData.trapsheet,groovedata)
local X=groovedata.X
@@ -63055,7 +65140,7 @@ local rho=groovedata.Rho
local lineupError=groovedata.LUE
local glideslopeError=groovedata.GSE
local AoA=groovedata.AoA
-if rho<=RXX and playerData.step==AIRBOSS.PatternStep.GROOVE_XX and(math.abs(groovedata.Roll)<=4.0 and playerData.unit:IsInZone(self:_GetZoneLineup()))then
+if rho<=RXX and playerData.step==AIRBOSS.PatternStep.GROOVE_XX and(math.abs(groovedata.Roll)<=3.5 or playerData.unit:IsInZone(self:_GetZoneLineup()))then
playerData.TIG0=timer.getTime()
self:RadioTransmission(self.LSORadio,self.LSOCall.CALLTHEBALL,nil,nil,nil,true)
playerData.Tlso=timer.getTime()
@@ -63076,6 +65161,8 @@ self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_AL)
else
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_IW)
end
+elseif rho<=RIW and playerData.step==AIRBOSS.PatternStep.GROOVE_IW then
+playerData.groove.IW=UTILS.DeepCopy(groovedata)
elseif rho<=RAR and playerData.step==AIRBOSS.PatternStep.GROOVE_AL then
playerData.groove.AL=UTILS.DeepCopy(groovedata)
local ZoneALS=self:_GetZoneAbeamLandingSpot()
@@ -63119,6 +65206,37 @@ local gd=playerData.groove[gs]
if gd then
self:T3(gd)
local d=UTILS.MetersToNM(rho)
+if playerData.case~=3 then
+if playerData.wrappedUpAtWakeUnderline==true or playerData.wrappedUpAtStartUnderline==true then
+gd.WrappedUp="_WU_"
+elseif playerData.wrappedUpAtWakeUnderline==false and playerData.wrappedUpAtStartUnderline==false then
+if playerData.wrappedUpAtWakeFull==true or playerData.wrappedUpAtStartFull==true then
+gd.WrappedUp="WU"
+elseif playerData.wrappedUpAtStartFull==false then
+if playerData.wrappedUpAtWakeLittle==true or playerData.wrappedUpAtStartLittle==true then
+gd.WrappedUp="(WU)"
+end
+end
+else
+end
+if playerData.AAatWakeUnderline==true then
+gd.AngledApch="_AA_"
+elseif playerData.AAatWakeUnderline==false then
+if playerData.AAatWakeFull==true then
+gd.AngledApch="AA"
+elseif playerData.AAatWakeFull==false then
+if playerData.AAatWakeLittle==true then
+gd.AngledApch="(AA)"
+end
+end
+else
+end
+if playerData.AFU==true then
+gd.AFU="AFU"
+else
+end
+else
+end
if rho>=RAR and rho<=RIM then
if gd.LUE>0.22 and lineupError<-0.22 then
env.info" Drift Right across centre ==> DR-"
@@ -63305,6 +65423,8 @@ elseif self.carriertype==AIRBOSS.CarrierType.STENNIS then
self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(7,FB+90,true,true)
elseif self.carriertype==AIRBOSS.CarrierType.FORRESTAL then
self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(7.5,FB+90,true,true)
+elseif self.carriertype==AIRBOSS.CarrierType.ESSEX then
+self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(-1,FB+90,true,true)
else
self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(9.5,FB+90,true,true)
end
@@ -63407,13 +65527,13 @@ end
playerData.wire=wire
local text=string.format("Trapped %d-wire.",wire)
if wire==3 then
-text=text.." Well done!"
+text=text.." "
elseif wire==2 then
-text=text.." Not bad, maybe you even get the 3rd next time."
+text=text.." "
elseif wire==4 then
-text=text.." That was scary. You can do better than this!"
+text=text.." "
elseif wire==1 then
-text=text.." Try harder next time!"
+text=text.." "
end
self:MessageToPlayer(playerData,text,"LSO","")
local hint=string.format("Trapped %d-wire.",wire)
@@ -63743,6 +65863,33 @@ else
text=text..string.format("\nR=%.2f NM | X=%d Z=%d m",UTILS.MetersToNM(rho),dx,dz)
text=text..string.format("\nGamma=%.1f° | Rho=%.1f°",relhead,phi)
end
+local lueWire=self:_LineupWIRE(playerData.unit,true)
+text=text..string.format("\nLineUpForWireCalls=%.2f° | lineup for Groove calls=%.2f°",lueWire or 0,lue or 0)
+local unitClient=Unit.getByName(unit:GetName())
+local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET
+local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B
+if hornet then
+local nozzlePosL=0
+local burnerPosL=unitClient:getDrawArgumentValue(28)
+if burnerPosL<0.2 then
+nozzlePosL=unitClient:getDrawArgumentValue(89)
+else
+nozzlePosL=0
+end
+local nozzlePosR=0
+local burnerPosR=unitClient:getDrawArgumentValue(29)
+if burnerPosR<0.2 then
+nozzlePosR=unitClient:getDrawArgumentValue(90)
+else
+nozzlePosR=0
+end
+text=text..string.format("\n Left Nozzle position=%.2f | Right Nozzle position=%.2f ",nozzlePosL,nozzlePosR)
+end
+if tomcat then
+local nozzlePosL=unitClient:getDrawArgumentValue(434)
+local nozzlePosR=unitClient:getDrawArgumentValue(433)
+text=text..string.format("\n Left Nozzle position=%.2f | Right Nozzle position=%.2f ",nozzlePosL,nozzlePosR)
+end
MESSAGE:New(text,1,nil,true):ToClient(playerData.client)
end
function AIRBOSS:_Glideslope(unit,optangle)
@@ -63803,6 +65950,61 @@ local z=UTILS.VecDot(Z,C)
local lineup=math.deg(math.atan2(z,x))
return lineup
end
+function AIRBOSS:_LineupWIRE(unit,runway)
+local landingcoord=self:_GetOptLandingCoordinateWIRE()
+local A=landingcoord:GetVec3()
+local B=unit:GetVec3()
+local C=UTILS.VecSubstract(A,B)
+C.y=0.0
+local X=self.carrier:GetOrientationX()
+X.y=0.0
+if runway then
+X=UTILS.Rotate2D(X,-self.carrierparam.rwyangle)
+end
+local x=UTILS.VecDot(X,C)
+local Z=self.carrier:GetOrientationZ()
+Z.y=0.0
+if runway then
+Z=UTILS.Rotate2D(Z,-self.carrierparam.rwyangle)
+end
+local z=UTILS.VecDot(Z,C)
+local lineup=math.deg(math.atan2(z,x))
+return lineup
+end
+function AIRBOSS:_NozzleArgumentLeft(unit)
+local unitClient=Unit.getByName(unit:GetName())
+local typeName=unit:GetTypeName()
+local nozzlePosL=0
+local burnerPosL=0
+if typeName=="FA-18C_hornet"then
+burnerPosL=unitClient:getDrawArgumentValue(28)
+if burnerPosL<0.2 then
+nozzlePosL=unitClient:getDrawArgumentValue(89)
+else
+nozzlePosL=0
+end
+elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then
+nozzlePosL=unitClient:getDrawArgumentValue(434)
+end
+return nozzlePosL
+end
+function AIRBOSS:_NozzleArgumentRight(unit)
+local unitClient=Unit.getByName(unit:GetName())
+local typeName=unit:GetTypeName()
+local nozzlePosR=0
+local burnerPosR=0
+if typeName=="FA-18C_hornet"then
+burnerPosR=unitClient:getDrawArgumentValue(29)
+if burnerPosR<0.2 then
+nozzlePosR=unitClient:getDrawArgumentValue(90)
+else
+nozzlePosR=0
+end
+elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then
+nozzlePosR=unitClient:getDrawArgumentValue(433)
+end
+return nozzlePosR
+end
function AIRBOSS:_GetAltCarrier(unit)
local h=unit:GetAltitude()-self.carrierparam.deckheight-2
return h
@@ -63827,6 +66029,16 @@ self.landingcoord.y=self.landingcoord.y+2
end
return self.landingcoord
end
+function AIRBOSS:_GetOptLandingCoordinateWIRE()
+self.landingcoord:UpdateFromCoordinate(self:_GetSternCoord())
+local FB=self:GetFinalBearing(false)
+local case=self.case
+if self.carrierparam.wire3 then
+self.landingcoord:Translate(self.carrierparam.wire3+500,FB,true,true)
+end
+self.landingcoord.y=self.landingcoord.y+2
+return self.landingcoord
+end
function AIRBOSS:_GetLandingSpotCoordinate()
self.landingspotcoord:UpdateFromCoordinate(self:_GetSternCoord())
local hdg=self:GetHeading()
@@ -63940,7 +66152,7 @@ theta=math.asin(vdeck*math.sin(alpha)/vwind)
v=vdeck*math.cos(alpha)-vwind*math.cos(theta)
end
local magvar=magnetic and self.magvar or 0
-local intowind=(540+(windto-magvar+math.deg(theta)))%360
+local intowind=self:GetHeadingIntoWind_old(vdeck)
return intowind,v
end
function AIRBOSS:GetBRCintoWind(vdeck)
@@ -64051,6 +66263,9 @@ advice=advice+self.LSOCall.POWER.duration
elseif glideslopeError=25 then
+grade="_LIG_"
elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t<55 then
grade="FAST V/STOL Groove"
elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t<75 then
@@ -64108,7 +66329,7 @@ grade="SLOW V/STOL Groove"
else
grade="LIG"
end
-if t>=16.4 and t<=16.6 then
+if t>=16.49 and t<=16.6 then
grade="_OK_"
end
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and(t>=60.0 and t<=65.0)then
@@ -64120,25 +66341,42 @@ function AIRBOSS:_LSOgrade(playerData)
local function count(base,pattern)
return select(2,string.gsub(base,pattern,""))
end
+local TIG=""
+if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then
+TIG=self:_EvalGrooveTime(playerData)
+end
local GXX,nXX=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.XX)
local GIM,nIM=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IM)
local GIC,nIC=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IC)
local GAR,nAR=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.AR)
+local GIW,nIW=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IW)
local vtol=playerData.actype==AIRBOSS.AircraftCarrier.AV8B
-local G=GXX.." "..GIM.." ".." "..GIC.." "..GAR
-local N=nXX+nIM+nIC+nAR
+local G=GXX.." "..GIM.." ".." "..GIC.." "..GAR.." "..GIW.." "..TIG
+local gradeWithDeviations=GXX.."["..nXX.."] "..GIM.."["..nIM.."] "..GIC.."["..nIC.."] "..GAR.."["..nAR.."] "..GIW.."["..nIW.."]"
+env.info("LSO Grade [with deviation count]: "..gradeWithDeviations)
+local N=nXX+nIM+nIC+nAR+nIW
local nL=count(G,'_')/2
local nS=count(G,'%(')
local nN=N-nS-nL
local Tgroove=playerData.Tgroove
-local TgrooveUnicorn=Tgroove and(Tgroove>=15.0 and Tgroove<=18.99)or false
+local TgrooveUnicorn=Tgroove and(Tgroove>=16.49 and Tgroove<=16.59)or false
local TgrooveVstolUnicorn=Tgroove and(Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false
local grade
local points
-if N==0 and(TgrooveUnicorn or TgrooveVstolUnicorn or playerData.case==3)then
+if N==0 and TgrooveVstolUnicorn then
grade="_OK_"
points=5.0
G="Unicorn"
+end
+if N==0 and TgrooveUnicorn then
+if playerData.wire==3 then
+grade="_OK_"
+points=5.0
+G="Unicorn"
+else
+grade="OK"
+points=4.0
+end
else
if vtol then
local Gb=GXX.." "..GIM
@@ -64184,6 +66422,7 @@ text=text.."# of large deviations _ = "..nL.."\n"
text=text.."# of normal deviations = "..nN.."\n"
text=text.."# of small deviations ( = "..nS.."\n"
self:T2(self.lid..text)
+env.info(text)
if playerData.wop then
if playerData.lig then
grade="WO"
@@ -64226,6 +66465,16 @@ grade="CUT"
points=0.0
end
end
+if playerData.wire==1 and points>1 then
+if points==4 then
+points=3
+grade="(OK)"
+elseif points==3 then
+points=2
+grade="--"
+end
+end
+env.info("Returning: "..grade.." "..points.." "..G)
return grade,points,G
end
function AIRBOSS:_Flightdata2Text(playerData,groovestep)
@@ -64244,20 +66493,24 @@ local step=fdata.Step
local AOA=fdata.AoA
local GSE=fdata.GSE
local LUE=fdata.LUE
+local LUEwire=fdata.LUEwire
+local Lnoz=fdata.LeftNozzle
+local Rnoz=fdata.RightNozzle
local ROL=fdata.Roll
+local GT=fdata.GT
local acaoa=self:_GetAircraftAoA(playerData)
local P=nil
-if step==AIRBOSS.PatternStep.GROOVE_XX and ROL<=4.0 and playerData.case<3 then
-if LUE>self.lue.RIGHT then
-P=underline("AA")
-elseif LUE>self.lue.RightMed then
-P="AA "
-elseif LUE>self.lue.Right then
-P=little("AA")
+if step==AIRBOSS.PatternStep.GROOVE_XX and ROL<=3.5 and playerData.case<3 then
+if LUE>3.2 then
+P=underline("LUL")
+elseif LUE>2.2 then
+P="LUL"
+elseif LUE>1.2 then
+P=little("LUL")
end
end
local O=nil
-if step==AIRBOSS.PatternStep.GROOVE_XX then
+if step==AIRBOSS.PatternStep.GROOVE_XX and playerData.case<3 then
if LUEacaoa.SLOW then
S=underline("SLO")
elseif AOA>acaoa.Slow then
@@ -64294,59 +66550,141 @@ A="LO"
elseif GSEself.lue.RIGHT then
+local DW=nil
+local Rol=nil
+local Noz=nil
+if AIRBOSS.PatternStep.GROOVE_AR and playerData.waveoff==true and playerData.owo==true then
+else
+if LUE>self.lue.RIGHT and step~=AIRBOSS.PatternStep.GROOVE_XX and step~=AIRBOSS.PatternStep.GROOVE_IW then
D=underline("LUL")
-elseif LUE>self.lue.Right then
+elseif LUE>self.lue.Right and step~=AIRBOSS.PatternStep.GROOVE_XX and step~=AIRBOSS.PatternStep.GROOVE_IW then
D="LUL"
-elseif LUE>self.lue._max then
+elseif LUE>self.lue._max and step~=AIRBOSS.PatternStep.GROOVE_XX and step~=AIRBOSS.PatternStep.GROOVE_IW then
D=little("LUL")
-elseif playerData.case<3 then
-if LUE1.2 then
+DW=underline("LL")
+elseif LUEwire>0.4 then
+DW="LL"
+elseif LUEwire>0.25 then
+DW=little("LL")
+elseif LUEwire<-1.17 then
+DW=underline("LR")
+elseif LUEwire<-0.46 then
+DW="LR"
+elseif LUEwire<-0.25 then
+DW=little("LR")
+else
+end
+if ROL>5 and ROL<=10 then
+Rol=little("LRWD")
+elseif ROL>10 and ROL<=15 then
+Rol=("LRWD")
+elseif ROL>15 then
+Rol=underline("LRWD")
+elseif ROL<-5 and ROL>=-10 then
+Rol=little("LLWD")
+elseif ROL<-10 and ROL>=-15 then
+Rol=("LLWD")
+elseif ROL<-15 then
+Rol=underline("LLWD")
+else
+end
+local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET
+local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B
+if hornet then
+if Lnoz>0.6 and Rnoz>0.6 then
+Noz=underline("EG")
+else
+end
+end
+if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then
+local grooveTime=playerData.Tgroove
+if grooveTime>19 or grooveTime<15 then
+GT=""
+end
end
end
local G=""
local n=0
+if stepMod=="XX"then
+if playerData.case<3 then
+if fdata.WrappedUp then
+env.info("Adding WrappedUp deviation.")
+G=G..fdata.WrappedUp
+n=n+1
+end
+if fdata.AngledApch then
+env.info("Adding AngledApch deviation.")
+G=G..fdata.AngledApch
+n=n+1
+end
+if fdata.AFU then
+env.info("Adding AFU deviation.")
+G=G..fdata.AFU
+n=n+1
+end
+end
+end
if fdata.FlyThrough then
G=G..fdata.FlyThrough
end
-if P then
-G=G..P
-n=n
-end
if S then
+env.info("Adding speed deviation.")
G=G..S
n=n+1
end
if A then
+env.info("Adding altitude deviation.")
G=G..A
n=n+1
end
if D then
+env.info("Adding line up deviation.")
G=G..D
n=n+1
end
if fdata.Drift then
+env.info("Adding drift deviation.")
G=G..fdata.Drift
n=n
end
if O then
+env.info("Adding overshoot deviation.")
G=G..O
n=n+1
end
+if DW then
+env.info("Adding landed L/R deviation.")
+G=G..DW
+n=n+1
+end
+if Rol then
+env.info("Adding landed rol deviation.")
+G=G..Rol
+n=n+1
+end
+if Noz then
+env.info("Adding eased guns deviation.")
+G=G..Noz
+n=n+1
+end
+if GT then
+G=G..GT
+n=n+1
+end
local step=self:_GS(step)
step=step:gsub("XX","X")
if G~=""then
@@ -64815,7 +67153,7 @@ if playerData.wire and playerData.wire<=4 then
text=text..string.format(" %d-wire",playerData.wire)
end
if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then
-text=text..string.format("\nTime in the groove %.1f seconds: %s",playerData.Tgroove,self:_EvalGrooveTime(playerData))
+text=text..string.format("\nTime in the groove %.1f seconds.",playerData.Tgroove)
end
end
playerData.lastdebrief=UTILS.DeepCopy(playerData.debrief)
@@ -66412,6 +68750,15 @@ text=string.format("negative, you are not airborne. Request denied!")
end
else
text="affirmative, you can bypass the pattern and are cleared for final approach!"
+playerData.wrappedUpAtWakeLittle=false
+playerData.wrappedUpAtWakeFull=false
+playerData.wrappedUpAtWakeUnderline=false
+playerData.wrappedUpAtStartLittle=false
+playerData.wrappedUpAtStartFull=false
+playerData.wrappedUpAtStartUnderline=false
+playerData.AAatWakeLittle=false
+playerData.AAatWakeFull=false
+playerData.AAatWakeUnderline=false
local lead=self:_GetFlightLead(playerData)
self:_SetPlayerStep(lead,AIRBOSS.PatternStep.EMERGENCY)
for _,sec in pairs(lead.section)do
@@ -66581,7 +68928,7 @@ if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local mycoord=_unit:GetCoordinate()
-local dmax=100
+local dmax=self.maxsectiondistance
local text
if self.NmaxSection==0 then
text=string.format("negative, setting sections is disabled in this mission. You stay alone.")
@@ -67786,6 +70133,7 @@ self.orientation=self.carrier:GetOrientationX()
self.orientlast=self.carrier:GetOrientationX()
self.position=self.carrier:GetCoordinate()
self:__Status(10)
+return self
end
function RECOVERYTANKER:onafterStatus(From,Event,To)
local time=timer.getTime()
@@ -68424,6 +70772,7 @@ self.formation:SetFollowTimeInterval(self.dtFollow)
self.formation:SetFlightModeFormation(self.helo)
self.formation:__Start(delay)
self:__Status(1)
+return self
end
function RESCUEHELO:onafterStatus(From,Event,To)
local time=timer.getTime()
@@ -68632,6 +70981,10 @@ Syria=5,
MarianaIslands=2,
Falklands=12,
SinaiMap=5,
+Kola=15,
+Afghanistan=3,
+Iraq=4.4,
+GermanyCW=0.1,
}
ATIS.ICAOPhraseology={
Caucasus=true,
@@ -68643,85 +70996,89 @@ Syria=true,
MarianaIslands=true,
Falklands=true,
SinaiMap=true,
+Kola=true,
+Afghanistan=true,
+Iraq=true,
+GermanyCW=true,
}
ATIS.Sound={
-ActiveRunway={filename="ActiveRunway.ogg",duration=0.99},
-ActiveRunwayDeparture={filename="ActiveRunwayDeparture.ogg",duration=0.99},
-ActiveRunwayArrival={filename="ActiveRunwayArrival.ogg",duration=0.99},
-AdviceOnInitial={filename="AdviceOnInitial.ogg",duration=3.00},
-Airport={filename="Airport.ogg",duration=0.66},
-Altimeter={filename="Altimeter.ogg",duration=0.68},
-At={filename="At.ogg",duration=0.41},
-CloudBase={filename="CloudBase.ogg",duration=0.82},
-CloudCeiling={filename="CloudCeiling.ogg",duration=0.61},
-CloudsBroken={filename="CloudsBroken.ogg",duration=1.07},
-CloudsFew={filename="CloudsFew.ogg",duration=0.99},
-CloudsNo={filename="CloudsNo.ogg",duration=1.01},
-CloudsNotAvailable={filename="CloudsNotAvailable.ogg",duration=2.35},
-CloudsOvercast={filename="CloudsOvercast.ogg",duration=0.83},
-CloudsScattered={filename="CloudsScattered.ogg",duration=1.18},
-Decimal={filename="Decimal.ogg",duration=0.54},
-DegreesCelsius={filename="DegreesCelsius.ogg",duration=1.27},
-DegreesFahrenheit={filename="DegreesFahrenheit.ogg",duration=1.23},
-DewPoint={filename="DewPoint.ogg",duration=0.65},
-Dust={filename="Dust.ogg",duration=0.54},
-Elevation={filename="Elevation.ogg",duration=0.78},
-EndOfInformation={filename="EndOfInformation.ogg",duration=1.15},
-Feet={filename="Feet.ogg",duration=0.45},
-Fog={filename="Fog.ogg",duration=0.47},
-Gusting={filename="Gusting.ogg",duration=0.55},
-HectoPascal={filename="HectoPascal.ogg",duration=1.15},
-Hundred={filename="Hundred.ogg",duration=0.47},
-InchesOfMercury={filename="InchesOfMercury.ogg",duration=1.16},
-Information={filename="Information.ogg",duration=0.85},
-Kilometers={filename="Kilometers.ogg",duration=0.78},
-Knots={filename="Knots.ogg",duration=0.59},
-Left={filename="Left.ogg",duration=0.54},
-MegaHertz={filename="MegaHertz.ogg",duration=0.87},
-Meters={filename="Meters.ogg",duration=0.59},
-MetersPerSecond={filename="MetersPerSecond.ogg",duration=1.14},
-Miles={filename="Miles.ogg",duration=0.60},
-MillimetersOfMercury={filename="MillimetersOfMercury.ogg",duration=1.53},
-Minus={filename="Minus.ogg",duration=0.64},
-N0={filename="N-0.ogg",duration=0.55},
-N1={filename="N-1.ogg",duration=0.41},
-N2={filename="N-2.ogg",duration=0.37},
-N3={filename="N-3.ogg",duration=0.41},
-N4={filename="N-4.ogg",duration=0.37},
-N5={filename="N-5.ogg",duration=0.43},
-N6={filename="N-6.ogg",duration=0.55},
-N7={filename="N-7.ogg",duration=0.43},
-N8={filename="N-8.ogg",duration=0.38},
-N9={filename="N-9.ogg",duration=0.55},
-NauticalMiles={filename="NauticalMiles.ogg",duration=1.04},
-None={filename="None.ogg",duration=0.43},
-QFE={filename="QFE.ogg",duration=0.63},
-QNH={filename="QNH.ogg",duration=0.71},
-Rain={filename="Rain.ogg",duration=0.41},
-Right={filename="Right.ogg",duration=0.44},
-Snow={filename="Snow.ogg",duration=0.48},
-SnowStorm={filename="SnowStorm.ogg",duration=0.82},
-StatuteMiles={filename="StatuteMiles.ogg",duration=1.15},
-SunriseAt={filename="SunriseAt.ogg",duration=0.92},
-SunsetAt={filename="SunsetAt.ogg",duration=0.95},
-Temperature={filename="Temperature.ogg",duration=0.64},
-Thousand={filename="Thousand.ogg",duration=0.55},
-ThunderStorm={filename="ThunderStorm.ogg",duration=0.81},
-TimeLocal={filename="TimeLocal.ogg",duration=0.90},
-TimeZulu={filename="TimeZulu.ogg",duration=0.86},
-TowerFrequency={filename="TowerFrequency.ogg",duration=1.19},
-Visibilty={filename="Visibility.ogg",duration=0.79},
-WeatherPhenomena={filename="WeatherPhenomena.ogg",duration=1.07},
-WindFrom={filename="WindFrom.ogg",duration=0.60},
+ActiveRunway={filename="ActiveRunway.ogg",duration=0.85},
+ActiveRunwayDeparture={filename="ActiveRunwayDeparture.ogg",duration=1.50},
+ActiveRunwayArrival={filename="ActiveRunwayArrival.ogg",duration=1.38},
+AdviceOnInitial={filename="AdviceOnInitial.ogg",duration=2.98},
+Airport={filename="Airport.ogg",duration=0.55},
+Altimeter={filename="Altimeter.ogg",duration=0.91},
+At={filename="At.ogg",duration=0.32},
+CloudBase={filename="CloudBase.ogg",duration=0.69},
+CloudCeiling={filename="CloudCeiling.ogg",duration=0.53},
+CloudsBroken={filename="CloudsBroken.ogg",duration=0.81},
+CloudsFew={filename="CloudsFew.ogg",duration=0.74},
+CloudsNo={filename="CloudsNo.ogg",duration=0.69},
+CloudsNotAvailable={filename="CloudsNotAvailable.ogg",duration=2.64},
+CloudsOvercast={filename="CloudsOvercast.ogg",duration=0.82},
+CloudsScattered={filename="CloudsScattered.ogg",duration=0.89},
+Decimal={filename="Decimal.ogg",duration=0.71},
+DegreesCelsius={filename="DegreesCelsius.ogg",duration=1.08},
+DegreesFahrenheit={filename="DegreesFahrenheit.ogg",duration=1.07},
+DewPoint={filename="DewPoint.ogg",duration=0.59},
+Dust={filename="Dust.ogg",duration=0.37},
+Elevation={filename="Elevation.ogg",duration=0.92},
+EndOfInformation={filename="EndOfInformation.ogg",duration=1.24},
+Feet={filename="Feet.ogg",duration=0.34},
+Fog={filename="Fog.ogg",duration=0.41},
+Gusting={filename="Gusting.ogg",duration=0.58},
+HectoPascal={filename="HectoPascal.ogg",duration=0.92},
+Hundred={filename="Hundred.ogg",duration=0.53},
ILSFrequency={filename="ILSFrequency.ogg",duration=1.30},
-InnerNDBFrequency={filename="InnerNDBFrequency.ogg",duration=1.56},
-OuterNDBFrequency={filename="OuterNDBFrequency.ogg",duration=1.59},
-RunwayLength={filename="RunwayLength.ogg",duration=0.91},
-VORFrequency={filename="VORFrequency.ogg",duration=1.38},
-TACANChannel={filename="TACANChannel.ogg",duration=0.88},
-PRMGChannel={filename="PRMGChannel.ogg",duration=1.18},
-RSBNChannel={filename="RSBNChannel.ogg",duration=1.14},
-Zulu={filename="Zulu.ogg",duration=0.62},
+InchesOfMercury={filename="InchesOfMercury.ogg",duration=1.26},
+Information={filename="Information.ogg",duration=0.99},
+InnerNDBFrequency={filename="InnerNDBFrequency.ogg",duration=1.69},
+Kilometers={filename="Kilometers.ogg",duration=0.93},
+Knots={filename="Knots.ogg",duration=0.46},
+Left={filename="Left.ogg",duration=0.41},
+MegaHertz={filename="MegaHertz.ogg",duration=0.83},
+Meters={filename="Meters.ogg",duration=0.55},
+MetersPerSecond={filename="MetersPerSecond.ogg",duration=1.03},
+Miles={filename="Miles.ogg",duration=0.44},
+MillimetersOfMercury={filename="MillimetersOfMercury.ogg",duration=1.59},
+Minus={filename="Minus.ogg",duration=0.55},
+N0={filename="N-0.ogg",duration=0.52},
+N1={filename="N-1.ogg",duration=0.35},
+N2={filename="N-2.ogg",duration=0.41},
+N3={filename="N-3.ogg",duration=0.34},
+N4={filename="N-4.ogg",duration=0.37},
+N5={filename="N-5.ogg",duration=0.40},
+N6={filename="N-6.ogg",duration=0.46},
+N7={filename="N-7.ogg",duration=0.52},
+N8={filename="N-8.ogg",duration=0.36},
+N9={filename="N-9.ogg",duration=0.51},
+NauticalMiles={filename="NauticalMiles.ogg",duration=0.93},
+None={filename="None.ogg",duration=0.33},
+OuterNDBFrequency={filename="OuterNDBFrequency.ogg",duration=1.70},
+PRMGChannel={filename="PRMGChannel.ogg",duration=1.27},
+QFE={filename="QFE.ogg",duration=0.90},
+QNH={filename="QNH.ogg",duration=0.94},
+Rain={filename="Rain.ogg",duration=0.35},
+Right={filename="Right.ogg",duration=0.31},
+RSBNChannel={filename="RSBNChannel.ogg",duration=1.26},
+RunwayLength={filename="RunwayLength.ogg",duration=0.81},
+Snow={filename="Snow.ogg",duration=0.40},
+SnowStorm={filename="SnowStorm.ogg",duration=0.73},
+StatuteMiles={filename="StatuteMiles.ogg",duration=0.90},
+SunriseAt={filename="SunriseAt.ogg",duration=0.82},
+SunsetAt={filename="SunsetAt.ogg",duration=0.87},
+TACANChannel={filename="TACANChannel.ogg",duration=0.81},
+Temperature={filename="Temperature.ogg",duration=0.70},
+Thousand={filename="Thousand.ogg",duration=0.58},
+ThunderStorm={filename="ThunderStorm.ogg",duration=0.79},
+TimeLocal={filename="TimeLocal.ogg",duration=0.83},
+TimeZulu={filename="TimeZulu.ogg",duration=0.83},
+TowerFrequency={filename="TowerFrequency.ogg",duration=1.05},
+Visibilty={filename="Visibility.ogg",duration=1.16},
+VORFrequency={filename="VORFrequency.ogg",duration=1.28},
+WeatherPhenomena={filename="WeatherPhenomena.ogg",duration=1.09},
+WindFrom={filename="WindFrom.ogg",duration=0.63},
+Zulu={filename="Zulu.ogg",duration=0.51},
}
ATIS.Messages={
EN=
@@ -68966,7 +71323,7 @@ DELIMITER="Décimal",
}
ATIS.locale="en"
_ATIS={}
-ATIS.version="1.0.0"
+ATIS.version="1.0.1"
function ATIS:New(AirbaseName,Frequency,Modulation)
local self=BASE:Inherit(self,FSM:New())
self.airbasename=AirbaseName
@@ -69497,12 +71854,14 @@ self:T3(string.format("NATO =%s",tostring(NATO)))
local hours=self.gettext:GetEntry("HOURS",self.locale)
local sunrise=coord:GetSunrise()
local SUNRISE="no time"
+local NorthPolar=true
if tostring(sunrise)~="N/S"and tostring(sunrise)~="N/R"then
sunrise=UTILS.Split(sunrise,":")
SUNRISE=string.format("%s%s",sunrise[1],sunrise[2])
if self.useSRS then
SUNRISE=string.format("%s %s %s",sunrise[1],sunrise[2],hours)
end
+NorthPolar=false
end
local sunset=coord:GetSunset()
local SUNSET="no time"
@@ -69512,6 +71871,7 @@ SUNSET=string.format("%s%s",sunset[1],sunset[2])
if self.useSRS then
SUNSET=string.format("%s %s %s",sunset[1],sunset[2],hours)
end
+NorthPolar=false
end
local temperature=coord:GetTemperature(height+5)
local dewpoint=temperature-(100-self.relHumidity)/5
@@ -69521,33 +71881,31 @@ dewpoint=UTILS.CelsiusToFahrenheit(dewpoint)
end
local TEMPERATURE=string.format("%d",math.abs(temperature))
local DEWPOINT=string.format("%d",math.abs(dewpoint))
-local clouds,visibility,turbulence,fog,dust,static=self:GetMissionWeather()
-if fog and fog.thicknessheight+25 then
+dust=true
+visibility=math.min(visibility,dustdens)
end
-if dust and height+25>UTILS.FeetToMeters(1500)then
-dust=nil
-end
-local visibilitymin=visibility
-if fog then
-if fog.visibility0 and fheight>height+25 then
+fog=true
+visibility=math.min(visibility,fvis)
end
end
local VISIBILITY=""
if self.metric then
-local reportedviz=UTILS.Round(visibilitymin/1000)
+local reportedviz=UTILS.Round(visibility/1000)
if reportedviz>10 then
reportedviz=10
end
VISIBILITY=string.format("%d",reportedviz)
else
-local reportedviz=UTILS.Round(UTILS.MetersToSM(visibilitymin))
+local reportedviz=UTILS.Round(UTILS.MetersToSM(visibility))
if reportedviz>10 then
reportedviz=10
end
@@ -69735,7 +72093,7 @@ alltext=alltext..";\n"..subtitle
if not self.zulutimeonly then
local sunrise=self.gettext:GetEntry("SUNRISEAT",self.locale)
subtitle=string.format(sunrise,SUNRISE)
-if not self.useSRS then
+if not self.useSRS and NorthPolar==false then
self:Transmission(self.Sound.SunriseAt,0.5,subtitle)
self.radioqueue:Number2Transmission(SUNRISE,nil,0.2)
self:Transmission(self.Sound.TimeLocal,0.2)
@@ -69743,7 +72101,7 @@ end
alltext=alltext..";\n"..subtitle
local sunset=self.gettext:GetEntry("SUNSETAT",self.locale)
subtitle=string.format(sunset,SUNSET)
-if not self.useSRS then
+if not self.useSRS and NorthPolar==false then
self:Transmission(self.Sound.SunsetAt,0.5,subtitle)
self.radioqueue:Number2Transmission(SUNSET,nil,0.5)
self:Transmission(self.Sound.TimeLocal,0.2)
@@ -70050,7 +72408,6 @@ end
end
end
_RUNACT=subtitle
-alltext=alltext..";\n"..subtitle
if self.rwylength then
local runact=self.airbase:GetActiveRunway(self.runwaym2t)
local length=runact.length
@@ -70407,18 +72764,13 @@ local dust=nil
if weather.enable_dust==true then
dust=weather.dust_density
end
-local fog=nil
-if weather.enable_fog==true then
-fog=weather.fog
-end
self:T("FF weather:")
self:T({clouds=clouds})
self:T({visibility=visibility})
self:T({turbulence=turbulence})
-self:T({fog=fog})
self:T({dust=dust})
self:T({static=static})
-return clouds,visibility,turbulence,fog,dust,static
+return clouds,visibility,turbulence,dust,static
end
function ATIS:_GetThousandsAndHundreds(n)
local N=UTILS.Round(n/1000,1)
@@ -70441,6 +72793,7 @@ Positionable=nil,
HasBeenDropped=false,
PerCrateMass=0,
Stock=nil,
+Stock0=nil,
Mark=nil,
DontShowInMenu=false,
Location=nil,
@@ -70469,6 +72822,7 @@ self.Positionable=Positionable or nil
self.HasBeenDropped=Dropped or false
self.PerCrateMass=PerCrateMass or 0
self.Stock=Stock or nil
+self.Stock0=Stock or nil
self.Mark=nil
self.Subcategory=Subcategory or"Other"
self.DontShowInMenu=DontShowInMenu or false
@@ -70572,6 +72926,20 @@ else
return-1
end
end
+function CTLD_CARGO:GetStock0()
+if self.Stock0 then
+return self.Stock0
+else
+return-1
+end
+end
+function CTLD_CARGO:GetRelativeStock()
+if self.Stock and self.Stock0 then
+return math.floor((self.Stock/self.Stock0)*100)
+else
+return-1
+end
+end
function CTLD_CARGO:AddStock(Number)
if self.Stock then
local number=Number or 1
@@ -70807,8 +73175,13 @@ ChinookTroopCircleRadius=5,
TroopUnloadDistGround=5,
TroopUnloadDistGroundHerc=25,
TroopUnloadDistGroundHook=15,
+TroopUnloadDistHoverHook=5,
TroopUnloadDistHover=1.5,
UserSetGroup=nil,
+LoadedGroupsTable={},
+keeploadtable=true,
+allowCATransport=false,
+VehicleMoveFormation=AI.Task.VehicleFormation.VEE,
}
CTLD.RadioModulation={
AM=0,
@@ -70835,6 +73208,7 @@ CTLD.UnitTypeCapabilities={
["Mi-24V"]={type="Mi-24V",crates=true,troops=true,cratelimit=2,trooplimit=8,length=18,cargoweightlimit=700},
["Hercules"]={type="Hercules",crates=true,troops=true,cratelimit=7,trooplimit=64,length=25,cargoweightlimit=19000},
["UH-60L"]={type="UH-60L",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500},
+["UH-60L_DAP"]={type="UH-60L_DAP",crates=false,troops=true,cratelimit=0,trooplimit=2,length=16,cargoweightlimit=500},
["MH-60R"]={type="MH-60R",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500},
["SH-60B"]={type="SH-60B",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500},
["AH-64D_BLK_II"]={type="AH-64D_BLK_II",crates=false,troops=true,cratelimit=0,trooplimit=2,length=17,cargoweightlimit=200},
@@ -70842,8 +73216,15 @@ CTLD.UnitTypeCapabilities={
["OH-6A"]={type="OH-6A",crates=false,troops=true,cratelimit=0,trooplimit=4,length=7,cargoweightlimit=550},
["OH58D"]={type="OH58D",crates=false,troops=false,cratelimit=0,trooplimit=0,length=14,cargoweightlimit=400},
["CH-47Fbl1"]={type="CH-47Fbl1",crates=true,troops=true,cratelimit=4,trooplimit=31,length=20,cargoweightlimit=10800},
+["MosquitoFBMkVI"]={type="MosquitoFBMkVI",crates=true,troops=false,cratelimit=2,trooplimit=0,length=13,cargoweightlimit=1800},
+["M 818"]={type="M 818",crates=true,troops=true,cratelimit=4,trooplimit=12,length=9,cargoweightlimit=4500},
}
-CTLD.version="1.1.18"
+CTLD.FixedWingTypes={
+["Hercules"]="Hercules",
+["Bronco"]="Bronco",
+["Mosquito"]="Mosquito",
+}
+CTLD.version="1.3.38"
function CTLD:New(Coalition,Prefixes,Alias)
local self=BASE:Inherit(self,FSM:New())
BASE:T({Coalition,Prefixes,Alias})
@@ -70890,7 +73271,10 @@ self:AddTransition("*","CratesBuild","*")
self:AddTransition("*","CratesRepaired","*")
self:AddTransition("*","CratesBuildStarted","*")
self:AddTransition("*","CratesRepairStarted","*")
+self:AddTransition("*","CratesPacked","*")
+self:AddTransition("*","HelicopterLost","*")
self:AddTransition("*","Load","*")
+self:AddTransition("*","Loaded","*")
self:AddTransition("*","Save","*")
self:AddTransition("*","Stop","Stopped")
self.PilotGroups={}
@@ -70943,10 +73327,13 @@ self.smokedistance=2000
self.movetroopstowpzone=true
self.movetroopsdistance=5000
self.troopdropzoneradius=100
+self.VehicleMoveFormation=AI.Task.VehicleFormation.VEE
self.enableHercules=false
-self.HercMinAngels=165
-self.HercMaxAngels=2000
-self.HercMaxSpeed=77
+self.enableFixedWing=false
+self.FixedMinAngels=165
+self.FixedMaxAngels=2000
+self.FixedMaxSpeed=77
+self.validateAndRepositionUnits=false
self.suppressmessages=false
self.repairtime=300
self.buildtime=300
@@ -70960,9 +73347,13 @@ self.enableLoadSave=false
self.filepath=nil
self.saveinterval=600
self.eventoninject=true
+self.keeploadtable=true
+self.LoadedGroupsTable={}
self.usesubcats=false
self.subcats={}
self.subcatsTroop={}
+self.showstockinmenuitems=false
+self.onestepmenu=false
self.nobuildinloadzones=true
self.movecratesbeforebuild=true
self.surfacetypes={land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER}
@@ -70979,6 +73370,8 @@ self.FlareColor=FLARECOLOR.Red
for i=1,100 do
math.random()
end
+self.allowCATransport=false
+self.CATransportSet=nil
self:_GenerateVHFrequencies()
self:_GenerateUHFrequencies()
self:_GenerateFMFrequencies()
@@ -71001,6 +73394,11 @@ capabilities.cargoweightlimit=0
end
return capabilities
end
+function CTLD:AllowCATransport(OnOff,ClientSet)
+self.allowCATransport=OnOff
+self.CATransportSet=ClientSet
+return self
+end
function CTLD:_GenerateUHFrequencies()
self:T(self.lid.." _GenerateUHFrequencies")
self.FreeUHFFrequencies={}
@@ -71052,14 +73450,33 @@ local unitname=event.IniUnitName or"none"
self.Loaded_Cargo[unitname]=nil
self:_RefreshF10Menus()
end
-if self:IsHercules(_unit)and self.enableHercules then
+if self:IsFixedWing(_unit)and self.enableFixedWing then
+local unitname=event.IniUnitName or"none"
+self.Loaded_Cargo[unitname]=nil
+self:_RefreshF10Menus()
+end
+if _unit:IsGround()and self.allowCATransport then
local unitname=event.IniUnitName or"none"
self.Loaded_Cargo[unitname]=nil
self:_RefreshF10Menus()
end
return
+elseif event.id==EVENTS.Land or event.id==EVENTS.Takeoff then
+local unitname=event.IniUnitName
+if self.CtldUnits[unitname]then
+local _group=event.IniGroup
+local _unit=event.IniUnit
+self:_RefreshLoadCratesMenu(_group,_unit)
+if self:IsFixedWing(_unit)and self.enableFixedWing then
+self:_RefreshDropCratesMenu(_group,_unit)
+end
+end
elseif event.id==EVENTS.PlayerLeaveUnit or event.id==EVENTS.UnitLost then
local unitname=event.IniUnitName or"none"
+if self.CtldUnits[unitname]then
+local lostcargo=UTILS.DeepCopy(self.Loaded_Cargo[unitname]or{})
+self:__HelicopterLost(1,unitname,lostcargo)
+end
self.CtldUnits[unitname]=nil
self.Loaded_Cargo[unitname]=nil
self.MenusDone[unitname]=nil
@@ -71160,6 +73577,17 @@ end
end
return nil
end
+function CTLD:AddAllowedFixedWingType(typename)
+if type(typename)=="string"then
+self.FixedWingTypes[typename]=typename
+elseif typename and typename.ClassName and typename:IsInstanceOf("UNIT")then
+local TypeName=typename:GetTypeName()or"none"
+self.FixedWingTypes[TypeName]=TypeName
+else
+self:E(self.lid.."No valid typename or no UNIT handed!")
+end
+return self
+end
function CTLD:PreloadTroops(Unit,Troopname)
self:T(self.lid.." PreloadTroops")
local name=Troopname or"Unknown"
@@ -71291,7 +73719,8 @@ self:T({cargotype=loadcargotype})
loaded.Troopsloaded=loaded.Troopsloaded+troopsize
table.insert(loaded.Cargo,loadcargotype)
self.Loaded_Cargo[unitname]=loaded
-self:_SendMessage("Troops boarded!",10,false,Group)
+self:_SendMessage(string.format("%s boarded!",cgoname),10,false,Group)
+self:_RefreshDropTroopsMenu(Group,Unit)
self:__TroopsPickedUp(1,Group,Unit,Cargotype)
self:_UpdateUnitCargoMass(Unit)
Cargotype:RemoveStock()
@@ -71391,6 +73820,7 @@ function CTLD:_ExtractTroops(Group,Unit)
self:T(self.lid.." _ExtractTroops")
local grounded=not self:IsUnitInAir(Unit)
local hoverload=self:CanHoverLoad(Unit)
+local hassecondaries=false
if not grounded and not hoverload then
self:_SendMessage("You need to land or hover in position to load!",10,false,Group)
if not self.debug then return self end
@@ -71475,10 +73905,12 @@ local running=math.floor(nearestDistance/4)+20
loaded.Troopsloaded=loaded.Troopsloaded+troopsize
table.insert(loaded.Cargo,loadcargotype)
self.Loaded_Cargo[unitname]=loaded
-self:ScheduleOnce(running,self._SendMessage,self,"Troops boarded!",10,false,Group)
-self:_SendMessage("Troops boarding!",10,false,Group)
+self:ScheduleOnce(running,self._SendMessage,self,string.format("%s boarded!",Cargotype.Name),10,false,Group)
+self:_SendMessage(string.format("%s boarding!",Cargotype.Name),10,false,Group)
+self:_RefreshDropTroopsMenu(Group,Unit)
self:_UpdateUnitCargoMass(Unit)
-self:__TroopsExtracted(running,Group,Unit,nearestGroup)
+local groupname=nearestGroup:GetName()
+self:__TroopsExtracted(running,Group,Unit,nearestGroup,groupname)
local coord=Unit:GetCoordinate()or Group:GetCoordinate()
local Point
if coord then
@@ -71489,7 +73921,7 @@ if Point then
nearestGroup:RouteToVec2(Point,5)
end
end
-local hassecondaries=false
+hassecondaries=false
if type(Cargotype.Templates)=="table"and Cargotype.Templates[2]then
for _,_key in pairs(Cargotype.Templates)do
table.insert(secondarygroups,_key)
@@ -71568,8 +74000,9 @@ if numbernearby>=canloadcratesno and not drop then
self:_SendMessage("There are enough crates nearby already! Take care of those first!",10,false,Group)
return self
end
-local IsHerc=self:IsHercules(Unit)
+local IsHerc=self:IsFixedWing(Unit)
local IsHook=self:IsHook(Unit)
+local IsTruck=Unit:IsGround()
local cargotype=Cargo
local number=number or cargotype:GetCratesNeeded()
local cratesneeded=cargotype:GetCratesNeeded()
@@ -71590,7 +74023,7 @@ local cratedistance=0
local rheading=0
local angleOffNose=0
local addon=0
-if IsHerc or IsHook then
+if IsHerc or IsHook or IsTruck then
addon=180
end
heading=(heading+addon)%360
@@ -71607,7 +74040,11 @@ local cratealias=string.format("%s-%s-%d",cratename,cratetemplate,math.random(1,
if not self.placeCratesAhead or drop==true then
cratedistance=(i-1)*2.5+capabilities.length
if cratedistance>self.CrateDistance then cratedistance=self.CrateDistance end
+if self:IsUnitInAir(Unit)and self:IsFixedWing(Unit)then
+rheading=math.random(20,60)
+else
rheading=UTILS.RandomGaussian(0,30,-90,90,100)
+end
rheading=math.fmod((heading+rheading),360)
cratecoord=position:Translate(cratedistance,rheading)
else
@@ -71696,8 +74133,10 @@ local text=string.format("Crates for %s have been positioned near you!",cratenam
if drop then
text=string.format("Crates for %s have been dropped!",cratename)
self:__CratesDropped(1,Group,Unit,droppedcargo)
-end
+else
self:_SendMessage(text,10,false,Group)
+end
+self:_RefreshLoadCratesMenu(Group,Unit)
return self
end
function CTLD:InjectStatics(Zone,Cargo,RandomCoord,FromLoad)
@@ -71802,24 +74241,25 @@ self:T(self.lid.." _RemoveCratesNearby")
local finddist=self.CrateDistance or 35
local crates,number=self:_FindCratesNearby(_group,_unit,finddist,true,true)
if number>0 then
+local removedIDs={}
local text=REPORT:New("Removing Crates Found Nearby:")
text:Add("------------------------------------------------------------")
for _,_entry in pairs(crates)do
local entry=_entry
-local name=entry:GetName()
-local dropped=entry:WasDropped()
-if dropped then
+local name=entry:GetName()or"none"
text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass))
-else
-text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass))
-end
+if entry:GetPositionable()then
entry:GetPositionable():Destroy(false)
end
+table.insert(removedIDs,entry:GetID())
+end
if text:GetCount()==1 then
text:Add(" N O N E")
end
text:Add("------------------------------------------------------------")
self:_SendMessage(text:Text(),30,true,_group)
+self:_CleanupTrackedCrates(removedIDs)
+self:_RefreshLoadCratesMenu(_group,_unit)
else
self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group)
end
@@ -71862,21 +74302,26 @@ local IsHook=self:IsHook(_unit)
if not _ignoreweight then
maxloadable=self:_GetMaxLoadableMass(_unit)
end
-self:T2(self.lid.." Max loadable mass: "..maxloadable)
+self:T(self.lid.." Max loadable mass: "..maxloadable)
for _,_cargoobject in pairs(existingcrates)do
local cargo=_cargoobject
local static=cargo:GetPositionable()
local weight=cargo:GetMass()
local staticid=cargo:GetID()
-self:T2(self.lid.." Found cargo mass: "..weight)
+self:T(self.lid.." Found cargo mass: "..weight)
if static and static:IsAlive()then
local restricthooktononstatics=self.enableChinookGCLoading and IsHook
+self:T(self.lid.." restricthooktononstatics: "..tostring(restricthooktononstatics))
local cargoisstatic=cargo:GetType()==CTLD_CARGO.Enum.STATIC and true or false
+self:T(self.lid.." Cargo is static: "..tostring(cargoisstatic))
local restricted=cargoisstatic and restricthooktononstatics
+self:T(self.lid.." Loading restricted: "..tostring(restricted))
local staticpos=static:GetCoordinate()
local cando=cargo:UnitCanCarry(_unit)
if ignoretype==true then cando=true end
+self:T(self.lid.." Unit can carry: "..tostring(cando))
local distance=self:_GetDistance(location,staticpos)
+self:T(self.lid..string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable))
if distance<=finddist and(weight<=maxloadable or _ignoreweight)and restricted==false and cando==true then
index=index+1
table.insert(found,staticid,cargo)
@@ -71909,12 +74354,10 @@ elseif not grounded and not canhoverload then
self:_SendMessage("Land or hover over the crates to pick them up!",10,false,Group)
else
local numberonboard=0
-local massonboard=0
local loaded={}
if self.Loaded_Cargo[unitname]then
loaded=self.Loaded_Cargo[unitname]
numberonboard=loaded.Cratesloaded or 0
-massonboard=self:_GetUnitCargoMass(Unit)
else
loaded={}
loaded.Troopsloaded=0
@@ -71935,23 +74378,24 @@ return self
else
local capacity=cratelimit-numberonboard
local crateidsloaded={}
-local loops=0
-while loaded.Cratesloadedcrateind and _crate.Positionable~=nil then
-crateind=_crate:GetID()
-end
-else
-if not _crate:HasMoved()and not _crate:WasDropped()and _crate:GetID()>crateind then
-crateind=_crate:GetID()
+local crateMap={}
+for _,cObj in pairs(nearcrates)do
+if not cObj:HasMoved()or self.allowcratepickupagain then
+local cName=cObj:GetName()or"Unknown"
+crateMap[cName]=crateMap[cName]or{}
+table.insert(crateMap[cName],cObj)
end
end
-end
-if crateind>0 then
-local crate=nearcrates[crateind]
+for cName,crateList in pairs(crateMap)do
+if capacity<=0 then break end
+table.sort(crateList,function(a,b)return a:GetID()>b:GetID()end)
+local needed=crateList[1]:GetCratesNeeded()or 1
+local totalFound=#crateList
+local loadedHere=0
+while loaded.Cratesloaded0 then
+local fullSets=math.floor(loadedHere/needed)
+local leftover=loadedHere%needed
+if needed>1 then
+if fullSets>0 and leftover==0 then
+self:_SendMessage(string.format("Loaded %d %s.",fullSets,cName),10,false,Group)
+elseif fullSets>0 and leftover>0 then
+self:_SendMessage(string.format("Loaded %d %s(s), with %d leftover crate(s).",fullSets,cName,leftover),10,false,Group)
+else
+self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s.",loadedHere,needed,cName),15,false,Group)
+end
+else
+self:_SendMessage(string.format("Loaded %d %s(s).",loadedHere,cName),10,false,Group)
+end
end
end
self.Loaded_Cargo[unitname]=loaded
self:_UpdateUnitCargoMass(Unit)
+self:_RefreshDropCratesMenu(Group,Unit)
+self:_RefreshLoadCratesMenu(Group,Unit)
self:_CleanupTrackedCrates(crateidsloaded)
+self:__CratesPickedUp(1,Group,Unit,loaded.Cargo)
end
end
return self
@@ -72069,18 +74533,25 @@ end
report:Add("------------------------------------------------------------")
report:Add(" -- CRATES --")
local cratecount=0
+local accumCrates={}
for _,_cargo in pairs(cargotable or{})do
local cargo=_cargo
local type=cargo:GetType()
if(type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and type~=CTLD_CARGO.Enum.GCLOADABLE)and(not cargo:WasDropped()or self.allowcratepickupagain)then
-report:Add(string.format("Crate: %s size 1",cargo:GetName()))
-cratecount=cratecount+1
+local cName=cargo:GetName()
+local needed=cargo:GetCratesNeeded()or 1
+accumCrates[cName]=accumCrates[cName]or{count=0,needed=needed}
+accumCrates[cName].count=accumCrates[cName].count+1
end
if type==CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped()then
report:Add(string.format("GC loaded Crate: %s size 1",cargo:GetName()))
cratecount=cratecount+1
end
end
+for cName,data in pairs(accumCrates)do
+cratecount=cratecount+data.count
+report:Add(string.format("Crate: %s %d/%d",cName,data.count,data.needed))
+end
if cratecount==0 then
report:Add(" N O N E")
end
@@ -72176,15 +74647,20 @@ self:_SendMessage(string.format("Nothing in stock!"),10,false,Group)
end
return self
end
-function CTLD:IsHercules(Unit)
-if Unit:GetTypeName()=="Hercules"or string.find(Unit:GetTypeName(),"Bronco")then
+function CTLD:IsFixedWing(Unit)
+local typename=Unit:GetTypeName()or"none"
+for _,_name in pairs(self.FixedWingTypes or{})do
+if _name and(typename==_name or string.find(typename,_name,1,true))then
return true
-else
+end
+end
return false
end
-end
function CTLD:IsHook(Unit)
-if Unit and string.find(Unit:GetTypeName(),"CH.47")then
+if not Unit then return false end
+local typeName=Unit:GetTypeName()
+if not typeName then return false end
+if string.find(typeName,"CH.47")then
return true
else
return false
@@ -72225,7 +74701,7 @@ if inzone then
droppingatbase=true
end
local hoverunload=self:IsCorrectHover(Unit)
-local IsHerc=self:IsHercules(Unit)
+local IsHerc=self:IsFixedWing(Unit)
local IsHook=self:IsHook(Unit)
if IsHerc and(not IsHook)then
hoverunload=self:IsCorrectFlightParameters(Unit)
@@ -72258,7 +74734,12 @@ local Angle=(heading+270)%360
if IsHerc or IsHook then Angle=(heading+180)%360 end
local offset=hoverunload and self.TroopUnloadDistHover or self.TroopUnloadDistGround
if IsHerc then offset=self.TroopUnloadDistGroundHerc or 25 end
-if IsHook then offset=self.TroopUnloadDistGroundHook or 15 end
+if IsHook then
+offset=self.TroopUnloadDistGroundHook or 15
+if hoverunload and self.TroopUnloadDistHoverHook then
+offset=self.TroopUnloadDistHoverHook or 5
+end
+end
randomcoord:Translate(offset,Angle,nil,true)
end
local tempcount=0
@@ -72273,6 +74754,8 @@ local Positions=self:_GetUnitPositions(randomcoord,rad,heading,_template)
self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
:InitDelayOff()
:InitSetUnitAbsolutePositions(Positions)
+:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
+:OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end)
:SpawnFromVec2(randomcoord:GetVec2())
self:__TroopsDeployed(1,Group,Unit,self.DroppedTroops[self.TroopCounter],type)
end
@@ -72319,6 +74802,7 @@ end
end
self.Loaded_Cargo[unitname]=nil
self.Loaded_Cargo[unitname]=loaded
+self:_RefreshDropTroopsMenu(Group,Unit)
self:_UpdateUnitCargoMass(Unit)
else
if IsHerc then
@@ -72345,7 +74829,7 @@ self:_SendMessage("You need to open the door(s) to drop cargo!",10,false,Group)
if not self.debug then return self end
end
local hoverunload=self:IsCorrectHover(Unit)
-local IsHerc=self:IsHercules(Unit)
+local IsHerc=self:IsFixedWing(Unit)
local IsHook=self:IsHook(Unit)
if IsHerc and(not IsHook)then
hoverunload=self:IsCorrectFlightParameters(Unit)
@@ -72355,6 +74839,8 @@ local unitname=Unit:GetName()
if self.Loaded_Cargo[unitname]and(grounded or hoverunload)then
local loadedcargo=self.Loaded_Cargo[unitname]or{}
local cargotable=loadedcargo.Cargo
+local droppedCount={}
+local neededMap={}
for _,_cargo in pairs(cargotable)do
local cargo=_cargo
local type=cargo:GetType()
@@ -72362,6 +74848,27 @@ if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and type~=CT
self:_GetCrates(Group,Unit,cargo,1,true)
cargo:SetWasDropped(true)
cargo:SetHasMoved(true)
+local cname=cargo:GetName()or"Unknown"
+droppedCount[cname]=(droppedCount[cname]or 0)+1
+if not neededMap[cname]then
+neededMap[cname]=cargo:GetCratesNeeded()or 1
+end
+end
+end
+for cname,count in pairs(droppedCount)do
+local needed=neededMap[cname]or 1
+if needed>1 then
+local full=math.floor(count/needed)
+local left=count%needed
+if full>0 and left==0 then
+self:_SendMessage(string.format("Dropped %d %s.",full,cname),10,false,Group)
+elseif full>0 and left>0 then
+self:_SendMessage(string.format("Dropped %d %s(s), with %d leftover crate(s).",full,cname,left),10,false,Group)
+else
+self:_SendMessage(string.format("Dropped %d/%d crate(s) of %s.",count,needed,cname),15,false,Group)
+end
+else
+self:_SendMessage(string.format("Dropped %d %s(s).",count,cname),10,false,Group)
end
end
local loaded={}
@@ -72384,6 +74891,7 @@ end
self.Loaded_Cargo[unitname]=nil
self.Loaded_Cargo[unitname]=loaded
self:_UpdateUnitCargoMass(Unit)
+self:_RefreshDropCratesMenu(Group,Unit)
else
if IsHerc then
self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group)
@@ -72393,9 +74901,9 @@ end
end
return self
end
-function CTLD:_BuildCrates(Group,Unit,Engineering)
+function CTLD:_BuildCrates(Group,Unit,Engineering,MultiDrop)
self:T(self.lid.." _BuildCrates")
-if self:IsHercules(Unit)and self.enableHercules and not Engineering then
+if self:IsFixedWing(Unit)and self.enableFixedWing and not Engineering then
local speed=Unit:GetVelocityKMH()
if speed>1 then
self:_SendMessage("You need to land / stop to build something, Pilot!",10,false,Group)
@@ -72478,12 +74986,13 @@ local build=_build
if build.CanBuild then
self:_CleanUpCrates(crates,build,number)
if self.buildtime and self.buildtime>0 then
-local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate())
+local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop)
buildtimer:Start(self.buildtime)
self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group)
-self:__CratesBuildStarted(1,Group,Unit)
+self:__CratesBuildStarted(1,Group,Unit,build.Name)
+self:_RefreshDropTroopsMenu(Group,Unit)
else
-self:_BuildObjectFromCrates(Group,Unit,build)
+self:_BuildObjectFromCrates(Group,Unit,build,false,nil,MultiDrop)
end
end
end
@@ -72504,13 +75013,16 @@ for _,_entry in pairs(self.Cargo_Crates)do
if(_entry.Templates[1]==_Template.GroupName)then
_Group:Destroy()
self:_GetCrates(Group,Unit,_entry,nil,false,true)
-return self
+self:_RefreshLoadCratesMenu(Group,Unit)
+self:__CratesPacked(1,Group,Unit,_entry)
+return true
end
end
end
end
end
-return self
+self:_SendMessage("Nothing to pack at this distance pilot!",10,false,Group)
+return false
end
function CTLD:_RepairCrates(Group,Unit,Engineering)
self:T(self.lid.." _RepairCrates")
@@ -72583,7 +75095,7 @@ if not Engineering then self:_SendMessage(string.format("No crates within %d met
end
return self
end
-function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation)
+function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,MultiDrop)
self:T(self.lid.." _BuildObjectFromCrates")
if Group and Group:IsAlive()or(RepairLocation and not Repair)then
local name=Build.Name
@@ -72604,6 +75116,9 @@ else
zone=ZONE_GROUP:New(string.format("Unload zone-%d",math.random(1,10000)),Group,100)
end
local randomcoord=Build.Coord or zone:GetRandomCoordinate(35):GetVec2()
+if MultiDrop and(not Repair)and canmove then
+local randomcoord=zone:GetRandomCoordinate(35):GetVec2()
+end
if Repair then
randomcoord=RepairLocation:GetVec2()
end
@@ -72613,10 +75128,14 @@ local alias=string.format("%s-%d",_template,math.random(1,100000))
if canmove then
self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
:InitDelayOff()
+:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
+:OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end)
:SpawnFromVec2(randomcoord)
else
self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
:InitDelayOff()
+:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
+:OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end)
:SpawnFromVec2(randomcoord)
end
if Repair then
@@ -72630,20 +75149,28 @@ self:T(self.lid.."Group KIA while building!")
end
return self
end
+function CTLD:_GetVehicleFormation()
+local VehicleMoveFormation=self.VehicleMoveFormation or AI.Task.VehicleFormation.VEE
+if type(self.VehicleMoveFormation)=="table"then
+VehicleMoveFormation=self.VehicleMoveFormation[math.random(1,#self.VehicleMoveFormation)]
+end
+return VehicleMoveFormation
+end
function CTLD:_MoveGroupToZone(Group)
self:T(self.lid.." _MoveGroupToZone")
local groupname=Group:GetName()or"none"
local groupcoord=Group:GetCoordinate()
local outcome,name,zone,distance=self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE)
+self:T({canmove=outcome,name=name,zone=zone,dist=distance,max=self.movetroopsdistance})
if(distance<=self.movetroopsdistance)and outcome==true and zone~=nil then
local groupname=Group:GetName()
local zonecoord=zone:GetRandomCoordinate(20,125)
-local coordinate=zonecoord:GetVec2()
+local formation=self:_GetVehicleFormation()
Group:SetAIOn()
Group:OptionAlarmStateAuto()
Group:OptionDisperseOnAttack(30)
-Group:OptionROEOpenFirePossible()
-Group:RouteToVec2(coordinate,5)
+Group:OptionROEOpenFire()
+Group:RouteGroundTo(zonecoord,25,formation)
end
return self
end
@@ -72673,25 +75200,86 @@ end
self:_CleanupTrackedCrates(destIDs)
return self
end
+function CTLD:_DropAndBuild(Group,Unit)
+if self.nobuildinloadzones then
+if self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)then
+self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group)
+return self
+end
+end
+self:_UnloadCrates(Group,Unit)
+timer.scheduleFunction(function()self:_BuildCrates(Group,Unit,false,true)end,{},timer.getTime()+1)
+end
+function CTLD:_DropSingleAndBuild(Group,Unit,setIndex)
+if self.nobuildinloadzones then
+if self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)then
+self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group)
+return self
+end
+end
+self:_UnloadSingleCrateSet(Group,Unit,setIndex)
+timer.scheduleFunction(function()self:_BuildCrates(Group,Unit,false)end,{},timer.getTime()+1)
+end
+function CTLD:_PackAndLoad(Group,Unit)
+if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then
+self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group)
+return self
+end
+if not self:_PackCratesNearby(Group,Unit)then
+return self
+end
+timer.scheduleFunction(function()self:_LoadCratesNearby(Group,Unit)end,{},timer.getTime()+1)
+return self
+end
+function CTLD:_PackAndRemove(Group,Unit)
+if not self:_PackCratesNearby(Group,Unit)then
+return self
+end
+timer.scheduleFunction(function()self:_RemoveCratesNearby(Group,Unit)end,{},timer.getTime()+1)
+return self
+end
+function CTLD:_GetAndLoad(Group,Unit,cargoObj)
+if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then
+self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group)
+return self
+end
+self:_GetCrates(Group,Unit,cargoObj)
+timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name)end,{},timer.getTime()+1)
+end
+function CTLD:_GetAllAndLoad(Group,Unit)
+if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then
+self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group)
+return self
+end
+timer.scheduleFunction(function()self:_LoadCratesNearby(Group,Unit)end,{},timer.getTime()+1)
+end
function CTLD:_RefreshF10Menus()
self:T(self.lid.." _RefreshF10Menus")
+self.onestepmenu=self.onestepmenu or false
local PlayerSet=self.PilotGroups
local PlayerTable=PlayerSet:GetSetObjects()
local _UnitList={}
-for _key,_group in pairs(PlayerTable)do
-local _unit=_group:GetFirstUnitAlive()
-if _unit then
-if _unit:IsAlive()and _unit:IsPlayer()then
-if _unit:IsHelicopter()or(self:IsHercules(_unit)and self.enableHercules)then
-local unitName=_unit:GetName()
-_UnitList[unitName]=unitName
-else
-local unitName=_unit:GetName()
-_UnitList[unitName]=nil
+for _,groupObj in pairs(PlayerTable)do
+local firstUnit=groupObj:GetFirstUnitAlive()
+if firstUnit then
+if firstUnit:IsPlayer()then
+if firstUnit:IsHelicopter()or(self.enableFixedWing and self:IsFixedWing(firstUnit))then
+local _unit=firstUnit:GetName()
+_UnitList[_unit]=_unit
end
end
end
end
+if self.allowCATransport and self.CATransportSet then
+for _,_clientobj in pairs(self.CATransportSet.Set)do
+local client=_clientobj
+if client:IsGround()then
+local cname=client:GetName()
+self:T(self.lid.."Adding: "..cname)
+_UnitList[cname]=cname
+end
+end
+end
self.CtldUnits=_UnitList
if self.usesubcats then
for _id,_cargo in pairs(self.Cargo_Crates)do
@@ -72716,156 +75304,237 @@ end
local menucount=0
local menus={}
for _,_unitName in pairs(self.CtldUnits)do
-if not self.MenusDone[_unitName]then
+if(not self.MenusDone[_unitName])or(self.showstockinmenuitems==true)then
+self:T(self.lid.."Menu not done yet for ".._unitName)
local _unit=UNIT:FindByName(_unitName)
-if _unit then
+if not _unit and self.allowCATransport then
+_unit=CLIENT:FindByName(_unitName)
+end
+if _unit and _unit:IsAlive()then
local _group=_unit:GetGroup()
if _group then
-local unittype=_unit:GetTypeName()
+self:T(self.lid.."Unit and Group exist")
local capabilities=self:_GetUnitCapabilities(_unit)
local cantroops=capabilities.troops
local cancrates=capabilities.crates
+local unittype=_unit:GetTypeName()
local isHook=self:IsHook(_unit)
local nohookswitch=true
-local topmenu=MENU_GROUP:New(_group,"CTLD",nil)
+if _group.CTLDTopmenu then
+_group.CTLDTopmenu:Remove()
+_group.CTLDTopmenu=nil
+end
local toptroops=nil
local topcrates=nil
+local topmenu=MENU_GROUP:New(_group,"CTLD",nil)
+_group.CTLDTopmenu=topmenu
if cantroops then
-toptroops=MENU_GROUP:New(_group,"Manage Troops",topmenu)
-end
-if cancrates then
-topcrates=MENU_GROUP:New(_group,"Manage Crates",topmenu)
-end
-local listmenu=MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu,self._ListCargo,self,_group,_unit)
-local invtry=MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu,self._ListInventory,self,_group,_unit)
-local rbcns=MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu,self._ListRadioBeacons,self,_group,_unit)
-local smoketopmenu=MENU_GROUP:New(_group,"Smokes, Flares, Beacons",topmenu)
-local smokemenu=MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",smoketopmenu,self.SmokeZoneNearBy,self,_unit,false)
-local smokeself=MENU_GROUP:New(_group,"Drop smoke now",smoketopmenu)
-local smokeselfred=MENU_GROUP_COMMAND:New(_group,"Red smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Red)
-local smokeselfblue=MENU_GROUP_COMMAND:New(_group,"Blue smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Blue)
-local smokeselfgreen=MENU_GROUP_COMMAND:New(_group,"Green smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Green)
-local smokeselforange=MENU_GROUP_COMMAND:New(_group,"Orange smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Orange)
-local smokeselfwhite=MENU_GROUP_COMMAND:New(_group,"White smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.White)
-local flaremenu=MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",smoketopmenu,self.SmokeZoneNearBy,self,_unit,true)
-local flareself=MENU_GROUP_COMMAND:New(_group,"Fire flare now",smoketopmenu,self.SmokePositionNow,self,_unit,true)
-local beaconself=MENU_GROUP_COMMAND:New(_group,"Drop beacon now",smoketopmenu,self.DropBeaconNow,self,_unit):Refresh()
-if cantroops then
+local toptroops=MENU_GROUP:New(_group,"Manage Troops",topmenu)
local troopsmenu=MENU_GROUP:New(_group,"Load troops",toptroops)
+_group.MyTopTroopsMenu=toptroops
if self.usesubcats then
local subcatmenus={}
-for _name,_entry in pairs(self.subcatsTroop)do
-subcatmenus[_name]=MENU_GROUP:New(_group,_name,troopsmenu)
+for catName,_ in pairs(self.subcatsTroop)do
+subcatmenus[catName]=MENU_GROUP:New(_group,catName,troopsmenu)
end
-for _,_entry in pairs(self.Cargo_Troops)do
-local entry=_entry
-local subcat=entry.Subcategory
-local noshow=entry.DontShowInMenu
-if not noshow then
-menucount=menucount+1
-menus[menucount]=MENU_GROUP_COMMAND:New(_group,entry.Name,subcatmenus[subcat],self._LoadTroops,self,_group,_unit,entry)
+for _,cargoObj in pairs(self.Cargo_Troops)do
+if not cargoObj.DontShowInMenu then
+local stock=cargoObj:GetStock()
+local menutext=cargoObj.Name
+if(stock>=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end
+MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[cargoObj.Subcategory],self._LoadTroops,self,_group,_unit,cargoObj)
end
end
else
-for _,_entry in pairs(self.Cargo_Troops)do
-local entry=_entry
-local noshow=entry.DontShowInMenu
-if not noshow then
-menucount=menucount+1
-menus[menucount]=MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops,self,_group,_unit,entry)
+for _,cargoObj in pairs(self.Cargo_Troops)do
+if not cargoObj.DontShowInMenu then
+local stock=cargoObj:GetStock()
+local menutext=cargoObj.Name
+if(stock>=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end
+MENU_GROUP_COMMAND:New(_group,menutext,troopsmenu,self._LoadTroops,self,_group,_unit,cargoObj)
+end
+end
+end
+local dropTroopsMenu=MENU_GROUP:New(_group,"Drop Troops",toptroops):Refresh()
+MENU_GROUP_COMMAND:New(_group,"Drop ALL troops",dropTroopsMenu,self._UnloadTroops,self,_group,_unit):Refresh()
+MENU_GROUP_COMMAND:New(_group,"Extract troops",toptroops,self._ExtractTroops,self,_group,_unit):Refresh()
+local uName=_unit:GetName()
+local loadedData=self.Loaded_Cargo[uName]
+if loadedData and loadedData.Cargo then
+for i,cargoObj in ipairs(loadedData.Cargo)do
+if cargoObj and(cargoObj:GetType()==CTLD_CARGO.Enum.TROOPS or cargoObj:GetType()==CTLD_CARGO.Enum.ENGINEERS)and not cargoObj:WasDropped()then
+local name=cargoObj:GetName()or"Unknown"
+local needed=cargoObj:GetCratesNeeded()or 1
+local cID=cargoObj:GetID()
+local line=string.format("Drop: %s",name,needed,cID)
+MENU_GROUP_COMMAND:New(_group,line,dropTroopsMenu,self._UnloadSingleTroopByID,self,_group,_unit,cID):Refresh()
end
end
end
-local unloadmenu1=MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops,self._UnloadTroops,self,_group,_unit):Refresh()
-local extractMenu1=MENU_GROUP_COMMAND:New(_group,"Extract troops",toptroops,self._ExtractTroops,self,_group,_unit):Refresh()
end
if cancrates then
-if nohookswitch then
-local loadmenu=MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates,self._LoadCratesNearby,self,_group,_unit)
-end
+local topcrates=MENU_GROUP:New(_group,"Manage Crates",topmenu)
+_group.MyTopCratesMenu=topcrates
local cratesmenu=MENU_GROUP:New(_group,"Get Crates",topcrates)
-local packmenu=MENU_GROUP_COMMAND:New(_group,"Pack crates",topcrates,self._PackCratesNearby,self,_group,_unit)
-local removecratesmenu=MENU_GROUP:New(_group,"Remove crates",topcrates)
+if self.onestepmenu then
if self.usesubcats then
local subcatmenus={}
-for _name,_entry in pairs(self.subcats)do
-subcatmenus[_name]=MENU_GROUP:New(_group,_name,cratesmenu)
+for catName,_ in pairs(self.subcats)do
+subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu)
end
-for _,_entry in pairs(self.Cargo_Crates)do
-local entry=_entry
-local subcat=entry.Subcategory
-local noshow=entry.DontShowInMenu
-local zone=entry.Location
-if not noshow then
-menucount=menucount+1
-local menutext=string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
-if zone then
-menutext=string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0)
-end
-menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates,self,_group,_unit,entry)
+for _,cargoObj in pairs(self.Cargo_Crates)do
+if not cargoObj.DontShowInMenu then
+local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
+if cargoObj.Location then txt=txt.."[R]"end
+local stock=cargoObj:GetStock()
+if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end
+local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory])
+MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj)
+MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj)
end
end
-for _,_entry in pairs(self.Cargo_Statics)do
-local entry=_entry
-local subcat=entry.Subcategory
-local noshow=entry.DontShowInMenu
-local zone=entry.Location
-if not noshow then
-menucount=menucount+1
-local menutext=string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
-if zone then
-menutext=string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0)
-end
-menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates,self,_group,_unit,entry)
+for _,cargoObj in pairs(self.Cargo_Statics)do
+if not cargoObj.DontShowInMenu then
+local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
+if cargoObj.Location then txt=txt.."[R]"end
+local stock=cargoObj:GetStock()
+if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end
+local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory])
+MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj)
+MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj)
end
end
else
-for _,_entry in pairs(self.Cargo_Crates)do
-local entry=_entry
-local noshow=entry.DontShowInMenu
-local zone=entry.Location
-if not noshow then
-menucount=menucount+1
-local menutext=string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
-if zone then
-menutext=string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0)
-end
-menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates,self,_group,_unit,entry)
+for _,cargoObj in pairs(self.Cargo_Crates)do
+if not cargoObj.DontShowInMenu then
+local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
+if cargoObj.Location then txt=txt.."[R]"end
+local stock=cargoObj:GetStock()
+if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end
+local mSet=MENU_GROUP:New(_group,txt,cratesmenu)
+MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj)
+MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj)
end
end
-for _,_entry in pairs(self.Cargo_Statics)do
-local entry=_entry
-local noshow=entry.DontShowInMenu
-local zone=entry.Location
-if not noshow then
-menucount=menucount+1
-local menutext=string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
-if zone then
-menutext=string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0)
-end
-menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates,self,_group,_unit,entry)
+for _,cargoObj in pairs(self.Cargo_Statics)do
+if not cargoObj.DontShowInMenu then
+local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
+if cargoObj.Location then txt=txt.."[R]"end
+local stock=cargoObj:GetStock()
+if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end
+local mSet=MENU_GROUP:New(_group,txt,cratesmenu)
+MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj)
+MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj)
end
end
end
-listmenu=MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates,self._ListCratesNearby,self,_group,_unit)
-local removecrates=MENU_GROUP_COMMAND:New(_group,"Remove crates nearby",removecratesmenu,self._RemoveCratesNearby,self,_group,_unit)
-local unloadmenu
-if nohookswitch then
-unloadmenu=MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates,self._UnloadCrates,self,_group,_unit)
+else
+if self.usesubcats then
+local subcatmenus={}
+for catName,_ in pairs(self.subcats)do
+subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu)
end
+for _,cargoObj in pairs(self.Cargo_Crates)do
+if not cargoObj.DontShowInMenu then
+local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
+if cargoObj.Location then txt=txt.."[R]"end
+local stock=cargoObj:GetStock()
+if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end
+MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj)
+end
+end
+for _,cargoObj in pairs(self.Cargo_Statics)do
+if not cargoObj.DontShowInMenu then
+local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
+if cargoObj.Location then txt=txt.."[R]"end
+local stock=cargoObj:GetStock()
+if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end
+MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj)
+end
+end
+else
+for _,cargoObj in pairs(self.Cargo_Crates)do
+if not cargoObj.DontShowInMenu then
+local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
+if cargoObj.Location then txt=txt.."[R]"end
+local stock=cargoObj:GetStock()
+if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end
+MENU_GROUP_COMMAND:New(_group,txt,cratesmenu,self._GetCrates,self,_group,_unit,cargoObj)
+end
+end
+for _,cargoObj in pairs(self.Cargo_Statics)do
+if not cargoObj.DontShowInMenu then
+local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
+if cargoObj.Location then txt=txt.."[R]"end
+local stock=cargoObj:GetStock()
+if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end
+MENU_GROUP_COMMAND:New(_group,txt,cratesmenu,self._GetCrates,self,_group,_unit,cargoObj)
+end
+end
+end
+end
+local loadCratesMenu=MENU_GROUP:New(_group,"Load Crates",topcrates)
+_group.MyLoadCratesMenu=loadCratesMenu
+MENU_GROUP_COMMAND:New(_group,"Load ALL",loadCratesMenu,self._LoadCratesNearby,self,_group,_unit)
+MENU_GROUP_COMMAND:New(_group,"Show loadable crates",loadCratesMenu,self._RefreshLoadCratesMenu,self,_group,_unit)
+local dropCratesMenu=MENU_GROUP:New(_group,"Drop Crates",topcrates)
+topcrates.DropCratesMenu=dropCratesMenu
if not self.nobuildmenu then
-local buildmenu=MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates,self._BuildCrates,self,_group,_unit)
-local repairmenu=MENU_GROUP_COMMAND:New(_group,"Repair",topcrates,self._RepairCrates,self,_group,_unit):Refresh()
-elseif unloadmenu then
-unloadmenu:Refresh()
+MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates,self._BuildCrates,self,_group,_unit)
+MENU_GROUP_COMMAND:New(_group,"Repair",topcrates,self._RepairCrates,self,_group,_unit):Refresh()
end
-end
-if self:IsHercules(_unit)then
-local hoverpars=MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu,self._ShowFlightParams,self,_group,_unit):Refresh()
+local removecratesmenu=MENU_GROUP:New(_group,"Remove crates",topcrates)
+MENU_GROUP_COMMAND:New(_group,"Remove crates nearby",removecratesmenu,self._RemoveCratesNearby,self,_group,_unit)
+if self.onestepmenu then
+local mPack=MENU_GROUP:New(_group,"Pack crates",topcrates)
+MENU_GROUP_COMMAND:New(_group,"Pack",mPack,self._PackCratesNearby,self,_group,_unit)
+MENU_GROUP_COMMAND:New(_group,"Pack and Load",mPack,self._PackAndLoad,self,_group,_unit)
+MENU_GROUP_COMMAND:New(_group,"Pack and Remove",mPack,self._PackAndRemove,self,_group,_unit)
+MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates,self._ListCratesNearby,self,_group,_unit)
else
-local hoverpars=MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu,self._ShowHoverParams,self,_group,_unit):Refresh()
+MENU_GROUP_COMMAND:New(_group,"Pack crates",topcrates,self._PackCratesNearby,self,_group,_unit)
+MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates,self._ListCratesNearby,self,_group,_unit)
+end
+local uName=_unit:GetName()
+local loadedData=self.Loaded_Cargo[uName]
+if loadedData and loadedData.Cargo then
+local cargoByName={}
+for _,cgo in pairs(loadedData.Cargo)do
+if cgo and(not cgo:WasDropped())then
+local cname=cgo:GetName()
+local cneeded=cgo:GetCratesNeeded()
+cargoByName[cname]=cargoByName[cname]or{count=0,needed=cneeded}
+cargoByName[cname].count=cargoByName[cname].count+1
+end
+end
+for name,info in pairs(cargoByName)do
+local line=string.format("Drop %s (%d/%d)",name,info.count,info.needed)
+MENU_GROUP_COMMAND:New(_group,line,dropCratesMenu,self._UnloadSingleCrateSet,self,_group,_unit,name)
+end
+end
+end
+MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu,self._ListCargo,self,_group,_unit)
+MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu,self._ListInventory,self,_group,_unit)
+MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu,self._ListRadioBeacons,self,_group,_unit)
+local smoketopmenu=MENU_GROUP:New(_group,"Smokes, Flares, Beacons",topmenu)
+MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",smoketopmenu,self.SmokeZoneNearBy,self,_unit,false)
+local smokeself=MENU_GROUP:New(_group,"Drop smoke now",smoketopmenu)
+MENU_GROUP_COMMAND:New(_group,"Red smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Red)
+MENU_GROUP_COMMAND:New(_group,"Blue smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Blue)
+MENU_GROUP_COMMAND:New(_group,"Green smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Green)
+MENU_GROUP_COMMAND:New(_group,"Orange smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Orange)
+MENU_GROUP_COMMAND:New(_group,"White smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.White)
+MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",smoketopmenu,self.SmokeZoneNearBy,self,_unit,true)
+MENU_GROUP_COMMAND:New(_group,"Fire flare now",smoketopmenu,self.SmokePositionNow,self,_unit,true)
+MENU_GROUP_COMMAND:New(_group,"Drop beacon now",smoketopmenu,self.DropBeaconNow,self,_unit):Refresh()
+if self:IsFixedWing(_unit)then
+MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu,self._ShowFlightParams,self,_group,_unit):Refresh()
+else
+MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu,self._ShowHoverParams,self,_group,_unit):Refresh()
end
self.MenusDone[_unitName]=true
+self:_RefreshLoadCratesMenu(_group,_unit)
+self:_RefreshDropCratesMenu(_group,_unit)
end
end
else
@@ -72874,6 +75543,534 @@ end
end
return self
end
+function CTLD:_RefreshLoadCratesMenu(Group,Unit)
+if not Group.MyLoadCratesMenu then return end
+Group.MyLoadCratesMenu:RemoveSubMenus()
+local d=self.CrateDistance or 35
+local nearby,n=self:_FindCratesNearby(Group,Unit,d,true,true)
+if n==0 then
+MENU_GROUP_COMMAND:New(Group,"No crates found! Rescan?",Group.MyLoadCratesMenu,function()self:_RefreshLoadCratesMenu(Group,Unit)end)
+return
+end
+MENU_GROUP_COMMAND:New(Group,"Load ALL",Group.MyLoadCratesMenu,self._LoadCratesNearby,self,Group,Unit)
+local cargoByName={}
+for _,crate in pairs(nearby)do
+local name=crate:GetName()
+cargoByName[name]=cargoByName[name]or{}
+table.insert(cargoByName[name],crate)
+end
+local lineIndex=1
+for cName,list in pairs(cargoByName)do
+local needed=list[1]:GetCratesNeeded()or 1
+table.sort(list,function(a,b)return a:GetID()=needed then
+label=string.format("%d. Load %s",lineIndex,cName)
+i=i+needed
+else
+label=string.format("%d. Load %s (%d/%d)",lineIndex,cName,left,needed)
+i=#list+1
+end
+MENU_GROUP_COMMAND:New(Group,label,Group.MyLoadCratesMenu,self._LoadSingleCrateSet,self,Group,Unit,cName)
+lineIndex=lineIndex+1
+end
+end
+end
+function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName)
+self:T(self.lid.." _LoadSingleCrateSet cargoName="..(cargoName or"nil"))
+local grounded=not self:IsUnitInAir(Unit)
+local hover=self:CanHoverLoad(Unit)
+if not grounded and not hover then
+self:_SendMessage("You must land or hover to load crates!",10,false,Group)
+return self
+end
+if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then
+self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group)
+return self
+end
+local finddist=self.CrateDistance or 35
+local cratesNearby,number=self:_FindCratesNearby(Group,Unit,finddist,false,false)
+if number==0 then
+self:_SendMessage("No crates found in range!",10,false,Group)
+return self
+end
+local matchingCrates={}
+local needed=nil
+for _,crateObj in pairs(cratesNearby)do
+if crateObj:GetName()==cargoName then
+needed=needed or crateObj:GetCratesNeeded()
+table.insert(matchingCrates,crateObj)
+end
+end
+if not needed then
+self:_SendMessage(string.format("No \"%s\" crates found in range!",cargoName),10,false,Group)
+return self
+end
+local found=#matchingCrates
+local unitName=Unit:GetName()
+local loadedData=self.Loaded_Cargo[unitName]or{Troopsloaded=0,Cratesloaded=0,Cargo={}}
+local capabilities=self:_GetUnitCapabilities(Unit)
+local capacity=capabilities.cratelimit or 0
+if loadedData.Cratesloaded>=capacity then
+self:_SendMessage("No more capacity to load crates!",10,false,Group)
+return self
+end
+local spaceLeft=capacity-loadedData.Cratesloaded
+local toLoad=math.min(found,needed,spaceLeft)
+if toLoad<1 then
+self:_SendMessage("Cannot load crates: either none found or no capacity left.",10,false,Group)
+return self
+end
+local crateIDsLoaded={}
+for i=1,toLoad do
+local crate=matchingCrates[i]
+crate:SetHasMoved(true)
+crate:SetWasDropped(false)
+table.insert(loadedData.Cargo,crate)
+loadedData.Cratesloaded=loadedData.Cratesloaded+1
+local stObj=crate:GetPositionable()
+if stObj and stObj:IsAlive()then
+stObj:Destroy(false)
+end
+table.insert(crateIDsLoaded,crate:GetID())
+end
+self.Loaded_Cargo[unitName]=loadedData
+self:_UpdateUnitCargoMass(Unit)
+local newSpawned={}
+for _,cObj in ipairs(self.Spawned_Cargo)do
+local keep=true
+for i=1,toLoad do
+if matchingCrates[i]and cObj:GetID()==matchingCrates[i]:GetID()then
+keep=false
+break
+end
+end
+if keep then
+table.insert(newSpawned,cObj)
+end
+end
+self.Spawned_Cargo=newSpawned
+local loadedHere=toLoad
+if loadedHere=capacity then
+self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!",loadedHere,needed,cargoName),10,false,Group)
+else
+local fullSets=math.floor(loadedHere/needed)
+local leftover=loadedHere%needed
+if needed>1 then
+if fullSets>0 and leftover==0 then
+self:_SendMessage(string.format("Loaded %d %s.",fullSets,cargoName),10,false,Group)
+elseif fullSets>0 and leftover>0 then
+self:_SendMessage(string.format("Loaded %d %s(s), with %d leftover crate(s).",fullSets,cargoName,leftover),10,false,Group)
+else
+self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s.",loadedHere,needed,cargoName),15,false,Group)
+end
+else
+self:_SendMessage(string.format("Loaded %d %s(s).",loadedHere,cargoName),10,false,Group)
+end
+end
+self:_RefreshLoadCratesMenu(Group,Unit)
+self:_RefreshDropCratesMenu(Group,Unit)
+return self
+end
+function CTLD:_UnloadSingleCrateSet(Group,Unit,setIndex)
+self:T(self.lid.." _UnloadSingleCrateSet")
+if not self.dropcratesanywhere then
+local inzone,zoneName,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP)
+if not inzone then
+self:_SendMessage("You are not close enough to a drop zone!",10,false,Group)
+if not self.debug then
+return self
+end
+end
+end
+if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then
+self:_SendMessage("You need to open the door(s) to drop cargo!",10,false,Group)
+if not self.debug then return self end
+end
+local unitName=Unit:GetName()
+if not self.CrateGroupList or not self.CrateGroupList[unitName]then
+self:_SendMessage("No crate groups found for this unit!",10,false,Group)
+if not self.debug then return self end
+return self
+end
+local chunk=self.CrateGroupList[unitName][setIndex]
+if not chunk then
+self:_SendMessage("No crate set found or index invalid!",10,false,Group)
+if not self.debug then return self end
+return self
+end
+if#chunk==0 then
+self:_SendMessage("No crate found in that set!",10,false,Group)
+if not self.debug then return self end
+return self
+end
+local grounded=not self:IsUnitInAir(Unit)
+local hoverunload=self:IsCorrectHover(Unit)
+local isHerc=self:IsFixedWing(Unit)
+local isHook=self:IsHook(Unit)
+if isHerc and not isHook then
+hoverunload=self:IsCorrectFlightParameters(Unit)
+end
+if not grounded and not hoverunload then
+if isHerc then
+self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group)
+else
+self:_SendMessage("Nothing loaded or not hovering within parameters!",10,false,Group)
+end
+if not self.debug then return self end
+return self
+end
+local crateObj=chunk[1]
+if not crateObj then
+self:_SendMessage("No crate found in that set!",10,false,Group)
+if not self.debug then return self end
+return self
+end
+local needed=crateObj:GetCratesNeeded()or 1
+self:_GetCrates(Group,Unit,crateObj,#chunk,true)
+for _,cObj in ipairs(chunk)do
+cObj:SetWasDropped(true)
+cObj:SetHasMoved(true)
+end
+local cname=crateObj:GetName()or"Unknown"
+local count=#chunk
+if needed>1 then
+if count==needed then
+self:_SendMessage(string.format("Dropped %d %s.",1,cname),10,false,Group)
+else
+self:_SendMessage(string.format("Dropped %d/%d crate(s) of %s.",count,needed,cname),15,false,Group)
+end
+else
+self:_SendMessage(string.format("Dropped %d %s(s).",count,cname),10,false,Group)
+end
+local loadedData=self.Loaded_Cargo[unitName]
+if loadedData and loadedData.Cargo then
+local newList={}
+local newCratesCount=0
+for _,cObj in ipairs(loadedData.Cargo)do
+if not cObj:WasDropped()then
+table.insert(newList,cObj)
+local ct=cObj:GetType()
+if ct~=CTLD_CARGO.Enum.TROOPS and ct~=CTLD_CARGO.Enum.ENGINEERS then
+newCratesCount=newCratesCount+1
+end
+end
+end
+loadedData.Cargo=newList
+loadedData.Cratesloaded=newCratesCount
+self.Loaded_Cargo[unitName]=loadedData
+end
+self:_UpdateUnitCargoMass(Unit)
+self:_RefreshDropCratesMenu(Group,Unit)
+self:_RefreshLoadCratesMenu(Group,Unit)
+return self
+end
+function CTLD:_RefreshDropCratesMenu(Group,Unit)
+if not Group.CTLDTopmenu then return end
+local topCrates=Group.MyTopCratesMenu
+if not topCrates then return end
+if topCrates.DropCratesMenu then
+topCrates.DropCratesMenu:RemoveSubMenus()
+else
+topCrates.DropCratesMenu=MENU_GROUP:New(Group,"Drop Crates",topCrates)
+end
+local dropCratesMenu=topCrates.DropCratesMenu
+local loadedData=self.Loaded_Cargo[Unit:GetName()]
+if not loadedData or not loadedData.Cargo then
+MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function()end)
+return
+end
+local cargoByName={}
+local dropableCrates=0
+for _,cObj in ipairs(loadedData.Cargo)do
+if cObj and not cObj:WasDropped()then
+local cType=cObj:GetType()
+if cType~=CTLD_CARGO.Enum.TROOPS and cType~=CTLD_CARGO.Enum.ENGINEERS and cType~=CTLD_CARGO.Enum.GCLOADABLE then
+local name=cObj:GetName()or"Unknown"
+cargoByName[name]=cargoByName[name]or{}
+table.insert(cargoByName[name],cObj)
+dropableCrates=dropableCrates+1
+end
+end
+end
+if dropableCrates==0 then
+MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function()end)
+return
+end
+if not self.onestepmenu then
+MENU_GROUP_COMMAND:New(Group,"Drop ALL crates",dropCratesMenu,self._UnloadCrates,self,Group,Unit)
+self.CrateGroupList=self.CrateGroupList or{}
+self.CrateGroupList[Unit:GetName()]={}
+local lineIndex=1
+for cName,list in pairs(cargoByName)do
+local needed=list[1]:GetCratesNeeded()or 1
+table.sort(list,function(a,b)return a:GetID()=needed then
+local chunk={}
+for n=i,i+needed-1 do
+table.insert(chunk,list[n])
+end
+local label=string.format("%d. %s",lineIndex,cName)
+table.insert(self.CrateGroupList[Unit:GetName()],chunk)
+local setIndex=#self.CrateGroupList[Unit:GetName()]
+MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex)
+i=i+needed
+else
+local chunk={}
+for n=i,#list do
+table.insert(chunk,list[n])
+end
+local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed)
+table.insert(self.CrateGroupList[Unit:GetName()],chunk)
+local setIndex=#self.CrateGroupList[Unit:GetName()]
+MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex)
+i=#list+1
+end
+lineIndex=lineIndex+1
+end
+end
+else
+local mAll=MENU_GROUP:New(Group,"Drop ALL crates",dropCratesMenu)
+MENU_GROUP_COMMAND:New(Group,"Drop",mAll,self._UnloadCrates,self,Group,Unit)
+if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then
+MENU_GROUP_COMMAND:New(Group,"Drop and build",mAll,self._DropAndBuild,self,Group,Unit)
+end
+self.CrateGroupList=self.CrateGroupList or{}
+self.CrateGroupList[Unit:GetName()]={}
+local lineIndex=1
+for cName,list in pairs(cargoByName)do
+local needed=list[1]:GetCratesNeeded()or 1
+table.sort(list,function(a,b)return a:GetID()=needed then
+local chunk={}
+for n=i,i+needed-1 do
+table.insert(chunk,list[n])
+end
+local label=string.format("%d. %s",lineIndex,cName)
+table.insert(self.CrateGroupList[Unit:GetName()],chunk)
+local setIndex=#self.CrateGroupList[Unit:GetName()]
+local mSet=MENU_GROUP:New(Group,label,dropCratesMenu)
+MENU_GROUP_COMMAND:New(Group,"Drop",mSet,self._UnloadSingleCrateSet,self,Group,Unit,setIndex)
+if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then
+MENU_GROUP_COMMAND:New(Group,"Drop and build",mSet,self._DropSingleAndBuild,self,Group,Unit,setIndex)
+end
+i=i+needed
+else
+local chunk={}
+for n=i,#list do
+table.insert(chunk,list[n])
+end
+local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed)
+table.insert(self.CrateGroupList[Unit:GetName()],chunk)
+local setIndex=#self.CrateGroupList[Unit:GetName()]
+MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex)
+i=#list+1
+end
+lineIndex=lineIndex+1
+end
+end
+end
+end
+function CTLD:_UnloadSingleTroopByID(Group,Unit,chunkID)
+self:T(self.lid.." _UnloadSingleTroopByID chunkID="..tostring(chunkID))
+local droppingatbase=false
+local inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)
+if not inzone then
+inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP)
+end
+if inzone then
+droppingatbase=true
+end
+if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then
+self:_SendMessage("You need to open the door(s) to unload troops!",10,false,Group)
+if not self.debug then return self end
+end
+local hoverunload=self:IsCorrectHover(Unit)
+local isHerc=self:IsFixedWing(Unit)
+local isHook=self:IsHook(Unit)
+if isHerc and not isHook then
+hoverunload=self:IsCorrectFlightParameters(Unit)
+end
+local grounded=not self:IsUnitInAir(Unit)
+local unitName=Unit:GetName()
+if self.Loaded_Cargo[unitName]and(grounded or hoverunload)then
+if not droppingatbase or self.debug then
+if not self.TroopsIDToChunk or not self.TroopsIDToChunk[chunkID]then
+self:_SendMessage(string.format("No troop cargo chunk found for ID %d!",chunkID),10,false,Group)
+if not self.debug then return self end
+return self
+end
+local chunk=self.TroopsIDToChunk[chunkID]
+if not chunk or#chunk==0 then
+self:_SendMessage(string.format("Troop chunk is empty for ID %d!",chunkID),10,false,Group)
+if not self.debug then return self end
+return self
+end
+local foundCargo=chunk[1]
+if not foundCargo then
+self:_SendMessage(string.format("No troop cargo at chunk %d!",chunkID),10,false,Group)
+if not self.debug then return self end
+return self
+end
+local cType=foundCargo:GetType()
+local name=foundCargo:GetName()or"none"
+local tmpl=foundCargo:GetTemplates()or{}
+local zoneradius=self.troopdropzoneradius or 100
+local factor=1
+if isHerc then
+factor=foundCargo:GetCratesNeeded()or 1
+zoneradius=Unit:GetVelocityMPS()or 100
+end
+local zone=ZONE_GROUP:New(string.format("Unload zone-%s",unitName),Group,zoneradius*factor)
+local randomcoord=zone:GetRandomCoordinate(10,30*factor)
+local heading=Group:GetHeading()or 0
+if grounded or hoverunload then
+randomcoord=Group:GetCoordinate()
+local Angle=(heading+270)%360
+if isHerc or isHook then
+Angle=(heading+180)%360
+end
+local offset=hoverunload and self.TroopUnloadDistHover or self.TroopUnloadDistGround
+if isHerc then
+offset=self.TroopUnloadDistGroundHerc or 25
+end
+if isHook then
+offset=self.TroopUnloadDistGroundHook or 15
+if hoverunload and self.TroopUnloadDistHoverHook then
+offset=self.TroopUnloadDistHoverHook or 5
+end
+end
+randomcoord:Translate(offset,Angle,nil,true)
+end
+local tempcount=0
+if isHook then
+tempcount=self.ChinookTroopCircleRadius or 5
+end
+for _,_template in pairs(tmpl)do
+self.TroopCounter=self.TroopCounter+1
+tempcount=tempcount+1
+local alias=string.format("%s-%d",_template,math.random(1,100000))
+local rad=2.5+(tempcount*2)
+local Positions=self:_GetUnitPositions(randomcoord,rad,heading,_template)
+self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
+:InitDelayOff()
+:InitSetUnitAbsolutePositions(Positions)
+:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
+:OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end)
+:SpawnFromVec2(randomcoord:GetVec2())
+self:__TroopsDeployed(1,Group,Unit,self.DroppedTroops[self.TroopCounter],cType)
+end
+foundCargo:SetWasDropped(true)
+if cType==CTLD_CARGO.Enum.ENGINEERS then
+self.Engineers=self.Engineers+1
+local grpname=self.DroppedTroops[self.TroopCounter]:GetName()
+self.EngineersInField[self.Engineers]=CTLD_ENGINEERING:New(name,grpname)
+self:_SendMessage(string.format("Dropped Engineers %s into action!",name),10,false,Group)
+else
+self:_SendMessage(string.format("Dropped Troops %s into action!",name),10,false,Group)
+end
+table.remove(chunk,1)
+if#chunk==0 then
+self.TroopsIDToChunk[chunkID]=nil
+end
+else
+self:_SendMessage("Troops have returned to base!",10,false,Group)
+self:__TroopsRTB(1,Group,Unit,zonename,zone)
+if self.TroopsIDToChunk and self.TroopsIDToChunk[chunkID]then
+local chunk=self.TroopsIDToChunk[chunkID]
+if#chunk>0 then
+local firstObj=chunk[1]
+local cName=firstObj:GetName()
+local gentroops=self.Cargo_Troops
+for _id,_troop in pairs(gentroops)do
+if _troop.Name==cName then
+local st=_troop:GetStock()
+if st and tonumber(st)>=0 then
+_troop:AddStock()
+end
+end
+end
+firstObj:SetWasDropped(true)
+table.remove(chunk,1)
+if#chunk==0 then
+self.TroopsIDToChunk[chunkID]=nil
+end
+end
+end
+end
+local cargoList=self.Loaded_Cargo[unitName].Cargo
+for i=#cargoList,1,-1 do
+if cargoList[i]:WasDropped()then
+table.remove(cargoList,i)
+end
+end
+local troopsLoaded=0
+local cratesLoaded=0
+for _,cargo in ipairs(cargoList)do
+local cT=cargo:GetType()
+if cT==CTLD_CARGO.Enum.TROOPS or cT==CTLD_CARGO.Enum.ENGINEERS then
+troopsLoaded=troopsLoaded+1
+else
+cratesLoaded=cratesLoaded+1
+end
+end
+self.Loaded_Cargo[unitName].Troopsloaded=troopsLoaded
+self.Loaded_Cargo[unitName].Cratesloaded=cratesLoaded
+self:_RefreshDropTroopsMenu(Group,Unit)
+else
+local isHerc=self:IsFixedWing(Unit)
+if isHerc then
+self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group)
+else
+self:_SendMessage("Nothing loaded or not hovering within parameters!",10,false,Group)
+end
+end
+return self
+end
+function CTLD:_RefreshDropTroopsMenu(Group,Unit)
+local theGroup=Group
+local theUnit=Unit
+if not theGroup.CTLDTopmenu then return end
+local topTroops=theGroup.MyTopTroopsMenu
+if not topTroops then return end
+if topTroops.DropTroopsMenu then
+topTroops.DropTroopsMenu:Remove()
+end
+local dropTroopsMenu=MENU_GROUP:New(theGroup,"Drop Troops",topTroops)
+topTroops.DropTroopsMenu=dropTroopsMenu
+MENU_GROUP_COMMAND:New(theGroup,"Drop ALL troops",dropTroopsMenu,self._UnloadTroops,self,theGroup,theUnit)
+local loadedData=self.Loaded_Cargo[theUnit:GetName()]
+if not loadedData or not loadedData.Cargo then return end
+local troopsByName={}
+for _,cargoObj in ipairs(loadedData.Cargo)do
+if cargoObj
+and(cargoObj:GetType()==CTLD_CARGO.Enum.TROOPS or cargoObj:GetType()==CTLD_CARGO.Enum.ENGINEERS)
+and not cargoObj:WasDropped()
+then
+local name=cargoObj:GetName()or"Unknown"
+troopsByName[name]=troopsByName[name]or{}
+table.insert(troopsByName[name],cargoObj)
+end
+end
+self.TroopsIDToChunk=self.TroopsIDToChunk or{}
+for tName,objList in pairs(troopsByName)do
+table.sort(objList,function(a,b)return a:GetID()distance then
+self:T("Zone Active: "..tostring(active))
+if(zone:IsVec2InZone(unitVec2)or Zonetype==CTLD.CargoZoneType.MOVE)and active==true and distance0 and not Troopstable[genname]then
+Troopstable[genname]={
+Stock0=generic:GetStock0(),
+Stock=generic:GetStock(),
+StockR=generic:GetRelativeStock(),
+Infield=0,
+Inhelo=0,
+CratesInfield=0,
+Sum=generic:GetStock(),
+}
+if Restock==true then
+Troopstable[genname].GenericCargo=generic
+end
+end
+end
+for _id,_cargo in pairs(self.Cargo_Troops)do
+local generic=_cargo
+local genname=generic:GetName()
+if generic and generic:GetStock0()>0 and not Troopstable[genname]then
+Troopstable[genname]={
+Stock0=generic:GetStock0(),
+Stock=generic:GetStock(),
+StockR=generic:GetRelativeStock(),
+Infield=0,
+Inhelo=0,
+CratesInfield=0,
+Sum=generic:GetStock(),
+}
+if Restock==true then
+Troopstable[genname].GenericCargo=generic
+end
+end
+end
+for _index,_group in pairs(self.DroppedTroops)do
+if _group and _group:IsAlive()then
+self:T("Looking at ".._group:GetName().." in the field")
+local generic=self:GetGenericCargoObjectFromGroupName(_group:GetName())
+if generic then
+local genname=generic:GetName()
+self:T("Found Generic "..genname.." in the field. Adding.")
+if generic:GetStock0()>0 then
+Troopstable[genname].Infield=Troopstable[genname].Infield+1
+Troopstable[genname].Sum=Troopstable[genname].Infield+Troopstable[genname].Stock+Troopstable[genname].Inhelo
+end
+else
+self:E(self.lid.."Group without Cargo Generic: ".._group:GetName())
+end
+end
+end
+for _unitname,_loaded in pairs(self.Loaded_Cargo)do
+local _unit=UNIT:FindByName(_unitname)
+if _unit and _unit:IsAlive()then
+local unitname=_unit:GetName()
+local loadedcargo=self.Loaded_Cargo[unitname].Cargo or{}
+for _,_cgo in pairs(loadedcargo)do
+local cargo=_cgo
+local type=cargo.CargoType
+local gname=cargo.Name
+local gcargo=self:_FindCratesCargoObject(gname)or self:_FindTroopsCargoObject(gname)
+self:T("Looking at "..gname.." in the helo - type = "..type)
+if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS or type==CTLD_CARGO.Enum.VEHICLE or type==CTLD_CARGO.Enum.FOB)then
+if gcargo and gcargo:GetStock0()>0 then
+self:T("Adding "..gname.." in the helo - type = "..type)
+if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)then
+Troopstable[gname].Inhelo=Troopstable[gname].Inhelo+1
+end
+if(type==CTLD_CARGO.Enum.VEHICLE or type==CTLD_CARGO.Enum.FOB)then
+local counting=gcargo.CratesNeeded
+local added=1
+if counting>1 then
+added=added/counting
+end
+Troopstable[gname].Inhelo=Troopstable[gname].Inhelo+added
+end
+Troopstable[gname].Sum=Troopstable[gname].Infield+Troopstable[gname].Stock+Troopstable[gname].Inhelo+Troopstable[gname].CratesInfield
+end
+end
+end
+end
+end
+if self.Spawned_Cargo then
+for i=#self.Spawned_Cargo,1,-1 do
+local cargo=self.Spawned_Cargo[i]
+if cargo and cargo:GetPositionable()and cargo:GetPositionable():IsAlive()then
+local genname=cargo:GetName()
+local gcargo=self:_FindCratesCargoObject(genname)
+if Troopstable[genname]and gcargo and gcargo:GetStock0()>0 then
+local needed=gcargo.CratesNeeded or 1
+local added=1
+if needed>1 then
+added=added/needed
+end
+Troopstable[genname].CratesInfield=Troopstable[genname].CratesInfield+added
+Troopstable[genname].Sum=Troopstable[genname].Infield+Troopstable[genname].Stock
++Troopstable[genname].Inhelo+Troopstable[genname].CratesInfield
+end
+end
+end
+for i=#self.Spawned_Cargo,1,-1 do
+local cargo=self.Spawned_Cargo[i]
+if cargo and cargo:GetPositionable()and cargo:GetPositionable():IsAlive()then
+local genname=cargo:GetName()
+if Troopstable[genname]then
+if Troopstable[genname].Inhelo==0 and Troopstable[genname].CratesInfield<1 then
+Troopstable[genname].CratesInfield=0
+Troopstable[genname].Sum=Troopstable[genname].Stock
+cargo:GetPositionable():Destroy(false)
+table.remove(self.Spawned_Cargo,i)
+local leftover=Troopstable[genname].Stock0-(Troopstable[genname].Infield+Troopstable[genname].Inhelo+Troopstable[genname].CratesInfield)
+if leftover12 then clock=clock-12 end
end
return clock
end
-function CSAR:_AddBeaconToGroup(_group,_freq,_name)
+function CSAR:_AddBeaconToGroup(_group,_freq,BeaconName)
self:T(self.lid.." _AddBeaconToGroup")
if self.CreateRadioBeacons==false then return end
local _group=_group
@@ -76124,10 +79643,10 @@ local _radioUnit=_group:GetUnit(1)
if _radioUnit then
local name=_radioUnit:GetName()
local Frequency=_freq
-local name=_radioUnit:GetName()
local Sound="l10n/DEFAULT/"..self.radioSound
local vec3=_radioUnit:GetVec3()or _radioUnit:GetPositionVec3()or{x=0,y=0,z=0}
-trigger.action.radioTransmission(Sound,vec3,0,false,Frequency,self.ADFRadioPwr or 1000,_name)
+self:I(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z))
+trigger.action.radioTransmission(Sound,vec3,0,true,Frequency,self.ADFRadioPwr or 500,BeaconName)
end
end
return self
@@ -76143,9 +79662,11 @@ local pilot=_pilot
local group=pilot.group
local frequency=pilot.frequency or 0
local bname=pilot.BeaconName or pilot.name..math.random(1,100000)
-trigger.action.stopRadioTransmission(bname)
if group and group:IsAlive()and frequency>0 then
-self:_AddBeaconToGroup(group,frequency,bname)
+else
+if frequency>0 then
+trigger.action.stopRadioTransmission(bname)
+end
end
end
end
@@ -76168,6 +79689,21 @@ local limit=self.maxdownedpilots
local islimited=self.limitmaxdownedpilots
local count=self:_CountActiveDownedPilots()
if islimited and(count>=limit)then
+if self.useFIFOLimitReplacement then
+local oldIndex=-1
+local oldDownedPilot=nil
+for _index,_downedpilot in pairs(self.downedPilots)do
+oldIndex=_index
+oldDownedPilot=_downedpilot
+break
+end
+if oldDownedPilot then
+oldDownedPilot.group:Destroy(false)
+oldDownedPilot.alive=false
+self:_CheckDownedPilotTable()
+return false
+end
+end
return true
else
return false
@@ -76199,6 +79735,8 @@ else
self.allheligroupset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart()
end
self.mash=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart()
+self.staticmashes=SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart()
+self.zonemashes=SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterStart()
if not self.coordinate then
local csarhq=self.mash:GetRandom()
if csarhq then
@@ -76519,7 +80057,7 @@ local description=dataset[7]
local typeName=dataset[8]
local unitName=dataset[9]
local freq=tonumber(dataset[10])
-self:_AddCsar(coalition,country,point,typeName,unitName,playerName,freq,nil,description,nil)
+self:_AddCsar(coalition,country,point,typeName,unitName,playerName,freq,false,description,nil)
end
return self
end
@@ -76540,7 +80078,7 @@ capFormation=nil,
capOptionVaryStartTime=nil,
capOptionVaryEndTime=nil,
}
-AIRWING.version="0.9.5"
+AIRWING.version="0.9.7"
function AIRWING:New(warehousename,airwingname)
local self=BASE:Inherit(self,LEGION:New(warehousename,airwingname))
if not self then
@@ -76834,19 +80372,29 @@ point.type,point.noccupied,point.heading,point.leg,point.altitude,point.speed)
return text
end
function AIRWING:UpdatePatrolPointMarker(point)
-if self.markpoints then
+if self and self.markpoints then
local text=string.format("%s Occupied=%d\nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts",
point.type,point.noccupied,point.heading,point.leg,point.altitude,point.speed)
+if point.IsZonePoint and point.IsZonePoint==true and point.patrolzone then
+local Coordinate=point.patrolzone:GetCoordinate()
+point.marker:UpdateCoordinate(Coordinate)
+point.marker:UpdateText(text,1.5)
+else
point.marker:UpdateText(text,1)
end
end
-function AIRWING:NewPatrolPoint(Type,Coordinate,Altitude,Speed,Heading,LegLength,RefuelSystem)
-if Coordinate and Coordinate:IsInstanceOf("ZONE_BASE")then
-Coordinate=Coordinate:GetCoordinate()
end
+function AIRWING:NewPatrolPoint(Type,Coordinate,Altitude,Speed,Heading,LegLength,RefuelSystem)
local patrolpoint={}
patrolpoint.type=Type or"Unknown"
patrolpoint.coord=Coordinate or self:GetCoordinate():Translate(UTILS.NMToMeters(math.random(10,15)),math.random(360))
+if Coordinate and Coordinate:IsInstanceOf("ZONE_BASE")then
+patrolpoint.IsZonePoint=true
+patrolpoint.patrolzone=Coordinate
+patrolpoint.coord=patrolpoint.patrolzone:GetCoordinate()
+else
+patrolpoint.IsZonePoint=false
+end
patrolpoint.heading=Heading or math.random(360)
patrolpoint.leg=LegLength or 15
patrolpoint.altitude=Altitude or math.random(10,20)*1000
@@ -76855,7 +80403,7 @@ patrolpoint.noccupied=0
patrolpoint.refuelsystem=RefuelSystem
if self.markpoints then
patrolpoint.marker=MARKER:New(Coordinate,"New Patrol Point"):ToAll()
-AIRWING.UpdatePatrolPointMarker(patrolpoint)
+self:UpdatePatrolPointMarker(patrolpoint)
end
return patrolpoint
end
@@ -76908,6 +80456,26 @@ function AIRWING:SetTakeoffAir()
self:SetTakeoffType("Air")
return self
end
+function AIRWING:SetLandingStraightIn()
+self.OptionLandingStraightIn=true
+return self
+end
+function AIRWING:SetLandingForcePair()
+self.OptionLandingForcePair=true
+return self
+end
+function AIRWING:SetLandingRestrictPair()
+self.OptionLandingRestrictPair=true
+return self
+end
+function AIRWING:SetLandingOverheadBreak()
+self.OptionLandingOverheadBreak=true
+return self
+end
+function AIRWING:SetOptionPreferVerticalLanding()
+self.OptionPreferVerticalLanding=true
+return self
+end
function AIRWING:SetDespawnAfterLanding(Switch)
if Switch then
self.despawnAfterLanding=Switch
@@ -76979,6 +80547,9 @@ if PatrolPoints and#PatrolPoints>0 then
table.sort(PatrolPoints,sort)
for _,_patrolpoint in pairs(PatrolPoints)do
local patrolpoint=_patrolpoint
+if patrolpoint.IsZonePoint and patrolpoint.IsZonePoint==true and patrolpoint.patrolzone then
+patrolpoint.coord=patrolpoint.patrolzone:GetCoordinate()
+end
if(RefuelSystem and patrolpoint.refuelsystem and RefuelSystem==patrolpoint.refuelsystem)or RefuelSystem==nil or patrolpoint.refuelsystem==nil then
return patrolpoint
end
@@ -77009,7 +80580,7 @@ missionCAP:SetTime(ClockStart)
end
missionCAP.patroldata=patrol
patrol.noccupied=patrol.noccupied+1
-if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end
+if self.markpoints then self:UpdatePatrolPointMarker(patrol)end
self:AddMission(missionCAP)
end
return self
@@ -77035,7 +80606,7 @@ local missionRECON=AUFTRAG:NewRECON(ZoneSet,patrol.speed,patrol.altitude,true)
missionRECON.patroldata=patrol
missionRECON.categories={AUFTRAG.Category.AIRCRAFT}
patrol.noccupied=patrol.noccupied+1
-if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end
+if self.markpoints then self:UpdatePatrolPointMarker(patrol)end
self:AddMission(missionRECON)
end
return self
@@ -77059,7 +80630,7 @@ local altitude=patrol.altitude+1000*patrol.noccupied
local mission=AUFTRAG:NewTANKER(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg,Unit.RefuelingSystem.BOOM_AND_RECEPTACLE)
mission.patroldata=patrol
patrol.noccupied=patrol.noccupied+1
-if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end
+if self.markpoints then self:UpdatePatrolPointMarker(patrol)end
self:AddMission(mission)
end
for i=1,self.nflightsTANKERprobe-Nprob do
@@ -77068,7 +80639,7 @@ local altitude=patrol.altitude+1000*patrol.noccupied
local mission=AUFTRAG:NewTANKER(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg,Unit.RefuelingSystem.PROBE_AND_DROGUE)
mission.patroldata=patrol
patrol.noccupied=patrol.noccupied+1
-if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end
+if self.markpoints then self:UpdatePatrolPointMarker(patrol)end
self:AddMission(mission)
end
return self
@@ -77087,19 +80658,21 @@ local altitude=patrol.altitude+1000*patrol.noccupied
local mission=AUFTRAG:NewAWACS(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg)
mission.patroldata=patrol
patrol.noccupied=patrol.noccupied+1
-if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end
+if self.markpoints then self:UpdatePatrolPointMarker(patrol)end
self:AddMission(mission)
end
return self
end
function AIRWING:CheckRescuhelo()
local N=self:CountMissionsInQueue({AUFTRAG.Type.RESCUEHELO})
+if self.airbase then
local name=self.airbase:GetName()
local carrier=UNIT:FindByName(name)
for i=1,self.nflightsRescueHelo-N do
local mission=AUFTRAG:NewRESCUEHELO(carrier)
self:AddMission(mission)
end
+end
return self
end
function AIRWING:GetTankerForFlight(flightgroup)
@@ -77142,6 +80715,18 @@ self:T(self.lid..string.format("Group %s on %s mission %s",FlightGroup:GetName()
if self.UseConnectedOpsAwacs and self.ConnectedOpsAwacs then
self.ConnectedOpsAwacs:__FlightOnMission(2,FlightGroup,Mission)
end
+if self.OptionLandingForcePair then
+FlightGroup:SetOptionLandingForcePair()
+elseif self.OptionLandingOverheadBreak then
+FlightGroup:SetOptionLandingOverheadBreak()
+elseif self.OptionLandingRestrictPair then
+FlightGroup:SetOptionLandingRestrictPair()
+elseif self.OptionLandingStraightIn then
+FlightGroup:SetOptionLandingStraightIn()
+end
+if self.OptionPreferVerticalLanding then
+FlightGroup:SetOptionPreferVertical()
+end
end
function AIRWING:CountPayloadsInStock(MissionTypes,UnitTypes,Payloads)
if MissionTypes then
@@ -77222,7 +80807,7 @@ ClassName="ARMYGROUP",
formationPerma=nil,
engage={},
}
-ARMYGROUP.version="1.0.1"
+ARMYGROUP.version="1.0.3"
function ARMYGROUP:New(group)
local og=_DATABASE:GetOpsGroup(group)
if og then
@@ -78017,31 +81602,12 @@ self.radio.Modu=radio.modulation.AM
self:SetDefaultRadio(self.radio.Freq,self.radio.Modu,self.radio.On)
self.option.Formation=template.route.points[1].action
self.optionDefault.Formation=ENUMS.Formation.Vehicle.OnRoad
-if self.groupinitialized then
-self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
-return
-end
-self:T(self.lid.."FF Initializing Group")
-local template=Template or self:_GetTemplate()
-self.isAI=true
-self.isLateActivated=template.lateActivation
-self.isUncontrolled=false
-self.speedMax=self.group:GetSpeedMax()
-if self.speedMax>3.6 then
-self.isMobile=true
-else
-self.isMobile=false
-end
-self.speedCruise=self.speedMax*0.7
-self.ammo=self:GetAmmoTot()
-self.radio.On=false
-self.radio.Freq=133
-self.radio.Modu=radio.modulation.AM
-self:SetDefaultRadio(self.radio.Freq,self.radio.Modu,self.radio.On)
-self.option.Formation=template.route.points[1].action
-self.optionDefault.Formation=ENUMS.Formation.Vehicle.OnRoad
+if not self.tacanDefault then
self:SetDefaultTACAN(nil,nil,nil,nil,true)
+end
+if not self.tacan then
self.tacan=UTILS.DeepCopy(self.tacanDefault)
+end
local units=self.group:GetUnits()
local dcsgroup=Group.getByName(self.groupname)
local size0=dcsgroup:getInitialSize()
@@ -78165,6 +81731,7 @@ conditionFailure={},
conditionPush={},
conditionSuccessSet=false,
conditionFailureSet=false,
+repeatDelay=1,
}
_AUFTRAGSNR=0
AUFTRAG.Type={
@@ -78275,7 +81842,7 @@ HELICOPTER="Helicopter",
GROUND="Ground",
NAVAL="Naval",
}
-AUFTRAG.version="1.2.2"
+AUFTRAG.version="1.2.1"
function AUFTRAG:New(Type)
local self=BASE:Inherit(self,FSM:New())
_AUFTRAGSNR=_AUFTRAGSNR+1
@@ -78464,7 +82031,12 @@ mission.categories={AUFTRAG.Category.AIRCRAFT}
return mission
end
function AUFTRAG:NewTANKER(Coordinate,Altitude,Speed,Heading,Leg,RefuelSystem)
-local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg)
+local mission
+if Leg==0 then
+mission=AUFTRAG:NewORBIT_CIRCLE(Coordinate,Altitude,Speed)
+else
+mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg)
+end
mission.type=AUFTRAG.Type.TANKER
mission:_SetLogID()
mission.refuelSystem=RefuelSystem
@@ -78503,7 +82075,7 @@ Altitude=Altitude or 10000
local mission=AUFTRAG:NewORBIT(Coordinate or ZoneCAP:GetCoordinate(),Altitude,Speed or 350,Heading,Leg)
mission.type=AUFTRAG.Type.CAP
mission:_SetLogID()
-mission.engageZone=ZoneCAP
+mission.engageZone=ZoneCAP or Coordinate
mission.engageTargetTypes=TargetTypes or{"Air"}
mission.missionTask=ENUMS.MissionTask.CAP
mission.optionROE=ENUMS.ROE.OpenFire
@@ -78632,10 +82204,27 @@ mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
-function AUFTRAG:NewSTRIKE(Target,Altitude)
+function AUFTRAG:NewSEADInZone(TargetZone,Altitude,TargetTypes,Duration)
+local mission=AUFTRAG:New(AUFTRAG.Type.SEAD)
+mission.engageWeaponType=ENUMS.WeaponFlag.Auto
+mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
+mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000)
+mission.engageZone=TargetZone
+mission.engageTargetTypes=TargetTypes or{"Air Defence"}
+mission.missionTask=ENUMS.MissionTask.SEAD
+mission.missionAltitude=mission.engageAltitude
+mission.missionFraction=0.2
+mission.optionROE=ENUMS.ROE.OpenFire
+mission.optionROT=ENUMS.ROT.EvadeFire
+mission.categories={AUFTRAG.Category.AIRCRAFT}
+mission.DCStask=mission:GetDCSMissionTask()
+mission:SetDuration(Duration or 1800)
+return mission
+end
+function AUFTRAG:NewSTRIKE(Target,Altitude,EngageWeaponType)
local mission=AUFTRAG:New(AUFTRAG.Type.STRIKE)
mission:_TargetFromObject(Target)
-mission.engageWeaponType=ENUMS.WeaponFlag.Auto
+mission.engageWeaponType=EngageWeaponType or ENUMS.WeaponFlag.Auto
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000)
mission.missionTask=ENUMS.MissionTask.GROUNDATTACK
@@ -78647,10 +82236,10 @@ mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
-function AUFTRAG:NewBOMBING(Target,Altitude)
+function AUFTRAG:NewBOMBING(Target,Altitude,EngageWeaponType,Divebomb)
local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING)
mission:_TargetFromObject(Target)
-mission.engageWeaponType=ENUMS.WeaponFlag.Auto
+mission.engageWeaponType=EngageWeaponType or ENUMS.WeaponFlag.Auto
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000)
mission.missionTask=ENUMS.MissionTask.GROUNDATTACK
@@ -78658,6 +82247,7 @@ mission.missionAltitude=mission.engageAltitude*0.8
mission.missionFraction=0.5
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.NoReaction
+mission.optionDivebomb=Divebomb or nil
mission.dTevaluate=5*60
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
@@ -78832,6 +82422,7 @@ mission.engageWeaponType=ENUMS.WeaponFlag.Auto
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionAlarm=0
mission.missionFraction=0.0
+mission.missionWaypointRadius=0.0
mission.dTevaluate=8*60
mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL}
mission.DCStask=mission:GetDCSMissionTask()
@@ -79240,6 +82831,10 @@ function AUFTRAG:SetRepeat(Nrepeat)
self.Nrepeat=Nrepeat or 0
return self
end
+function AUFTRAG:SetRepeatDelay(RepeatDelay)
+self.repeatDelay=RepeatDelay
+return self
+end
function AUFTRAG:SetRepeatOnFailure(Nrepeat)
self.NrepeatFailure=Nrepeat or 0
return self
@@ -80054,6 +83649,11 @@ groupdata.waypointcoordinate=coordinate
end
return self
end
+function AUFTRAG:SetIngressCoordinate(coordinate)
+self.missionIngressCoord=coordinate
+self.missionIngressCoordAlt=UTILS.MetersToFeet(coordinate.y)or 10000
+return self
+end
function AUFTRAG:GetGroupWaypointCoordinate(opsgroup)
local groupdata=self:GetGroupData(opsgroup)
if groupdata then
@@ -80102,6 +83702,7 @@ return groupdata.waypointEgressUID
end
end
function AUFTRAG:CheckGroupsDone()
+local fsmState=self:GetState()
for groupname,data in pairs(self.groupdata)do
local groupdata=data
if groupdata then
@@ -80143,6 +83744,10 @@ if self:IsStarted()and self:CountOpsGroups()==0 then
self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] but count of alive OPSGROUP is zero. Mission DONE!",self.status,self:GetState()))
return true
end
+if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and self:CountOpsGroups()>0 then
+self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState()))
+return false
+end
return true
end
function AUFTRAG:OnEventUnitLost(EventData)
@@ -80283,7 +83888,7 @@ if repeatme then
self.repeatedSuccess=self.repeatedSuccess+1
local N=math.max(self.NrepeatSuccess,self.Nrepeat)
self:T(self.lid..string.format("Mission SUCCESS! Repeating mission for the %d time (max %d times) ==> Repeat mission!",self.repeated+1,N))
-self:Repeat()
+self:__Repeat(self.repeatDelay)
else
self:T(self.lid..string.format("Mission SUCCESS! Number of max repeats %d reached ==> Stopping mission!",self.repeated+1))
self:Stop()
@@ -80303,7 +83908,7 @@ if repeatme then
self.repeatedFailure=self.repeatedFailure+1
local N=math.max(self.NrepeatFailure,self.Nrepeat)
self:T(self.lid..string.format("Mission FAILED! Repeating mission for the %d time (max %d times) ==> Repeat mission!",self.repeated+1,N))
-self:Repeat()
+self:__Repeat(self.repeatDelay)
else
self:T(self.lid..string.format("Mission FAILED! Number of max repeats %d reached ==> Stopping mission!",self.repeated+1))
self:Stop()
@@ -80586,18 +84191,52 @@ function AUFTRAG:SetMissionWaypointRandomization(Radius)
self.missionWaypointRadius=Radius
return self
end
-function AUFTRAG:SetMissionEgressCoord(Coordinate,Altitude)
+function AUFTRAG:SetMissionEgressCoord(Coordinate,Altitude,Speed)
if Coordinate:IsInstanceOf("ZONE_BASE")then
Coordinate=Coordinate:GetCoordinate()
end
self.missionEgressCoord=Coordinate
if Altitude then
self.missionEgressCoord.y=UTILS.FeetToMeters(Altitude)
+self.missionEgressCoordAlt=UTILS.FeetToMeters(Altitude)
end
+self.missionEgressCoordSpeed=Speed and Speed or nil
+return self
+end
+function AUFTRAG:SetMissionIngressCoord(Coordinate,Altitude,Speed)
+if Coordinate:IsInstanceOf("ZONE_BASE")then
+Coordinate=Coordinate:GetCoordinate()
+end
+self.missionIngressCoord=Coordinate
+if Altitude then
+self.missionIngressCoord.y=UTILS.FeetToMeters(Altitude)
+self.missionIngressCoordAlt=UTILS.FeetToMeters(Altitude or 10000)
+end
+self.missionIngressCoordSpeed=Speed and Speed or nil
+return self
+end
+function AUFTRAG:SetMissionHoldingCoord(Coordinate,Altitude,Speed,Duration)
+if Coordinate:IsInstanceOf("ZONE_BASE")then
+Coordinate=Coordinate:GetCoordinate()
+end
+self.missionHoldingCoord=Coordinate
+self.missionHoldingDuration=Duration or 900
+if Altitude then
+self.missionHoldingCoord.y=UTILS.FeetToMeters(Altitude)
+self.missionHoldingCoordAlt=UTILS.FeetToMeters(Altitude or 10000)
+end
+self.missionHoldingCoordSpeed=Speed and Speed or nil
+return self
end
function AUFTRAG:GetMissionEgressCoord()
return self.missionEgressCoord
end
+function AUFTRAG:GetMissionIngressCoord()
+return self.missionIngressCoord
+end
+function AUFTRAG:GetMissionHoldingCoord()
+return self.missionHoldingCoord
+end
function AUFTRAG:_GetMissionWaypointCoordSet()
if self.missionWaypointCoord then
local coord=self.missionWaypointCoord
@@ -80615,8 +84254,20 @@ coord.y=self.missionAltitude
end
return coord
end
-local waypointcoord=COORDINATE:New(0,0,0)
local coord=group:GetCoordinate()
+if self.missionHoldingCoord then
+coord=self.missionHoldingCoord
+if self.missionHoldingCoorddAlt then
+coord:SetAltitude(self.missionHoldingCoordAlt,true)
+end
+end
+if self.missionIngressCoord then
+coord=self.missionIngressCoord
+if self.missionIngressCoordAlt then
+coord:SetAltitude(self.missionIngressCoordAlt,true)
+end
+end
+local waypointcoord=COORDINATE:New(0,0,0)
if coord then
waypointcoord=coord:GetIntermediateCoordinate(self:GetTargetCoordinate(),self.missionFraction)
else
@@ -80714,8 +84365,11 @@ table.insert(self.enrouteTasks,DCStask)
elseif self.type==AUFTRAG.Type.BAI then
self:_GetDCSAttackTask(self.engageTarget,DCStasks)
elseif self.type==AUFTRAG.Type.BOMBING then
-local DCStask=CONTROLLABLE.TaskBombing(nil,self:GetTargetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType,Divebomb)
+local coords=self.engageTarget:GetCoordinates()
+for _,coord in pairs(coords)do
+local DCStask=CONTROLLABLE.TaskBombing(nil,coord:GetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType,self.optionDivebomb)
table.insert(DCStasks,DCStask)
+end
elseif self.type==AUFTRAG.Type.STRAFING then
local DCStask=CONTROLLABLE.TaskStrafing(nil,self:GetTargetVec2(),self.engageQuantity,self.engageLength,self.engageWeaponType,self.engageWeaponExpend,self.engageDirection,self.engageAsGroup)
table.insert(DCStasks,DCStask)
@@ -80726,7 +84380,14 @@ elseif self.type==AUFTRAG.Type.BOMBCARPET then
local DCStask=CONTROLLABLE.TaskCarpetBombing(nil,self:GetTargetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType,self.engageLength)
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.CAP then
-local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil,self.engageZone:GetVec2(),self.engageZone:GetRadius(),self.engageTargetTypes,Priority)
+local Vec2=self.engageZone:GetVec2()
+local Radius
+if self.engageZone:IsInstanceOf("COORDINATE")then
+Radius=UTILS.NMToMeters(20)
+else
+Radius=self.engageZone:GetRadius()
+end
+local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil,Vec2,Radius,self.engageTargetTypes,Priority)
table.insert(self.enrouteTasks,DCStask)
elseif self.type==AUFTRAG.Type.CAS then
local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil,self.engageZone:GetVec2(),self.engageZone:GetRadius(),self.engageTargetTypes,Priority)
@@ -80778,10 +84439,29 @@ param.lastindex=nil
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.SEAD then
+if self.engageZone then
+self.engageZone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT})
+local ScanUnitSet=self.engageZone:GetScannedSetUnit()
+local SeadUnitSet=SET_UNIT:New()
+for _,_unit in pairs(ScanUnitSet.Set)do
+local unit=_unit
+if unit and unit:IsAlive()and unit:HasSEAD()then
+self:T("Adding UNIT for SEAD: "..unit:GetName())
+local task=CONTROLLABLE.TaskAttackUnit(nil,unit,GroupAttack,AI.Task.WeaponExpend.ALL,1,Direction,self.engageAltitude,2956984318)
+table.insert(DCStasks,task)
+SeadUnitSet:AddUnit(unit)
+end
+end
+self.engageTarget=TARGET:New(SeadUnitSet)
+else
self:_GetDCSAttackTask(self.engageTarget,DCStasks)
+end
elseif self.type==AUFTRAG.Type.STRIKE then
-local DCStask=CONTROLLABLE.TaskAttackMapObject(nil,self:GetTargetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType)
+local coords=self.engageTarget:GetCoordinates()
+for _,coord in pairs(coords)do
+local DCStask=CONTROLLABLE.TaskAttackMapObject(nil,coord:GetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType)
table.insert(DCStasks,DCStask)
+end
elseif self.type==AUFTRAG.Type.TANKER or self.type==AUFTRAG.Type.RECOVERYTANKER then
local DCStask=CONTROLLABLE.EnRouteTaskTanker(nil)
table.insert(self.enrouteTasks,DCStask)
@@ -81132,7 +84812,7 @@ end
do
AWACS={
ClassName="AWACS",
-version="0.2.67",
+version="0.2.72",
lid="",
coalition=coalition.side.BLUE,
coalitiontxt="blue",
@@ -81504,7 +85184,7 @@ self.HasEscorts=false
self.EscortTemplate=""
self.EscortMission={}
self.EscortMissionReplacement={}
-self.PathToSRS="C:\\Program Files\\DCS-SimpleRadio-Standalone"
+self.PathToSRS="C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.Gender="female"
self.Culture="en-GB"
self.Voice=nil
@@ -81593,6 +85273,8 @@ self:AddTransition("*","FlightOnMission","*")
self:AddTransition("*","Intercept","*")
self:AddTransition("*","InterceptSuccess","*")
self:AddTransition("*","InterceptFailure","*")
+self:AddTransition("*","VIDSuccess","*")
+self:AddTransition("*","VIDFailure","*")
self:AddTransition("*","Stop","Stopped")
local text=string.format("%sAWACS Version %s Initiated",self.lid,self.version)
self:I(text)
@@ -81859,7 +85541,7 @@ if Event.IniCoalition==self.coalition then
self:_SetClientMenus()
end
end
-if Event.id==EVENTS.PlayerLeaveUnit then
+if Event.id==EVENTS.PlayerLeaveUnit and Event.IniGroupName then
self:T("Player group left unit: "..Event.IniGroupName)
self:T("Player name left: "..Event.IniPlayerName)
self:T("Coalition = "..UTILS.GetCoalitionName(Event.IniCoalition))
@@ -82006,7 +85688,9 @@ self:T(self.lid.."SetAdditionalZone")
self.BorderZone=Zone
if self.debug then
Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true)
+if self.AllowMarkers then
MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition)
+end
elseif Draw then
Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true)
end
@@ -82019,8 +85703,10 @@ if Draw then
Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true)
elseif self.debug then
Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true)
+if self.AllowMarkers then
MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition)
end
+end
return self
end
function AWACS:DrawFEZ()
@@ -82055,7 +85741,7 @@ return self
end
function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Backend)
self:T(self.lid.."SetSRS")
-self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
+self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.Gender=Gender or MSRS.gender or"male"
self.Culture=Culture or MSRS.culture or"en-US"
self.Port=Port or MSRS.port or 5002
@@ -82099,7 +85785,7 @@ self.MaxAIonCAP=MaxAICap or 4
self.AICAPCAllName=Callsign or CALLSIGN.Aircraft.Colt
return self
end
-function AWACS:SetEscort(EscortNumber)
+function AWACS:SetEscort(EscortNumber,Formation,OffsetVector,EscortEngageMaxDistance)
self:T(self.lid.."SetEscort")
if EscortNumber and EscortNumber>0 then
self.HasEscorts=true
@@ -82108,6 +85794,9 @@ else
self.HasEscorts=false
self.EscortNumber=0
end
+self.EscortFormation=Formation
+self.OffsetVec=OffsetVector or{x=500,y=100,z=500}
+self.EscortEngageMaxDistance=EscortEngageMaxDistance or 45
return self
end
function AWACS:_MessageVector(GID,Tag,Coordinate,Angels)
@@ -82137,10 +85826,20 @@ self:T(self.lid.."_StartEscorts")
local AwacsFG=self.AwacsFG
local group=AwacsFG:GetGroup()
local timeonstation=(self.EscortsTimeOnStation+self.ShiftChangeTime)*3600
+local OffsetX=500
+local OffsetY=500
+local OffsetZ=500
+if self.OffsetVec then
+OffsetX=self.OffsetVec.x or 500
+OffsetY=self.OffsetVec.y or 500
+OffsetZ=self.OffsetVec.z or 500
+end
for i=1,self.EscortNumber do
-local escort=AUFTRAG:NewESCORT(group,{x=-100*((i+(i%2))/2),y=0,z=(100+100*((i+(i%2))/2))*(-1)^i},45,{"Air"})
-escort:SetRequiredAssets(1)
+local escort=AUFTRAG:NewESCORT(group,{x=OffsetX*((i+(i%2))/2),y=OffsetY*((i+(i%2))/2),z=(OffsetZ+OffsetZ*((i+(i%2))/2))*(-1)^i},self.EscortEngageMaxDistance,{"Air"})
escort:SetTime(nil,timeonstation)
+if self.Escortformation then
+escort:SetFormation(self.Escortformation)
+end
escort:SetMissionRange(self.MaxMissionRange)
self.AirWing:AddMission(escort)
self.CatchAllMissions[#self.CatchAllMissions+1]=escort
@@ -82852,11 +86551,13 @@ self.Contacts:Push(cluster,CID)
local vidpos=self.gettext:GetEntry("VIDPOS",self.locale)
text=string.format(vidpos,Callsign,self.callsigntxt,Declaration)
self:T(text)
+self:__VIDSuccess(3,GID,group,cluster)
else
self:T("Contact VID not close enough")
local vidneg=self.gettext:GetEntry("VIDNEG",self.locale)
text=string.format(vidneg,Callsign,self.callsigntxt)
self:T(text)
+self:__VIDFailure(3,GID,group,cluster)
end
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true)
end
@@ -83368,6 +87069,10 @@ basemenu=basemenu,
checkin=checkin,
}
self.clientmenus:Push(menus,cgrpname)
+local GID,hasentry=self:_GetManagedGrpID(cgrp)
+if hasentry then
+self:_CheckOut(cgrp,GID,true)
+end
end
end
else
@@ -83478,11 +87183,15 @@ AnchorStackOne.StationName=newname
if self.debug then
AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true)
local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
+if self.AllowMarkers then
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
+end
else
local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
+if self.AllowMarkers then
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
end
+end
self.AnchorStacks:Push(AnchorStackOne,newname)
self.PlayerStationName=newname
return self
@@ -83511,11 +87220,15 @@ AnchorStackOne.StationName=newname
if self.debug then
AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true)
local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
+if self.AllowMarkers then
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
+end
else
local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
+if self.AllowMarkers then
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
end
+end
self.AnchorStacks:Push(AnchorStackOne,newname)
else
local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1)
@@ -83536,11 +87249,15 @@ AnchorStackOne.StationName=newname
if self.debug then
AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true)
local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
+if self.AllowMarkers then
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
+end
else
local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
+if self.AllowMarkers then
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
end
+end
self.AnchorStacks:Push(AnchorStackOne,newname)
end
return true,self.AnchorStacks:GetSize()
@@ -84171,11 +87888,15 @@ AnchorStackOne.StationName=newname
if self.debug then
AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true)
local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
+if self.AllowMarkers then
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
+end
else
local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
+if self.AllowMarkers then
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
end
+end
self.AnchorStacks:Push(AnchorStackOne,newname)
AirWing.HasOwnStation=true
AirWing.StationName=newname
@@ -84814,28 +88535,41 @@ self.ControlZone:DrawZone(self.coalition,{0,1,0},1,{1,0,0},0.05,3,true)
self.OpsZone:DrawZone(self.coalition,{1,0,0},1,{1,0,0},0.2,5,true)
local AOCoordString=self.AOCoordinate:ToStringLLDDM()
local Rocktag=string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString)
+if self.AllowMarkers then
MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition)
+end
self.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true)
local stationtag=string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM())
if not self.GCI then
+if self.AllowMarkers then
MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
+end
self.OrbitZone:DrawZone(self.coalition,{0,1,0},1,{0,1,0},0.2,5,true)
+if self.AllowMarkers then
MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition)
end
+end
else
local AOCoordString=self.AOCoordinate:ToStringLLDDM()
local Rocktag=string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString)
+if self.AllowMarkers then
MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition)
+end
if not self.GCI then
+if self.AllowMarkers then
MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition)
end
+end
local stationtag=string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM())
+if self.AllowMarkers then
MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
end
+end
if not self.GCI then
local AwacsAW=self.AirWing
local mission=AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg)
mission:SetMissionRange(self.MaxMissionRange)
+mission:SetRequiredAttribute({GROUP.Attribute.AIR_AWACS})
local timeonstation=(self.AwacsTimeOnStation+self.ShiftChangeTime)*3600
mission:SetTime(nil,timeonstation)
self.CatchAllMissions[#self.CatchAllMissions+1]=mission
@@ -85060,17 +88794,18 @@ else
report:Add("***** Cannot obtain (yet) this missions OpsGroup!")
end
report:Add("====================")
+local RESMission
if self.ShiftChangeEscortsFlag and self.ShiftChangeEscortsRequested then
-ESmission=self.EscortMissionReplacement[i]
-local esstatus=ESmission:GetState()
-local ESmissiontime=(timer.getTime()-self.EscortsTimeStamp)
-local ESTOSLeft=UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600)-ESmissiontime),0)
+RESMission=self.EscortMissionReplacement[i]
+local esstatus=RESMission:GetState()
+local RESMissiontime=(timer.getTime()-self.EscortsTimeStamp)
+local ESTOSLeft=UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600)-RESMissiontime),0)
ESTOSLeft=UTILS.Round(ESTOSLeft/60,0)
local ChangeTime=UTILS.Round(((self.ShiftChangeTime*3600)/60),0)
report:Add("ESCORTS REPLACEMENT:")
report:Add(string.format("Auftrag Status: %s",esstatus))
report:Add(string.format("TOS Left: %d min",ESTOSLeft))
-local OpsGroups=ESmission:GetOpsGroups()
+local OpsGroups=RESMission:GetOpsGroups()
local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups)
if OpsGroup then
local OpsName=OpsGroup:GetName()or"Unknown"
@@ -85081,11 +88816,11 @@ report:Add(string.format("Mission FG State %s",OpsGroup:GetState()))
else
report:Add("***** Cannot obtain (yet) this missions OpsGroup!")
end
-if ESmission:IsExecuting()then
+if RESMission and RESMission:IsExecuting()then
self.ShiftChangeEscortsFlag=false
self.ShiftChangeEscortsRequested=false
if ESmission and ESmission:IsNotOver()then
-ESmission:Cancel()
+ESmission:__Cancel(1)
end
self.EscortMission[i]=self.EscortMissionReplacement[i]
self.EscortMissionReplacement[i]=nil
@@ -87127,11 +90862,19 @@ descriptors={},
properties={},
operations={},
}
-COHORT.version="0.3.5"
+COHORT.version="0.3.7"
+_COHORTNAMES={}
function COHORT:New(TemplateGroupName,Ngroups,CohortName)
+local name=tostring(CohortName or TemplateGroupName)
+if UTILS.IsAnyInTable(_COHORTNAMES,name)then
+env.error(string.format('ERROR: cannot create cohort "%s" because another cohort with that name already exists. Names must be unique!',name))
+return nil
+else
+table.insert(_COHORTNAMES,name)
+end
local self=BASE:Inherit(self,FSM:New())
self.templatename=TemplateGroupName
-self.name=tostring(CohortName or TemplateGroupName)
+self.name=name
self.lid=string.format("COHORT %s | ",self.name)
self.templategroup=GROUP:FindByName(self.templatename)
if not self.templategroup then
@@ -87270,9 +91013,10 @@ function COHORT:SetMissionRange(Range)
self.engageRange=UTILS.NMToMeters(Range or 150)
return self
end
-function COHORT:SetCallsign(Callsign,Index)
+function COHORT:SetCallsign(Callsign,Index,CallsignString)
self.callsignName=Callsign
self.callsignIndex=Index
+self.callsignClearName=CallsignString
self.callsign={}
self.callsign.NumberSquad=Callsign
self.callsign.NumberGroup=Index
@@ -87372,6 +91116,9 @@ self.callsigncounter=self.callsigncounter+2
else
self.callsigncounter=self.callsigncounter+1
end
+callsign["name"]=self.callsignClearName or UTILS.GetCallsignName(self.callsignName)or"None"
+callsign["name"]=string.format("%s%d%d",callsign["name"],callsign[2],callsign[3])
+callsign[4]=callsign["name"]
Asset.callsign[i]=callsign
self:T3({callsign=callsign})
end
@@ -87894,6 +91641,7 @@ gcicapZones={},
awacsZones={},
tankerZones={},
limitMission={},
+maxMissionsAssignPerCycle=1,
}
COMMANDER.version="0.1.4"
function COMMANDER:New(Coalition,Alias)
@@ -88518,6 +92266,7 @@ if mission.importance and mission.importance=(self.maxMissionsAssignPerCycle or 1)then
return
end
+end
else
end
end
end
+function COMMANDER:SetMaxMissionsAssignPerCycle(MaxMissionsAssignPerCycle)
+self.maxMissionsAssignPerCycle=MaxMissionsAssignPerCycle or 1
+return self
+end
function COMMANDER:_GetCohorts(Legions,Cohorts,Operation)
local function CheckOperation(LegionOrCohort)
if#self.opsqueue==0 then
@@ -88576,7 +92332,10 @@ local cohorts={}
if(Legions and#Legions>0)or(Cohorts and#Cohorts>0)then
for _,_legion in pairs(Legions or{})do
local legion=_legion
-local Runway=legion:IsAirwing()and legion:IsRunwayOperational()or true
+local Runway=true
+if legion:IsAirwing()then
+Runway=legion:IsRunwayOperational()and legion.airbase and legion.airbase:GetCoalition()==legion:GetCoalition()
+end
if legion:IsRunning()and Runway then
for _,_cohort in pairs(legion.cohorts)do
local cohort=_cohort
@@ -88595,7 +92354,10 @@ end
else
for _,_legion in pairs(self.legions)do
local legion=_legion
-local Runway=legion:IsAirwing()and legion:IsRunwayOperational()or true
+local Runway=true
+if legion:IsAirwing()then
+Runway=legion:IsRunwayOperational()and legion.airbase and legion.airbase:GetCoalition()==legion:GetCoalition()
+end
if legion:IsRunning()and Runway then
for _,_cohort in pairs(legion.cohorts)do
local cohort=_cohort
@@ -88630,8 +92392,10 @@ if can and(MaxWeight==nil or cohort.cargobayLimit>MaxWeight)then
MaxWeight=cohort.cargobayLimit
end
end
+if MaxWeight then
self:T(self.lid..string.format("Largest cargo bay available=%.1f",MaxWeight))
end
+end
local legions=self.legions
local cohorts=nil
if Mission.specialLegions or Mission.specialCohorts then
@@ -91051,7 +94815,7 @@ GRADUATE="Graduate",
INSTRUCTOR="Instructor",
}
FLIGHTGROUP.Players={}
-FLIGHTGROUP.version="1.0.2"
+FLIGHTGROUP.version="1.0.3"
function FLIGHTGROUP:New(group)
local og=_DATABASE:GetOpsGroup(group)
if og then
@@ -91059,7 +94823,7 @@ og:I(og.lid..string.format("WARNING: OPS group already exists in data base!"))
return og
end
local self=BASE:Inherit(self,OPSGROUP:New(group))
-self.lid=string.format("FLIGHTGROUP %s | ",self.groupname)
+self.lid=string.format("FLIGHTGROUP %s | ",self.groupname or"N/A")
self:SetDefaultROE()
self:SetDefaultROT()
self:SetDefaultEPLRS(self.isEPLRS)
@@ -91169,6 +94933,41 @@ self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_JETT,not Switch)
end
return self
end
+function FLIGHTGROUP:SetOptionLandingStraightIn()
+self.OptionLandingStraightIn=true
+if self:GetGroup():IsAlive()then
+self:GetGroup():SetOptionLandingStraightIn()
+end
+return self
+end
+function FLIGHTGROUP:SetOptionLandingForcePair()
+self.OptionLandingForcePair=true
+if self:GetGroup():IsAlive()then
+self:GetGroup():SetOptionLandingForcePair()
+end
+return self
+end
+function FLIGHTGROUP:SetOptionLandingRestrictPair()
+self.OptionLandingRestrictPair=true
+if self:GetGroup():IsAlive()then
+self:GetGroup():SetOptionLandingRestrictPair()
+end
+return self
+end
+function FLIGHTGROUP:SetOptionLandingOverheadBreak()
+self.OptionLandingOverheadBreak=true
+if self:GetGroup():IsAlive()then
+self:GetGroup():SetOptionLandingOverheadBreak()
+end
+return self
+end
+function FLIGHTGROUP:SetOptionPreferVertical()
+self.OptionPreferVertical=true
+if self:GetGroup():IsAlive()then
+self:GetGroup():OptionPreferVerticalLanding()
+end
+return self
+end
function FLIGHTGROUP:SetReadyForTakeoff(ReadyTO,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,FLIGHTGROUP.SetReadyForTakeoff,self,ReadyTO,0)
@@ -91433,6 +95232,15 @@ if timer.getAbsTime()>self.Twaiting+self.dTwait then
end
end
end
+if mission and mission.missionHoldingCoord and self.isHoldingAtHoldingPoint==true then
+self:T(self.lid.."...yes")
+if mission:IsReadyToPush()then
+self.flaghold:Set(1)
+self.Twaiting=nil
+self.dTwait=nil
+self.isHoldingAtHoldingPoint=false
+end
+end
if mission and mission.updateDCSTask then
if(mission:GetType()==AUFTRAG.Type.ORBIT or mission:GetType()==AUFTRAG.Type.RECOVERYTANKER or mission:GetType()==AUFTRAG.Type.CAP)and mission.orbitVec2 then
local vec2=mission:GetTargetVec2()
@@ -91746,6 +95554,7 @@ self:__ElementAirborne(0.01,Element)
end
function FLIGHTGROUP:onafterElementAirborne(From,Event,To,Element)
self:T2(self.lid..string.format("Element airborne %s",Element.name))
+self:_SetElementParkingFree(Element)
self:_UpdateStatus(Element,OPSGROUP.ElementStatus.AIRBORNE)
end
function FLIGHTGROUP:onafterElementLanded(From,Event,To,Element,airbase)
@@ -92289,7 +96098,7 @@ if self:IsAlive()then
local allowed=true
local Tsuspend=nil
if airbase==nil then
-self:T(self.lid.."ERROR: Airbase is nil in LandAtAirase() call!")
+self:T(self.lid.."ERROR: Airbase is nil in LandAtAirbase() call!")
allowed=false
end
if airbase and airbase:GetCoalition()~=self.group:GetCoalition()and airbase:GetCoalition()>0 then
@@ -92613,7 +96422,10 @@ flightgroup.Twaiting=nil
flightgroup.dTwait=nil
flightgroup:_CheckGroupDone(0.1)
end
-function FLIGHTGROUP:_InitGroup(Template)
+function FLIGHTGROUP:_InitGroup(Template,Delay)
+if Delay and Delay>0 then
+self:ScheduleOnce(Delay,FLIGHTGROUP._InitGroup,self,Template,0)
+else
if self.groupinitialized then
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
return
@@ -92654,8 +96466,12 @@ self.optionDefault.Formation=ENUMS.Formation.RotaryWing.EchelonLeft.D300
else
self.optionDefault.Formation=ENUMS.Formation.FixedWing.EchelonLeft.Group
end
+if not self.tacanDefault then
self:SetDefaultTACAN(nil,nil,nil,nil,true)
+end
+if not self.tacan then
self.tacan=UTILS.DeepCopy(self.tacanDefault)
+end
self.isAI=not self:_IsHuman(group)
if not self.isAI then
self.menu=self.menu or{}
@@ -92673,6 +96489,7 @@ for _,unit in pairs(units)do
self:_AddElementByName(unit:GetName())
end
self.groupinitialized=true
+end
return self
end
function FLIGHTGROUP:GetHomebaseFromWaypoints()
@@ -92813,29 +96630,6 @@ end
end
return nil
end
-function FLIGHTGROUP:InitWaypoints()
-self.waypoints0=self.group:GetTemplateRoutePoints()
-self.waypoints={}
-for index,wp in pairs(self.waypoints0)do
-local waypoint=self:_CreateWaypoint(wp)
-self:_AddWaypoint(waypoint)
-end
-self.homebase=self.homebase or self:GetHomebaseFromWaypoints()
-self.destbase=self.destbase or self:GetDestinationFromWaypoints()
-self.currbase=self:GetHomebaseFromWaypoints()
-if self.destbase and#self.waypoints>1 then
-table.remove(self.waypoints,#self.waypoints)
-else
-self.destbase=self.homebase
-end
-self:T(self.lid..string.format("Initializing %d waypoints. Homebase %s ==> %s Destination",#self.waypoints,self.homebase and self.homebase:GetName()or"unknown",self.destbase and self.destbase:GetName()or"uknown"))
-if#self.waypoints>0 then
-if#self.waypoints==1 then
-self:_PassedFinalWaypoint(true,"FLIGHTGROUP:InitWaypoints #self.waypoints==1")
-end
-end
-return self
-end
function FLIGHTGROUP:AddWaypoint(Coordinate,Speed,AfterWaypointWithID,Altitude,Updateroute)
local coordinate=self:_CoordinateFromObject(Coordinate)
local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID)
@@ -92959,6 +96753,10 @@ end
function FLIGHTGROUP:GetParkingSpot(element,maxdist,airbase)
local coord=element.unit:GetCoordinate()
airbase=airbase or self:GetClosestAirbase()
+if airbase==nil then
+self:T(self.lid.."No airbase found for element "..element.name)
+return nil
+end
local parking=airbase.parking
if airbase and airbase:IsShip()then
if#parking>1 then
@@ -93029,12 +96827,14 @@ local clients=_DATABASE.CLIENTS
local coords={}
for clientname,client in pairs(clients)do
local template=_DATABASE:GetGroupTemplateFromUnitName(clientname)
+if template then
local units=template.units
for i,unit in pairs(units)do
local coord=COORDINATE:New(unit.x,unit.alt,unit.y)
coords[unit.name]=coord
end
end
+end
return coords
end
local airbasecategory=airbase:GetAirbaseCategory()
@@ -94510,13 +98310,13 @@ ClassName="INTEL_DLINK",
verbose=0,
lid=nil,
alias=nil,
-cachetime=300,
+cachetime=120,
interval=20,
contacts={},
clusters={},
contactcoords={},
}
-INTEL_DLINK.version="0.0.1"
+INTEL_DLINK.version="0.0.2"
function INTEL_DLINK:New(Intels,Alias,Interval,Cachetime)
local self=BASE:Inherit(self,FSM:New())
self.intels=Intels or{}
@@ -94528,9 +98328,9 @@ self.alias=tostring(Alias)
else
self.alias="SPECTRE"
end
-self.cachetime=Cachetime or 300
self.interval=Interval or 20
self.lid=string.format("INTEL_DLINK %s | ",self.alias)
+self:SetDLinkCacheTime(Cachetime or 120)
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Collect","*")
@@ -94552,6 +98352,11 @@ self:I(self.lid..text)
self:__Collect(-math.random(1,10))
return self
end
+function INTEL_DLINK:SetDLinkCacheTime(seconds)
+self.cachetime=math.abs(seconds or 120)
+self:I(self.lid.."Caching for "..self.cachetime.." seconds.")
+return self
+end
function INTEL_DLINK:onbeforeCollect(From,Event,To)
self:T({From,Event,To})
self:T("Contacts Data Gathering")
@@ -94635,7 +98440,7 @@ transportqueue={},
cohorts={},
}
LEGION.RandomAssetScore=1
-LEGION.version="0.5.0"
+LEGION.version="0.5.1"
function LEGION:New(WarehouseName,LegionName)
local self=BASE:Inherit(self,WAREHOUSE:New(WarehouseName,LegionName))
if not self then
@@ -94716,6 +98521,14 @@ end
end
return self
end
+function LEGION:DelAsset(Asset)
+if Asset.cohort then
+Asset.cohort:DelAsset(Asset)
+else
+self:E(self.lid..string.format("ERROR: Asset has not cohort attached. Cannot remove it from legion!"))
+end
+return self
+end
function LEGION:RelocateCohort(Cohort,Legion,Delay,NcarriersMin,NcarriersMax,TransportLegions)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,LEGION.RelocateCohort,self,Cohort,Legion,0,NcarriersMin,NcarriersMax,TransportLegions)
@@ -95261,6 +99074,7 @@ self:GetParent(self,LEGION).onafterAssetDead(self,From,Event,To,asset,request)
if self.commander and self.commander.chief then
self.commander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname})
end
+self:DelAsset(asset)
end
function LEGION:onafterDestroyed(From,Event,To)
self:T(self.lid.."Legion warehouse destroyed!")
@@ -95316,6 +99130,7 @@ if self:IsAirwing()then
opsgroup=FLIGHTGROUP:New(asset.spawngroupname)
elseif self:IsBrigade()then
opsgroup=ARMYGROUP:New(asset.spawngroupname)
+opsgroup:SetValidateAndRepositionGroundUnits(self.ValidateAndRepositionGroundUnits)
elseif self:IsFleet()then
opsgroup=NAVYGROUP:New(asset.spawngroupname)
else
@@ -95324,6 +99139,7 @@ end
opsgroup:_SetLegion(self)
opsgroup.cohort=self:_GetCohortOfAsset(asset)
opsgroup.homebase=self.airbase
+opsgroup.destbase=self.airbase
opsgroup.homezone=self.spawnzone
if opsgroup.cohort.weaponData then
local text="Weapon data for group:"
@@ -95673,7 +99489,10 @@ local cohorts={}
if(Legions and#Legions>0)or(Cohorts and#Cohorts>0)then
for _,_legion in pairs(Legions or{})do
local legion=_legion
-local Runway=legion:IsAirwing()and legion:IsRunwayOperational()or true
+local Runway=true
+if legion:IsAirwing()then
+Runway=legion:IsRunwayOperational()and legion.airbase and legion.airbase:GetCoalition()==legion:GetCoalition()
+end
if legion:IsRunning()and Runway then
for _,_cohort in pairs(legion.cohorts)do
local cohort=_cohort
@@ -96205,7 +100024,7 @@ Qintowind={},
pathCorridor=400,
engage={},
}
-NAVYGROUP.version="1.0.2"
+NAVYGROUP.version="1.0.3"
function NAVYGROUP:New(group)
local og=_DATABASE:GetOpsGroup(group)
if og then
@@ -96427,7 +100246,7 @@ else
return false
end
end
-function NAVYGROUP:Status(From,Event,To)
+function NAVYGROUP:Status()
local fsmstate=self:GetState()
local alive=self:IsAlive()
local freepath=0
@@ -96526,6 +100345,24 @@ text=text..string.format("\n[%d] ID=%d Start=%s Stop=%s Open=%s Over=%s",i,recov
end
self:I(self.lid..text)
end
+if self.verbose>=2 then
+local text="Elements:"
+for i,_element in pairs(self.elements)do
+local element=_element
+local name=element.name
+local status=element.status
+local unit=element.unit
+local life,life0=self:GetLifePoints(element)
+local life0=element.life0
+local ammo=self:GetAmmoElement(element)
+text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d, cargo=%d/%d kg",
+i,name,status,life,life0,ammo.Guns,ammo.Rockets,ammo.Bombs,ammo.Missiles,element.weightCargo,element.weightMaxCargo)
+end
+if#self.elements==0 then
+text=text.." none!"
+end
+self:I(self.lid..text)
+end
if self:IsCruising()and self.detectionOn and self.engagedetectedOn then
local targetgroup,targetdist=self:_GetDetectedTarget()
if targetgroup then
@@ -96543,7 +100380,7 @@ end
function NAVYGROUP:onafterSpawned(From,Event,To)
self:T(self.lid..string.format("Group spawned!"))
if self.verbose>=1 then
-local text=string.format("Initialized Navy Group %s:\n",self.groupname)
+local text=string.format("Initialized Navy Group %s [GID=%d]:\n",self.groupname,self.group:GetID())
text=text..string.format("Unit type = %s\n",self.actype)
text=text..string.format("Speed max = %.1f Knots\n",UTILS.KmphToKnots(self.speedMax))
text=text..string.format("Speed cruise = %.1f Knots\n",UTILS.KmphToKnots(self.speedCruise))
@@ -96886,7 +100723,10 @@ self:__UpdateRoute(-0.01)
end
return waypoint
end
-function NAVYGROUP:_InitGroup(Template)
+function NAVYGROUP:_InitGroup(Template,Delay)
+if Delay and Delay>0 then
+self:ScheduleOnce(Delay,NAVYGROUP._InitGroup,self,Template,0)
+else
if self.groupinitialized then
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
return
@@ -96909,10 +100749,18 @@ self.radio.Freq=tonumber(template.units[1].frequency)/1000000
self.radio.Modu=tonumber(template.units[1].modulation)
self.optionDefault.Formation="Off Road"
self.option.Formation=self.optionDefault.Formation
+if not self.tacanDefault then
self:SetDefaultTACAN(nil,nil,nil,nil,true)
+end
+if not self.tacan then
self.tacan=UTILS.DeepCopy(self.tacanDefault)
+end
+if not self.iclsDefault then
self:SetDefaultICLS(nil,nil,nil,true)
+end
+if not self.icls then
self.icls=UTILS.DeepCopy(self.iclsDefault)
+end
local units=self.group:GetUnits()
local dcsgroup=Group.getByName(self.groupname)
local size0=dcsgroup:getInitialSize()
@@ -96923,6 +100771,7 @@ for _,unit in pairs(units)do
self:_AddElementByName(unit:GetName())
end
self.groupinitialized=true
+end
return self
end
function NAVYGROUP:_CheckFreePath(DistanceMax,dx)
@@ -97754,7 +101603,7 @@ ASSIGNED="assigned to carrier",
BOARDING="boarding",
LOADED="loaded",
}
-OPSGROUP.version="1.0.1"
+OPSGROUP.version="1.0.4"
function OPSGROUP:New(group)
local self=BASE:Inherit(self,FSM:New())
if type(group)=="string"then
@@ -97942,7 +101791,7 @@ self.legionReturn=false
else
self.legionReturn=true
end
-self:T(self.lid..string.format("Setting ReturnToLetion=%s",tostring(self.legionReturn)))
+self:T(self.lid..string.format("Setting ReturnToLegion=%s",tostring(self.legionReturn)))
return self
end
function OPSGROUP:SetDefaultSpeed(Speed)
@@ -98103,21 +101952,43 @@ return false
end
return nil
end
-function OPSGROUP:GetCoordinateInRange(TargetCoord,WeaponBitType,RefCoord)
+function OPSGROUP:GetCoordinateInRange(TargetCoord,WeaponBitType,RefCoord,SurfaceTypes)
local coordInRange=nil
RefCoord=RefCoord or self:GetCoordinate()
local weapondata=self:GetWeaponData(WeaponBitType)
+local dh={0,-5,5,-10,10,-15,15,-20,20,-25,25,-30,30,-35,35,-40,40,-45,45,-50,50,-55,55,-60,60,-65,65,-70,70,-75,75,-80,80}
+local function _checkSurface(point)
+if SurfaceTypes then
+local stype=point:GetSurfaceType()
+for _,sf in pairs(SurfaceTypes)do
+if sf==stype then
+return true
+end
+end
+return false
+else
+return true
+end
+end
if weapondata then
-local heading=RefCoord:HeadingTo(TargetCoord)
+local heading=TargetCoord:HeadingTo(RefCoord)
local dist=RefCoord:Get2DDistance(TargetCoord)
+local range=nil
if dist>weapondata.RangeMax then
-local d=(dist-weapondata.RangeMax)*1.05
-coordInRange=RefCoord:Translate(d,heading)
-self:T(self.lid..string.format("Out of max range = %.1f km for weapon %s",weapondata.RangeMax/1000,tostring(WeaponBitType)))
+range=weapondata.RangeMax
+self:T(self.lid..string.format("Out of max range = %.1f km by %.1f km for weapon %s",weapondata.RangeMax/1000,(weapondata.RangeMax-dist)/1000,tostring(WeaponBitType)))
elseif dist TaskExecute()
self:TaskExecute(waypointtask)
return
end
-if self.speedMax<=3.6 or mission.teleport then
+if(self.speedMax<=3.6 or mission.teleport)and not mission.unpaused then
self:Teleport(waypointcoord,nil,true)
self:__TaskExecute(-1,waypointtask)
else
@@ -101224,14 +105115,27 @@ self.Ndestroyed=self.Ndestroyed+1
end
end
self:Despawn(0,true)
-else
+end
for _,_element in pairs(self.elements)do
local element=_element
+if element and element.status~=OPSGROUP.ElementStatus.DEAD then
self:ElementInUtero(element)
end
end
-self:T({Template=Template})
+self:_Spawn(0.01,Template)
+end
+return self
+end
+function OPSGROUP:_Spawn(Delay,Template)
+if Delay and Delay>0 then
+self:ScheduleOnce(Delay,OPSGROUP._Spawn,self,0,Template)
+else
+self:T2({Template=Template})
+if self:IsArmygroup()and self.ValidateAndRepositionGroundUnits then
+UTILS.ValidateAndRepositionGroundUnits(Template.units)
+end
self.group=_DATABASE:Spawn(Template)
+self.group:SetValidateAndRepositionGroundUnits(self.ValidateAndRepositionGroundUnits)
self.dcsgroup=self:GetDCSGroup()
self.controller=self.dcsgroup:getController()
self.isLateActivated=Template.lateActivation
@@ -101242,9 +105146,8 @@ self.groupinitialized=false
self.wpcounter=1
self.currentwp=1
self:_InitWaypoints()
-self:_InitGroup(Template)
+self:_InitGroup(Template,0.001)
end
-return self
end
function OPSGROUP:onafterInUtero(From,Event,To)
self:T(self.lid..string.format("Group inutero at t=%.3f",timer.getTime()))
@@ -101859,7 +105762,7 @@ local element=self:GetElementByName(UnitName)
if element then
element.weightCargo=element.weightCargo+Weight
self:T(self.lid..string.format("%s: Adding %.1f kg cargo weight. New cargo weight=%.1f kg",UnitName,Weight,element.weightCargo))
-if self.isFlightgroup then
+if self.isFlightgroup and element.unit and element.unit:IsAlive()then
trigger.action.setUnitInternalCargo(element.name,element.weightCargo)
end
end
@@ -104358,6 +108261,9 @@ end
end
return targetgroup,targetdist
end
+function OPSGROUP:SetValidateAndRepositionGroundUnits(Enabled)
+self.ValidateAndRepositionGroundUnits=Enabled
+end
OPSTRANSPORT={
ClassName="OPSTRANSPORT",
verbose=0,
@@ -105402,12 +109308,13 @@ Tblu=0,
Tnut=0,
chiefs={},
Missions={},
+UpdateSeconds=120,
}
OPSZONE.ZoneType={
Circular="Circular",
Polygon="Polygon",
}
-OPSZONE.version="0.6.1"
+OPSZONE.version="0.6.2"
function OPSZONE:New(Zone,CoalitionOwner)
local self=BASE:Inherit(self,FSM:New())
if Zone then
@@ -105641,10 +109548,12 @@ end
function OPSZONE:onafterStart(From,Event,To)
self:I(self.lid..string.format("Starting OPSZONE v%s",OPSZONE.version))
self.timerStatus=self.timerStatus or TIMER:New(OPSZONE.Status,self)
-self.timerStatus:Start(1,120)
+local EveryUpdateIn=self.UpdateSeconds or 120
+self.timerStatus:Start(1,EveryUpdateIn)
if self.airbase then
self:HandleEvent(EVENTS.BaseCaptured)
end
+return self
end
function OPSZONE:onafterStop(From,Event,To)
self:I(self.lid..string.format("Stopping OPSZONE"))
@@ -105912,7 +109821,7 @@ captured(coalition.side.NEUTRAL)
end
else
if Nblu>0 then
-if not self:IsAttacked()and self.Tnut>=self.threatlevelCapture then
+if not self:IsAttacked()and self.Tblu>=self.threatlevelCapture then
self:Attacked(coalition.side.BLUE)
end
elseif Nblu==0 then
@@ -105937,7 +109846,7 @@ captured(coalition.side.NEUTRAL)
end
else
if Nred>0 then
-if not self:IsAttacked()and self.Tnut>=self.threatlevelCapture then
+if not self:IsAttacked()and self.Tred>=self.threatlevelCapture then
self:Attacked(coalition.side.RED)
end
elseif Nred==0 then
@@ -106155,7 +110064,7 @@ NextTaskFailure={},
FinalState="none",
PreviousCount=0,
}
-PLAYERTASK.version="0.1.24"
+PLAYERTASK.version="0.1.28"
function PLAYERTASK:New(Type,Target,Repeat,Times,TTSType)
local self=BASE:Inherit(self,FSM:New())
self.Type=Type
@@ -106281,6 +110190,9 @@ end
end)
return OpsZone:GetOwner()==Coalition and isClientInZone and isCaptureGroupInZone
end
+function PLAYERTASK:CanJoinTask(Group,Client)
+return true
+end
function PLAYERTASK:_SetController(Controller)
self:T(self.lid.."_SetController")
self.TaskController=Controller
@@ -106365,17 +110277,17 @@ end,task:GetTarget()
)
return self
end
-function PLAYERTASK:AddOpsZoneCaptureSuccessCondition(CaptureSquadGroupNamePrefix,Coalition)
+function PLAYERTASK:AddOpsZoneCaptureSuccessCondition(CaptureSquadGroupNamePrefix,Coalition,CheckClientInZone)
local task=self
task:AddConditionSuccess(
function(target)
if target:IsInstanceOf("OPSZONE")then
-return task:_CheckCaptureOpsZoneSuccess(target,CaptureSquadGroupNamePrefix,Coalition,true)
+return task:_CheckCaptureOpsZoneSuccess(target,CaptureSquadGroupNamePrefix,Coalition,CheckClientInZone or true)
elseif target:IsInstanceOf("SET_OPSZONE")then
local successes=0
local isClientInZone=false
target:ForEachZone(function(opszone)
-if task:_CheckCaptureOpsZoneSuccess(opszone,CaptureSquadGroupNamePrefix,Coalition)then
+if task:_CheckCaptureOpsZoneSuccess(opszone,CaptureSquadGroupNamePrefix,Coalition,CheckClientInZone or true)then
successes=successes+1
end
for _,client in ipairs(task:GetClientObjects())do
@@ -106443,6 +110355,11 @@ IsDone=true
end
return IsDone
end
+function PLAYERTASK:IsNotDone()
+self:T(self.lid.."IsNotDone?")
+local IsNotDone=not self:IsDone()
+return IsNotDone
+end
function PLAYERTASK:HasClients()
self:T(self.lid.."HasClients?")
local hasclients=self:CountClients()>0 and true or false
@@ -106603,6 +110520,10 @@ self:T({From,Event,To})
self:T(self.lid.."onafterStatus")
local status=self:GetState()
if status=="Stopped"then return self end
+if self.TargetMarker then
+local coordinate=self.Target:GetCoordinate()
+self.TargetMarker:UpdateCoordinate(coordinate,0.5)
+end
local targetdead=false
if self.Type~=AUFTRAG.Type.CTLD and self.Type~=AUFTRAG.Type.CSAR then
if self.Target:IsDead()or self.Target:IsDestroyed()or self.Target:CountTargets()==0 then
@@ -106711,7 +110632,7 @@ if self.TaskController then
self.TaskController:__TaskCancelled(-1,self)
end
self.timestamp=timer.getAbsTime()
-self.FinalState="Cancel"
+self.FinalState="Cancelled"
self:__Done(-1)
return self
end
@@ -106723,7 +110644,7 @@ end
if self.TargetMarker then
self.TargetMarker:Remove()
end
-if self.TaskController.Scoring then
+if self.TaskController and self.TaskController.Scoring then
local clients,count=self:GetClientObjects()
if count>0 then
for _,_client in pairs(clients)do
@@ -106751,7 +110672,10 @@ if self.TargetMarker then
self.TargetMarker:Remove()
end
self.FinalState="Failed"
-self:__Done(-1)
+if self.TaskController then
+self.TaskController:__TaskFailed(-1,self)
+end
+self:__Done(-1.5)
end
if self.TaskController.Scoring then
local clients,count=self:GetClientObjects()
@@ -106809,6 +110733,8 @@ InfoHasCoordinate=false,
UseTypeNames=false,
Scoring=nil,
MenuNoTask=nil,
+InformationMenu=false,
+TaskInfoDuration=30,
}
PLAYERTASKCONTROLLER.Type={
A2A="Air-To-Air",
@@ -106925,6 +110851,7 @@ FRIGATE="Frigate",
CRUISER="Cruiser",
DESTROYER="Destroyer",
CARRIER="Aircraft Carrier",
+RADIOS="Radios",
},
DE={
TASKABORT="Auftrag abgebrochen!",
@@ -107008,9 +110935,10 @@ FRIGATE="Fregatte",
CRUISER="Kreuzer",
DESTROYER="Zerstörer",
CARRIER="Flugzeugträger",
+RADIOS="Frequenzen",
},
}
-PLAYERTASKCONTROLLER.version="0.1.67"
+PLAYERTASKCONTROLLER.version="0.1.70"
function PLAYERTASKCONTROLLER:New(Name,Coalition,Type,ClientFilter)
local self=BASE:Inherit(self,FSM:New())
self.Name=Name or"CentCom"
@@ -107028,13 +110956,14 @@ self.TargetQueue=FIFO:New()
self.TaskQueue=FIFO:New()
self.TasksPerPlayer=FIFO:New()
self.PrecisionTasks=FIFO:New()
+self.LasingDroneSet=SET_OPSGROUP:New()
self.FlashPlayer={}
self.AllowFlash=false
self.lasttaskcount=0
self.taskinfomenu=false
self.activehasinfomenu=false
self.MenuName=nil
-self.menuitemlimit=5
+self.menuitemlimit=6
self.holdmenutime=30
self.MarkerReadOnly=false
self.repeatonfailed=true
@@ -107048,6 +110977,8 @@ self.noflaresmokemenu=false
self.illumenu=false
self.ShowMagnetic=true
self.UseTypeNames=false
+self.InformationMenu=false
+self.TaskInfoDuration=30
self.IsClientSet=false
if ClientFilter and type(ClientFilter)=="table"and ClientFilter.ClassName and ClientFilter.ClassName=="SET_CLIENT"then
self.ClientSet=ClientFilter
@@ -107120,6 +111051,11 @@ self:T(self.lid.."SetAllowFlashDirection")
self.AllowFlash=OnOff
return self
end
+function PLAYERTASKCONTROLLER:SetShowRadioInfoMenu(OnOff)
+self:T(self.lid.."SetAllowRadioInfoMenu")
+self.InformationMenu=OnOff
+return self
+end
function PLAYERTASKCONTROLLER:SetDisableSmokeFlareTask()
self:T(self.lid.."SetDisableSmokeFlareTask")
self.noflaresmokemenu=true
@@ -107153,7 +111089,7 @@ self.InfoHasCoordinate=OnOff
self.InfoHasLLDDM=LLDDM
return self
end
-function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations)
+function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...)
if not ShortCallsign or ShortCallsign==false then
self.ShortCallsign=false
else
@@ -107161,6 +111097,8 @@ self.ShortCallsign=true
end
self.Keepnumber=Keepnumber or false
self.CallsignTranslations=CallsignTranslations
+self.CallsignCustomFunc=CallsignCustomFunc
+self.CallsignCustomArgs=arg or{}
return self
end
function PLAYERTASKCONTROLLER:_GetTextForSpeech(text)
@@ -107182,6 +111120,11 @@ self.repeattimes=Repeats or 5
end
return self
end
+function PLAYERTASKCONTROLLER:SetBriefingDuration(Seconds)
+self:T(self.lid.."SetBriefingDuration")
+self.TaskInfoDuration=Seconds or 30
+return self
+end
function PLAYERTASKCONTROLLER:_SendMessageToClients(Text,Seconds)
self:T(self.lid.."_SendMessageToClients")
local seconds=Seconds or 10
@@ -107194,35 +111137,46 @@ end
)
return self
end
-function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint,Alt,Speed)
+function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint,Alt,Speed,MaxTravelDist)
self:T(self.lid.."EnablePrecisionBombing")
+if not self.LasingDroneSet then
+self.LasingDroneSet=SET_OPSGROUP:New()
+end
+local LasingDrone
if FlightGroup then
if FlightGroup.ClassName and(FlightGroup.ClassName=="FLIGHTGROUP"or FlightGroup.ClassName=="ARMYGROUP")then
-self.LasingDrone=FlightGroup
-self.LasingDrone.playertask={}
-self.LasingDrone.playertask.busy=false
-self.LasingDrone.playertask.id=0
+LasingDrone=FlightGroup
self.precisionbombing=true
-self.LasingDrone:SetLaser(LaserCode)
-self.LaserCode=LaserCode or 1688
-self.LasingDroneTemplate=self.LasingDrone:_GetTemplate(true)
-self.LasingDroneAlt=Alt or 10000
-self.LasingDroneSpeed=Speed or 120
-if self.LasingDrone:IsFlightgroup()then
-self.LasingDroneIsFlightgroup=true
+LasingDrone.playertask={}
+LasingDrone.playertask.id=0
+LasingDrone.playertask.busy=false
+LasingDrone.playertask.lasercode=LaserCode or 1688
+LasingDrone:SetLaser(LasingDrone.playertask.lasercode)
+LasingDrone.playertask.template=LasingDrone:_GetTemplate(true)
+LasingDrone.playertask.alt=Alt or 10000
+LasingDrone.playertask.speed=Speed or 120
+LasingDrone.playertask.maxtravel=UTILS.NMToMeters(MaxTravelDist or 50)
+if LasingDrone:IsFlightgroup()then
local BullsCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(self.Coalition))
if HoldingPoint then BullsCoordinate=HoldingPoint end
-local Orbit=AUFTRAG:NewORBIT_CIRCLE(BullsCoordinate,self.LasingDroneAlt,self.LasingDroneSpeed)
-self.LasingDrone:AddMission(Orbit)
-elseif self.LasingDrone:IsArmygroup()then
-self.LasingDroneIsArmygroup=true
+local Orbit=AUFTRAG:NewORBIT_CIRCLE(BullsCoordinate,Alt,Speed)
+Orbit:SetMissionAltitude(Alt)
+LasingDrone:AddMission(Orbit)
+elseif LasingDrone:IsArmygroup()then
local BullsCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(self.Coalition))
if HoldingPoint then BullsCoordinate=HoldingPoint end
local Orbit=AUFTRAG:NewONGUARD(BullsCoordinate)
-self.LasingDrone:AddMission(Orbit)
+LasingDrone:AddMission(Orbit)
end
+self.LasingDroneSet:AddObject(FlightGroup)
+elseif FlightGroup.ClassName and(FlightGroup.ClassName=="SET_OPSGROUP")then
+FlightGroup:ForEachGroup(
+function(group)
+self:EnablePrecisionBombing(group,LaserCode,HoldingPoint,Alt,Speed,MaxTravelDist)
+end
+)
else
-self:E(self.lid.."No FLIGHTGROUP object passed or FLIGHTGROUP is not alive!")
+self:E(self.lid.."No OPSGROUP/SET_OPSGROUP object passed or object is not alive!")
end
else
self.autolase=nil
@@ -107230,6 +111184,10 @@ self.precisionbombing=false
end
return self
end
+function PLAYERTASKCONTROLLER:AddPrecisionBombingOpsGroup(FlightGroup,LaserCode,HoldingPoint,Alt,Speed)
+self:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint,Alt,Speed)
+return self
+end
function PLAYERTASKCONTROLLER:EnableBuddyLasing(Recce)
self:T(self.lid.."EnableBuddyLasing")
self.buddylasing=true
@@ -107276,7 +111234,7 @@ local ttsplayername=nil
if not self.customcallsigns[playername]then
local playergroup=Client:GetGroup()
if playergroup~=nil then
-ttsplayername=playergroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
+ttsplayername=playergroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
local newplayername=self:_GetTextForSpeech(ttsplayername)
self.customcallsigns[playername]=newplayername
ttsplayername=newplayername
@@ -107308,7 +111266,7 @@ self.activehasinfomenu=InfoMenu or false
if self.activehasinfomenu then
self:EnableTaskInfoMenu()
end
-self.menuitemlimit=ItemLimit or 5
+self.menuitemlimit=ItemLimit+1 or 6
self.holdmenutime=HoldTime or 30
return self
end
@@ -107371,7 +111329,7 @@ if EventData.IniGroup then
if self.customcallsigns[playername]then
self.customcallsigns[playername]=nil
end
-playername=EventData.IniGroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber)
+playername=EventData.IniGroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
end
playername=self:_GetTextForSpeech(playername)
local text=string.format(switchtext,playername,self.MenuName or self.Name,freqtext)
@@ -107569,37 +111527,62 @@ return self
end
function PLAYERTASKCONTROLLER:_CheckPrecisionTasks()
self:T(self.lid.."_CheckPrecisionTasks")
+self:T({count=self.PrecisionTasks:Count(),enabled=self.precisionbombing})
if self.PrecisionTasks:Count()>0 and self.precisionbombing then
-if not self.LasingDrone or self.LasingDrone:IsDead()then
+self.LasingDroneSet:ForEachGroup(
+function(LasingDrone)
+if not LasingDrone or LasingDrone:IsDead()then
self:E(self.lid.."Lasing drone is dead ... creating a new one!")
-if self.LasingDrone then
-self.LasingDrone:_Respawn(1,nil,true)
+if LasingDrone then
+LasingDrone:_Respawn(1,nil,true)
else
-if self.LasingDroneIsFlightgroup then
-local FG=FLIGHTGROUP:New(self.LasingDroneTemplate)
-FG:Activate()
-self:EnablePrecisionBombing(FG,self.LaserCode or 1688)
-else
-local FG=ARMYGROUP:New(self.LasingDroneTemplate)
-FG:Activate()
-self:EnablePrecisionBombing(FG,self.LaserCode or 1688)
end
end
-return self
end
-if self.LasingDrone and self.LasingDrone:IsAlive()then
-if self.LasingDrone.playertask and(not self.LasingDrone.playertask.busy)then
-self:T(self.lid.."Sending lasing unit to target")
+)
+local function SelectDrone(coord)
+local selected=nil
+local mindist=math.huge
+local dist=math.huge
+self.LasingDroneSet:ForEachGroup(
+function(grp)
+if grp.playertask and(not grp.playertask.busy)then
+local gc=grp:GetCoordinate()
+if coord and gc then
+dist=coord:Get2DDistance(gc)
+end
+if dist%s",CoordText)
end
end
local ThreatLocaleTextTTS=self.gettext:GetEntry("THREATTEXTTTS",self.locale)
local ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText)
if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then
-if self.LasingDrone.playertask.inreach and self.LasingDrone:IsLasing()then
+if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing()then
local lasingtext=self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale)
ttstext=ttstext..lasingtext
end
@@ -108172,7 +112204,7 @@ else
text=self.gettext:GetEntry("NOACTIVETASK",self.locale)
end
if not self.NoScreenOutput then
-local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client)
+local m=MESSAGE:New(text,self.TaskInfoDuration or 30,"Tasking"):ToClient(Client)
end
return self
end
@@ -108442,6 +112474,10 @@ if self.TaskQueue:Count()>0 and self.MenuNoTask~=nil then
JoinTaskMenuTemplate:DeleteGenericEntry(self.MenuNoTask)
self.MenuNoTask=nil
end
+if self.InformationMenu then
+local radioinfo=self.gettext:GetEntry("RADIOS",self.locale)
+JoinTaskMenuTemplate:NewEntry(radioinfo,self.JoinTopMenu,self._ShowRadioInfo,self)
+end
self.JoinTaskMenuTemplate=JoinTaskMenuTemplate
return self
end
@@ -108572,6 +112608,24 @@ self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
+function PLAYERTASKCONTROLLER:AddConflictZone(ConflictZone)
+self:T(self.lid.."AddConflictZone")
+if self.Intel then
+self.Intel:AddConflictZone(ConflictZone)
+else
+self:E(self.lid.."*****NO detection has been set up (yet)!")
+end
+return self
+end
+function PLAYERTASKCONTROLLER:AddConflictZoneSet(ConflictZoneSet)
+self:T(self.lid.."AddConflictZoneSet")
+if self.Intel then
+self.Intel.conflictzoneset:AddSet(ConflictZoneSet)
+else
+self:E(self.lid.."*****NO detection has been set up (yet)!")
+end
+return self
+end
function PLAYERTASKCONTROLLER:RemoveAcceptZone(AcceptZone)
self:T(self.lid.."RemoveAcceptZone")
if self.Intel then
@@ -108581,7 +112635,7 @@ self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
-function PLAYERTASKCONTROLLER:RemoveRejectZoneSet(RejectZone)
+function PLAYERTASKCONTROLLER:RemoveRejectZone(RejectZone)
self:T(self.lid.."RemoveRejectZone")
if self.Intel then
self.Intel:RemoveRejectZone(RejectZone)
@@ -108590,6 +112644,15 @@ self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
+function PLAYERTASKCONTROLLER:RemoveConflictZone(ConflictZone)
+self:T(self.lid.."RemoveConflictZone")
+if self.Intel then
+self.Intel:RemoveConflictZone(ConflictZone)
+else
+self:E(self.lid.."*****NO detection has been set up (yet)!")
+end
+return self
+end
function PLAYERTASKCONTROLLER:SetMenuName(Name)
self:T(self.lid.."SetMenuName: "..Name)
self.MenuName=Name
@@ -108674,9 +112737,9 @@ NewContact(Contact)
end
return self
end
-function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate)
+function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate,Backend)
self:T(self.lid.."SetSRS")
-self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
+self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.Gender=Gender or MSRS.gender or"male"
self.Culture=Culture or MSRS.culture or"en-US"
self.Port=Port or MSRS.port or 5002
@@ -108689,7 +112752,7 @@ self.Frequency=Frequency or{127,251}
self.BCFrequency=self.Frequency
self.Modulation=Modulation or{radio.modulation.FM,radio.modulation.AM}
self.BCModulation=self.Modulation
-self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation)
+self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation,Backend)
self.SRS:SetCoalition(self.Coalition)
self.SRS:SetLabel(self.MenuName or self.Name)
self.SRS:SetGender(self.Gender)
@@ -108879,7 +112942,7 @@ PLAYERRECCE={
ClassName="PLAYERRECCE",
verbose=true,
lid=nil,
-version="0.1.23",
+version="0.1.26",
ViewZone={},
ViewZoneVisual={},
ViewZoneLaser={},
@@ -108907,7 +112970,8 @@ TForget=600,
TargetCache=nil,
smokeownposition=false,
SmokeOwn={},
-smokeaveragetargetpos=false,
+smokeaveragetargetpos=true,
+reporttostringbullsonly=true,
}
PLAYERRECCE.LaserRelativePos={
["SA342M"]={x=1.7,y=1.2,z=0},
@@ -108915,7 +112979,8 @@ PLAYERRECCE.LaserRelativePos={
["SA342Minigun"]={x=1.7,y=1.2,z=0},
["SA342L"]={x=1.7,y=1.2,z=0},
["Ka-50"]={x=6.1,y=-0.85,z=0},
-["Ka-50_3"]={x=6.1,y=-0.85,z=0}
+["Ka-50_3"]={x=6.1,y=-0.85,z=0},
+["OH58D"]={x=0,y=2.8,z=0},
}
PLAYERRECCE.MaxViewDistance={
["SA342M"]=8000,
@@ -108924,6 +112989,7 @@ PLAYERRECCE.MaxViewDistance={
["SA342L"]=8000,
["Ka-50"]=8000,
["Ka-50_3"]=8000,
+["OH58D"]=8000,
}
PLAYERRECCE.Cameraheight={
["SA342M"]=2.85,
@@ -108932,6 +112998,7 @@ PLAYERRECCE.Cameraheight={
["SA342L"]=2.85,
["Ka-50"]=0.5,
["Ka-50_3"]=0.5,
+["OH58D"]=4.25,
}
PLAYERRECCE.CanLase={
["SA342M"]=true,
@@ -108940,6 +113007,7 @@ PLAYERRECCE.CanLase={
["SA342L"]=true,
["Ka-50"]=true,
["Ka-50_3"]=true,
+["OH58D"]=false,
}
PLAYERRECCE.SmokeColor={
["highsmoke"]=SMOKECOLOR.Orange,
@@ -108965,6 +113033,7 @@ self.lid=string.format("PlayerForwardController %s %s | ",self.Name,self.version
self:SetLaserCodes({1688,1130,4785,6547,1465,4578})
self.lasingtime=60
self.minthreatlevel=0
+self.reporttostringbullsonly=true
self.TForget=600
self.TargetCache=FIFO:New()
self:SetStartState("Stopped")
@@ -109090,12 +113159,39 @@ local vivihorizontal=dcsunit:getDrawArgumentValue(215)or 0
if vivihorizontal<-0.7 or vivihorizontal>0.7 then
camera=false
end
+elseif string.find(typename,"OH58")then
+local dcsunit=Unit.getByName(client:GetName())
+local vivihorizontal=dcsunit:getDrawArgumentValue(528)or 0
+if vivihorizontal<-0.527 or vivihorizontal>0.527 then
+camera=false
+end
elseif string.find(typename,"Ka-50")then
camera=true
end
end
return camera
end
+function PLAYERRECCE:_GetKiowaMMSSight(Kiowa)
+self:T(self.lid.."_GetKiowaMMSSight")
+local unit=Kiowa
+if unit and unit:IsAlive()then
+local dcsunit=Unit.getByName(Kiowa:GetName())
+local mmshorizontal=dcsunit:getDrawArgumentValue(528)or 0
+local mmsvertical=dcsunit:getDrawArgumentValue(527)or 0
+self:T(string.format("Kiowa MMS Arguments Read: H %.3f V %.3f",mmshorizontal,mmsvertical))
+local mmson=true
+if mmshorizontal<-0.527 or mmshorizontal>0.527 then mmson=false end
+local horizontalview=mmshorizontal/0.527*190
+local heading=unit:GetHeading()
+local mmsheading=(heading+horizontalview)%360
+local mmsyaw=math.atan(mmsvertical)*40
+local maxview=self:_GetActualMaxLOSight(unit,mmsheading,mmsyaw,not mmson)
+if maxview>8000 then maxview=8000 end
+self:T(string.format("Kiowa MMS Heading %d, Yaw %d, MaxView %dm MMS On %s",mmsheading,mmsyaw,maxview,tostring(mmson)))
+return mmsheading,mmsyaw,maxview,mmson
+end
+return 0,0,0,false
+end
function PLAYERRECCE:_GetGazelleVivianneSight(Gazelle)
self:T(self.lid.."GetGazelleVivianneSight")
local unit=Gazelle
@@ -109112,33 +113208,11 @@ vivihorizontal=0.67
vivioff=true
return 0,0,0,false
end
-vivivertical=vivivertical/1.10731
local horizontalview=vivihorizontal*-180
-local verticalview=vivivertical*30
+local verticalview=math.atan(vivivertical)
local heading=unit:GetHeading()
local viviheading=(heading+horizontalview)%360
local maxview=self:_GetActualMaxLOSight(unit,viviheading,verticalview,vivioff)
-local factor=3.15
-self.GazelleViewFactors={
-[1]=1.18,
-[2]=1.32,
-[3]=1.46,
-[4]=1.62,
-[5]=1.77,
-[6]=1.85,
-[7]=2.05,
-[8]=2.05,
-[9]=2.3,
-[10]=2.3,
-[11]=2.27,
-[12]=2.27,
-[13]=2.43,
-}
-local lfac=UTILS.Round(maxview,-2)
-if lfac<=1300 then
-factor=3.15
-maxview=math.ceil((maxview*factor)/100)*100
-end
if maxview>8000 then maxview=8000 end
return viviheading,verticalview,maxview,not vivioff
end
@@ -109151,20 +113225,20 @@ local maxview=0
if unit and unit:IsAlive()then
local typename=unit:GetTypeName()
maxview=self.MaxViewDistance[typename]or 8000
-local CamHeight=self.Cameraheight[typename]or 0
-if vnod<0 then
+local CamHeight=self.Cameraheight[typename]or 1
+if vnod<-2 then
local beta=90
-local gamma=math.floor(90-vnod)
-local alpha=math.floor(180-beta-gamma)
+local gamma=90-math.abs(vnod)
+local alpha=90-gamma
local a=unit:GetHeight()-unit:GetCoordinate():GetLandHeight()+CamHeight
local b=a/math.sin(math.rad(alpha))
local c=b*math.sin(math.rad(gamma))
maxview=c*1.2
end
end
-return math.abs(maxview)
+return math.ceil(math.abs(maxview))
end
-function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations)
+function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...)
if not ShortCallsign or ShortCallsign==false then
self.ShortCallsign=false
else
@@ -109172,6 +113246,8 @@ self.ShortCallsign=true
end
self.Keepnumber=Keepnumber or false
self.CallsignTranslations=CallsignTranslations
+self.CallsignCustomFunc=CallsignCustomFunc
+self.CallsignCustomArgs=arg or{}
return self
end
function PLAYERRECCE:_GetViewZone(unit,vheading,minview,maxview,angle,camon,laser)
@@ -109259,22 +113335,30 @@ local finalcount=0
local minview=0
local typename=unit:GetTypeName()
local playername=unit:GetPlayerName()
-local maxview=self.MaxViewDistance[typename]or 5000
+local maxview=self.MaxViewDistance[typename]or 8000
local heading,nod,maxview,angle=0,30,8000,10
local camon=false
local name=unit:GetName()
if string.find(typename,"SA342")and camera then
heading,nod,maxview,camon=self:_GetGazelleVivianneSight(unit)
angle=10
-maxview=self.MaxViewDistance[typename]or 5000
+maxview=self.MaxViewDistance[typename]or 8000
elseif string.find(typename,"Ka-50")and camera then
heading=unit:GetHeading()
nod,maxview,camon=10,1000,true
angle=10
-maxview=self.MaxViewDistance[typename]or 5000
+maxview=self.MaxViewDistance[typename]or 8000
+elseif string.find(typename,"OH58")and camera then
+nod,maxview,camon=0,8000,true
+heading,nod,maxview,camon=self:_GetKiowaMMSSight(unit)
+angle=8
+if maxview==0 then
+maxview=self.MaxViewDistance[typename]or 8000
+end
else
heading=unit:GetHeading()
-nod,maxview,camon=10,1000,true
+nod,maxview,camon=10,3000,true
+maxview=self.MaxViewDistance[typename]or 3000
angle=45
end
if laser then
@@ -109363,7 +113447,8 @@ self:T("Targetstate: "..target:GetState())
self:T("Laser State: "..tostring(laser:IsLasing()))
if(not oldtarget)or targetset:IsNotInSet(oldtarget)or target:IsDead()or target:IsDestroyed()then
laser:LaseOff()
-if target:IsDead()or target:IsDestroyed()or target:GetLife()<2 then
+self:T(self.lid.."Target Life Points: "..target:GetLife()or"none")
+if target:IsDead()or target:IsDestroyed()or target:GetDamage()>79 or target:GetLife()<=1 then
self:__Shack(-1,client,oldtarget)
else
self:__TargetLOSLost(-1,client,oldtarget)
@@ -109604,6 +113689,9 @@ report:Add("Target type: "..target:GetTypeName()or"unknown")
report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")")
if not self.ReferencePoint then
report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings))
+if self.reporttostringbullsonly~=true then
+report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings))
+end
else
report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings))
end
@@ -109640,8 +113728,14 @@ report:Add("Target count: "..number)
report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")")
if not self.ReferencePoint then
report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings))
+if self.reporttostringbullsonly~=true then
+report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings))
+end
else
report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings))
+if self.reporttostringbullsonly~=true then
+report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings))
+end
end
report:Add(string.rep("-",15))
local text=report:Text()
@@ -109665,6 +113759,7 @@ for _,_client in pairs(clientset)do
local client=_client
if client and client:IsAlive()then
local playername=client:GetPlayerName()
+self:T("Menu for "..playername)
if not self.UnitLaserCodes[playername]then
self:_SetClientLaserCode(nil,nil,playername,1688)
end
@@ -109673,6 +113768,7 @@ self.SmokeOwn[playername]=self.smokeownposition
end
local group=client:GetGroup()
if not self.ClientMenus[playername]then
+self:T("Start Menubuild for "..playername)
local canlase=self.CanLase[client:GetTypeName()]
self.ClientMenus[playername]=MENU_GROUP:New(group,self.MenuName or self.Name or"RECCE")
local txtonstation=self.OnStation[playername]and"ON"or"OFF"
@@ -109788,9 +113884,9 @@ self:TargetDetected(targetsbyclock,client,playername)
end
return self
end
-function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey)
+function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,Backend)
self:T(self.lid.."SetSRS")
-self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
+self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.Gender=Gender or MSRS.gender or"male"
self.Culture=Culture or MSRS.culture or"en-US"
self.Port=Port or MSRS.port or 5002
@@ -109809,6 +113905,9 @@ self.SRS:SetGender(self.Gender)
self.SRS:SetCulture(self.Culture)
self.SRS:SetPort(self.Port)
self.SRS:SetVolume(self.Volume)
+if Backend then
+self.SRS:SetBackend(Backend)
+end
if self.PathToGoogleKey then
self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.PathToGoogleKey)
self.SRS:SetProvider(MSRS.Provider.GOOGLE)
@@ -109834,11 +113933,21 @@ self:T(self.lid.."SetMenuName: "..Name)
self.MenuName=Name
return self
end
+function PLAYERRECCE:SetReportBullsOnly(OnOff)
+self:T(self.lid.."SetReportBullsOnly: "..tostring(OnOff))
+self.reporttostringbullsonly=OnOff
+return self
+end
function PLAYERRECCE:EnableSmokeOwnPosition()
self:T(self.lid.."EnableSmokeOwnPosition")
self.smokeownposition=true
return self
end
+function PLAYERRECCE:EnableKiowaAutolase()
+self:T(self.lid.."EnableKiowaAutolase")
+self.CanLase.OH58D=true
+return self
+end
function PLAYERRECCE:DisableSmokeOwnPosition()
self:T(self.lid.."DisableSmokeOwnPosition")
self.smokeownposition=false
@@ -109946,7 +114055,7 @@ return self
end
function PLAYERRECCE:onafterRecceOnStation(From,Event,To,Client,Playername)
self:T({From,Event,To})
-local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
+local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
local coord=Client:GetCoordinate()
local coordtext=coord:ToStringBULLS(self.Coalition)
if self.ReferencePoint then
@@ -109955,7 +114064,7 @@ coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Setti
end
local text1="Party time!"
local text2=string.format("All stations, FACA %s on station\nat %s!",callsign,coordtext)
-local text2tts=string.format("All stations, FACA %s on station at %s!",callsign,coordtext)
+local text2tts=string.format(" All stations, FACA %s on station at %s!",callsign,coordtext)
text2tts=self:_GetTextForSpeech(text2tts)
if self.debug then
self:T(text2.."\n"..text2tts)
@@ -109977,7 +114086,7 @@ return self
end
function PLAYERRECCE:onafterRecceOffStation(From,Event,To,Client,Playername)
self:T({From,Event,To})
-local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
+local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
local coord=Client:GetCoordinate()
local coordtext=coord:ToStringBULLS(self.Coalition)
if self.ReferencePoint then
@@ -110093,7 +114202,7 @@ return self
end
function PLAYERRECCE:onafterIllumination(From,Event,To,Client,Playername,TargetSet)
self:T({From,Event,To})
-local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
+local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
local coord=Client:GetCoordinate()
local coordtext=coord:ToStringBULLS(self.Coalition)
if self.AttackSet then
@@ -110126,7 +114235,7 @@ return self
end
function PLAYERRECCE:onafterTargetsSmoked(From,Event,To,Client,Playername,TargetSet)
self:T({From,Event,To})
-local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
+local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
local coord=Client:GetCoordinate()
local coordtext=coord:ToStringBULLS(self.Coalition)
if self.AttackSet then
@@ -110159,7 +114268,7 @@ return self
end
function PLAYERRECCE:onafterTargetsFlared(From,Event,To,Client,Playername,TargetSet)
self:T({From,Event,To})
-local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
+local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
local coord=Client:GetCoordinate()
local coordtext=coord:ToStringBULLS(self.Coalition)
if self.AttackSet then
@@ -110192,7 +114301,7 @@ return self
end
function PLAYERRECCE:onafterTargetLasing(From,Event,To,Client,Target,Lasercode,Lasingtime)
self:T({From,Event,To})
-local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
+local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
local Settings=(Client and _DATABASE:GetPlayerSettings(Client:GetPlayerName()))or _SETTINGS
local coord=Client:GetCoordinate()
local coordtext=coord:ToStringBULLS(self.Coalition,Settings)
@@ -110230,7 +114339,7 @@ return self
end
function PLAYERRECCE:onafterShack(From,Event,To,Client,Target)
self:T({From,Event,To})
-local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
+local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
local Settings=(Client and _DATABASE:GetPlayerSettings(Client:GetPlayerName()))or _SETTINGS
local coord=Client:GetCoordinate()
local coordtext=coord:ToStringBULLS(self.Coalition,Settings)
@@ -110268,7 +114377,7 @@ return self
end
function PLAYERRECCE:onafterTargetLOSLost(From,Event,To,Client,Target)
self:T({From,Event,To})
-local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
+local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
local Settings=(Client and _DATABASE:GetPlayerSettings(Client:GetPlayerName()))or _SETTINGS
local coord=Client:GetCoordinate()
local coordtext=coord:ToStringBULLS(self.Coalition,Settings)
@@ -111022,9 +115131,23 @@ return 0
elseif Target.Type==TARGET.ObjectType.COORDINATE then
return 0
elseif Target.Type==TARGET.ObjectType.ZONE then
-return 0
+local zone=Target.Object
+local foundunits={}
+if zone:IsInstanceOf("ZONE_RADIUS")or zone:IsInstanceOf("ZONE_POLYGON")then
+zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT,Unit.Category.SHIP})
+foundunits=zone:GetScannedSetUnit()
+else
+foundunits=SET_UNIT:New():FilterZones({zone}):FilterOnce()
+end
+local ThreatMax=foundunits:GetThreatLevelMax()or 0
+return ThreatMax
+elseif Target.Type==TARGET.ObjectType.OPSZONE then
+local unitset=Target.Object:GetScannedUnitSet()
+local ThreatMax=unitset:GetThreatLevelMax()
+return ThreatMax
else
self:E("ERROR: unknown target object type in GetTargetThreatLevel!")
+return 0
end
return self
end
@@ -111245,6 +115368,17 @@ end
self:E(self.lid..string.format("ERROR: Cannot get average coordinate of target %s",tostring(self.name)))
return nil
end
+function TARGET:GetCoordinates()
+local coordinates={}
+for _,_target in pairs(self.targets)do
+local target=_target
+local coordinate=self:GetTargetCoordinate(target)
+if coordinate then
+table.insert(coordinates,coordinate)
+end
+end
+return coordinates
+end
function TARGET:GetHeading()
for _,_target in pairs(self.targets)do
local Target=_target
@@ -111374,6 +115508,14 @@ return target.Object
end
return nil
end
+function TARGET:GetObjects()
+local objects={}
+for _,_target in pairs(self.targets)do
+local target=_target
+table.insert(objects,target.Object)
+end
+return objects
+end
function TARGET:CountObjectives(Target,Coalitions)
local N=0
if Target.Type==TARGET.ObjectType.GROUP then
@@ -111486,26 +115628,34 @@ engagerange=50,
repeatsonfailure=3,
GoZoneSet=nil,
NoGoZoneSet=nil,
+ConflictZoneSet=nil,
Monitor=false,
TankerInvisible=true,
CapFormation=nil,
ReadyFlightGroups={},
DespawnAfterLanding=false,
DespawnAfterHolding=true,
-ListOfAuftrag={}
+ListOfAuftrag={},
+defaulttakeofftype="hot",
+FuelLowThreshold=25,
+FuelCriticalThreshold=10,
+showpatrolpointmarks=false,
+EngageTargetTypes={"Air"},
}
-EASYGCICAP.version="0.1.15"
+EASYGCICAP.version="0.1.30"
function EASYGCICAP:New(Alias,AirbaseName,Coalition,EWRName)
local self=BASE:Inherit(self,FSM:New())
self.alias=Alias or AirbaseName.." CAP Wing"
self.coalitionname=string.lower(Coalition)or"blue"
self.coalition=self.coalitionname=="blue"and coalition.side.BLUE or coalition.side.RED
self.wings={}
-self.EWRName=EWRName or self.coalitionname.." EWR"
+if type(EWRName)=="string"then EWRName={EWRName}end
+self.EWRName=EWRName
self.airbasename=AirbaseName
self.airbase=AIRBASE:FindByName(self.airbasename)
self.GoZoneSet=SET_ZONE:New()
self.NoGoZoneSet=SET_ZONE:New()
+self.ConflictZoneSet=SET_ZONE:New()
self.resurrection=900
self.capspeed=300
self.capalt=25000
@@ -111523,6 +115673,11 @@ self.CapFormation=ENUMS.Formation.FixedWing.FingerFour.Group
self.DespawnAfterLanding=false
self.DespawnAfterHolding=true
self.ListOfAuftrag={}
+self.defaulttakeofftype="hot"
+self.FuelLowThreshold=25
+self.FuelCriticalThreshold=10
+self.showpatrolpointmarks=false
+self.EngageTargetTypes={"Air"}
self.lid=string.format("EASYGCICAP %s | ",self.alias)
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
@@ -111533,6 +115688,39 @@ self:I(self.lid.."Created new instance (v"..self.version..")")
self:__Start(math.random(6,12))
return self
end
+function EASYGCICAP:GetAirwing(AirbaseName)
+self:T(self.lid.."GetAirwing")
+if self.wings[AirbaseName]then
+return self.wings[AirbaseName][1]
+end
+return nil
+end
+function EASYGCICAP:GetAirwingTable()
+self:T(self.lid.."GetAirwingTable")
+local Wingtable={}
+for _,_object in pairs(self.wings or{})do
+table.insert(Wingtable,_object[1])
+end
+return Wingtable
+end
+function EASYGCICAP:SetFuelLow(Percent)
+self:T(self.lid.."SetFuelLow")
+self.FuelLowThreshold=Percent or 25
+return self
+end
+function EASYGCICAP:ShowPatrolPointMarkers(onoff)
+if onoff then
+self.showpatrolpointmarks=true
+else
+self.showpatrolpointmarks=false
+end
+return self
+end
+function EASYGCICAP:SetFuelCritical(Percent)
+self:T(self.lid.."SetFuelCritical")
+self.FuelCriticalThreshold=Percent or 10
+return self
+end
function EASYGCICAP:SetCAPFormation(Formation)
self:T(self.lid.."SetCAPFormation")
self.CapFormation=Formation
@@ -111543,6 +115731,16 @@ self:T(self.lid.."SetTankerAndAWACSInvisible")
self.TankerInvisible=Switch
return self
end
+function EASYGCICAP:_CountAliveAuftrags()
+local alive=0
+for _,_auftrag in pairs(self.ListOfAuftrag)do
+local auftrag=_auftrag
+if auftrag and(not(auftrag:IsCancelled()or auftrag:IsDone()or auftrag:IsOver()))then
+alive=alive+1
+end
+end
+return alive
+end
function EASYGCICAP:SetMaxAliveMissions(Maxiumum)
self:T(self.lid.."SetMaxAliveMissions")
self.MaxAliveMissions=Maxiumum or 8
@@ -111558,6 +115756,11 @@ self:T(self.lid.."SetDefaultRepeatOnFailure")
self.repeatsonfailure=Retries or 3
return self
end
+function EASYGCICAP:SetDefaultTakeOffType(Takeoff)
+self:T(self.lid.."SetDefaultTakeOffType")
+self.defaulttakeofftype=Takeoff or"hot"
+return self
+end
function EASYGCICAP:SetDefaultCAPSpeed(Speed)
self:T(self.lid.."SetDefaultSpeed")
self.capspeed=Speed or 300
@@ -111620,6 +115823,10 @@ self.capOptionVaryStartTime=Start or 5
self.capOptionVaryEndTime=End or 60
return self
end
+function EASYGCICAP:SetCAPEngageTargetTypes(types)
+self.EngageTargetTypes=types or{"Air"}
+return self
+end
function EASYGCICAP:AddAirwing(Airbasename,Alias)
self:T(self.lid.."AddAirwing "..Airbasename)
local AWEntry={}
@@ -111643,6 +115850,11 @@ self:T(self.lid.."_AddAirwing "..Airbasename)
local CapFormation=self.CapFormation
local DespawnAfterLanding=self.DespawnAfterLanding
local DespawnAfterHolding=self.DespawnAfterHolding
+local check=STATIC:FindByName(Airbasename,false)or UNIT:FindByName(Airbasename)
+if check==nil then
+MESSAGE:New(self.lid.."There's no warehouse static on the map (wrong naming?) for airbase "..tostring(Airbasename).."!",30,"CHECK"):ToAllIf(self.debug):ToLog()
+return
+end
local CAP_Wing=AIRWING:New(Airbasename,Alias)
CAP_Wing:SetVerbosityLevel(0)
CAP_Wing:SetReportOff()
@@ -111651,6 +115863,9 @@ CAP_Wing:SetAirbase(AIRBASE:FindByName(Airbasename))
CAP_Wing:SetRespawnAfterDestroyed()
CAP_Wing:SetNumberCAP(self.capgrouping)
CAP_Wing:SetCapCloseRaceTrack(true)
+if self.showpatrolpointmarks then
+CAP_Wing:ShowPatrolPointMarkers(true)
+end
if self.capOptionVaryStartTime then
CAP_Wing:SetCapStartTimeVariation(self.capOptionVaryStartTime,self.capOptionVaryEndTime)
end
@@ -111667,13 +115882,19 @@ end
if#self.ManagedREC>0 then
CAP_Wing:SetNumberRecon(1)
end
-CAP_Wing:SetTakeoffHot()
+CAP_Wing:SetTakeoffType(self.defaulttakeofftype)
CAP_Wing:SetLowFuelThreshold(0.3)
CAP_Wing.RandomAssetScore=math.random(50,100)
CAP_Wing:Start()
local Intel=self.Intel
local TankerInvisible=self.TankerInvisible
-function CAP_Wing:OnAfterFlightOnMission(From,Event,To,Flightgroup,Mission)
+local engagerange=self.engagerange
+local GoZoneSet=self.GoZoneSet
+local NoGoZoneSet=self.NoGoZoneSet
+local FuelLow=self.FuelLowThreshold or 25
+local FuelCritical=self.FuelCriticalThreshold or 10
+local EngageTypes=self.EngageTargetTypes or{"Air"}
+function CAP_Wing:onbeforeFlightOnMission(From,Event,To,Flightgroup,Mission)
local flightgroup=Flightgroup
if DespawnAfterLanding then
flightgroup:SetDespawnAfterLanding()
@@ -111683,10 +115904,15 @@ end
flightgroup:SetDestinationbase(AIRBASE:FindByName(Airbasename))
flightgroup:GetGroup():CommandEPLRS(true,5)
flightgroup:GetGroup():SetOptionRadarUsingForContinousSearch()
+flightgroup:GetGroup():SetOptionLandingOverheadBreak()
if Mission.type~=AUFTRAG.Type.TANKER and Mission.type~=AUFTRAG.Type.AWACS and Mission.type~=AUFTRAG.Type.RECON then
flightgroup:SetDetection(true)
-flightgroup:SetEngageDetectedOn(self.engagerange,{"Air"},self.GoZoneSet,self.NoGoZoneSet)
+flightgroup:SetEngageDetectedOn(engagerange,EngageTypes,GoZoneSet,NoGoZoneSet)
flightgroup:SetOutOfAAMRTB()
+flightgroup:SetFuelLowRTB(true)
+flightgroup:SetFuelLowThreshold(FuelLow)
+flightgroup:SetFuelCriticalRTB(true)
+flightgroup:SetFuelCriticalThreshold(FuelCritical)
if CapFormation then
flightgroup:GetGroup():SetOption(AI.Option.Air.id.FORMATION,CapFormation)
end
@@ -111703,7 +115929,7 @@ flightgroup:GetGroup():OptionROTEvadeFire()
flightgroup:SetFuelLowRTB(true)
Intel:AddAgent(flightgroup)
if DespawnAfterHolding then
-function flightgroup:OnAfterHolding(From,Event,To)
+function flightgroup:onbeforeHolding(From,Event,To)
self:Despawn(1,true)
end
end
@@ -111719,17 +115945,22 @@ self.wings[Airbasename]={CAP_Wing,AIRBASE:FindByName(Airbasename):GetZone(),Airb
return self
end
function EASYGCICAP:AddPatrolPointCAP(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength)
-self:T(self.lid.."AddPatrolPointCAP "..Coordinate:ToStringLLDDM())
+self:T(self.lid.."AddPatrolPointCAP")
+local coordinate=Coordinate
local EntryCAP={}
+if Coordinate:IsInstanceOf("ZONE_BASE")then
+coordinate=Coordinate:GetCoordinate()
+EntryCAP.Zone=Coordinate
+end
EntryCAP.AirbaseName=AirbaseName
-EntryCAP.Coordinate=Coordinate
+EntryCAP.Coordinate=coordinate
EntryCAP.Altitude=Altitude or 25000
EntryCAP.Speed=Speed or 300
EntryCAP.Heading=Heading or 90
EntryCAP.LegLength=LegLength or 15
self.ManagedCP[#self.ManagedCP+1]=EntryCAP
if self.debug then
-local mark=MARKER:New(Coordinate,self.lid.."Patrol Point"):ToAll()
+local mark=MARKER:New(coordinate,self.lid.."Patrol Point"):ToAll()
end
return self
end
@@ -111782,6 +116013,11 @@ function EASYGCICAP:_SetTankerPatrolPoints()
self:T(self.lid.."_SetTankerPatrolPoints")
for _,_data in pairs(self.ManagedTK)do
local data=_data
+self:T("Airbasename = "..data.AirbaseName)
+if not self.wings[data.AirbaseName]then
+MESSAGE:New(self.lid.."You are trying to create a TANKER point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog()
+return
+end
local Wing=self.wings[data.AirbaseName][1]
local Coordinate=data.Coordinate
local Altitude=data.Altitude
@@ -111796,6 +116032,11 @@ function EASYGCICAP:_SetAwacsPatrolPoints()
self:T(self.lid.."_SetAwacsPatrolPoints")
for _,_data in pairs(self.ManagedEWR)do
local data=_data
+self:T("Airbasename = "..data.AirbaseName)
+if not self.wings[data.AirbaseName]then
+MESSAGE:New(self.lid.."You are trying to create an AWACS point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog()
+return
+end
local Wing=self.wings[data.AirbaseName][1]
local Coordinate=data.Coordinate
local Altitude=data.Altitude
@@ -111810,20 +116051,35 @@ function EASYGCICAP:_SetCAPPatrolPoints()
self:T(self.lid.."_SetCAPPatrolPoints")
for _,_data in pairs(self.ManagedCP)do
local data=_data
+self:T("Airbasename = "..data.AirbaseName)
+if not self.wings[data.AirbaseName]then
+MESSAGE:New(self.lid.."You are trying to create a CAP point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog()
+return
+end
local Wing=self.wings[data.AirbaseName][1]
local Coordinate=data.Coordinate
local Altitude=data.Altitude
local Speed=data.Speed
local Heading=data.Heading
local LegLength=data.LegLength
+local Zone=_data.Zone
+if Zone then
+Wing:AddPatrolPointCAP(Zone,Altitude,Speed,Heading,LegLength)
+else
Wing:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength)
end
+end
return self
end
function EASYGCICAP:_SetReconPatrolPoints()
self:T(self.lid.."_SetReconPatrolPoints")
for _,_data in pairs(self.ManagedREC)do
local data=_data
+self:T("Airbasename = "..data.AirbaseName)
+if not self.wings[data.AirbaseName]then
+MESSAGE:New(self.lid.."You are trying to create a RECON point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog()
+return
+end
local Wing=self.wings[data.AirbaseName][1]
local Coordinate=data.Coordinate
local Altitude=data.Altitude
@@ -111961,7 +116217,9 @@ Squadron_One:SetLivery(Livery)
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
Squadron_One:SetMissionRange(self.missionrange)
Squadron_One:SetRadio(Frequency,Modulation)
+if TACAN then
Squadron_One:AddTacanChannel(TACAN,TACAN)
+end
local wing=self.wings[AirbaseName][1]
wing:AddSquadron(Squadron_One)
wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.TANKER},75)
@@ -111984,7 +116242,7 @@ wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.AWACS},75)
return self
end
function EASYGCICAP:AddAcceptZone(Zone)
-self:T(self.lid.."AddAcceptZone0")
+self:T(self.lid.."AddAcceptZone")
self.GoZoneSet:AddZone(Zone)
return self
end
@@ -111993,20 +116251,26 @@ self:T(self.lid.."AddRejectZone")
self.NoGoZoneSet:AddZone(Zone)
return self
end
+function EASYGCICAP:AddConflictZone(Zone)
+self:T(self.lid.."AddConflictZone")
+self.ConflictZoneSet:AddZone(Zone)
+self.GoZoneSet:AddZone(Zone)
+return self
+end
function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize)
-self:I("_TryAssignIntercept for size "..WingSize or 1)
+self:T("_TryAssignIntercept for size "..WingSize or 1)
local assigned=false
local wingsize=WingSize or 1
local mindist=0
local disttable={}
if Group and Group:IsAlive()then
local gcoord=Group:GetCoordinate()or COORDINATE:New(0,0,0)
-self:I(self.lid..string.format("Assignment for %s",Group:GetName()))
+self:T(self.lid..string.format("Assignment for %s",Group:GetName()))
for _name,_FG in pairs(ReadyFlightGroups or{})do
local FG=_FG
local fcoord=FG:GetCoordinate()
local dist=math.floor(UTILS.Round(fcoord:Get2DDistance(gcoord)/1000,1))
-self:I(self.lid..string.format("FG %s Distance %dkm",_name,dist))
+self:T(self.lid..string.format("FG %s Distance %dkm",_name,dist))
disttable[#disttable+1]={FG=FG,dist=dist}
if dist>mindist then mindist=dist end
end
@@ -112020,7 +116284,7 @@ FG:AddMission(InterceptAuftrag)
local cm=FG:GetMissionCurrent()
if cm then cm:Cancel()end
wingsize=wingsize-1
-self:I(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist))
+self:T(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist))
if wingsize==0 then
assigned=true
break
@@ -112037,8 +116301,9 @@ local maxsize=self.maxinterceptsize
local repeatsonfailure=self.repeatsonfailure
local wings=self.wings
local ctlpts=self.ManagedCP
-local MaxAliveMissions=self.MaxAliveMissions*self.capgrouping
+local MaxAliveMissions=self.MaxAliveMissions
local nogozoneset=self.NoGoZoneSet
+local conflictzoneset=self.ConflictZoneSet
local ReadyFlightGroups=self.ReadyFlightGroups
if Cluster.ctype~=INTEL.Ctype.AIRCRAFT then return end
local contact=self.Intel:GetHighestThreatContact(Cluster)
@@ -112076,6 +116341,9 @@ for _,_data in pairs(ctlpts)do
local data=_data
local name=data.AirbaseName
local zonecoord=data.Coordinate
+if data.Zone then
+zonecoord=data.Zone:GetCoordinate()
+end
local airwing=wings[name][1]
local coa=AIRBASE:FindByName(name):GetCoalition()
local samecoalitionab=coa==self.coalition and true or false
@@ -112091,8 +116359,9 @@ local text=string.format("Closest Airwing is %s",targetawname)
local m=MESSAGE:New(text,10,"CAPGCI"):ToAllIf(self.debug):ToLog()
if targetairwing then
local AssetCount=targetairwing:CountAssetsOnMission(MissionTypes,Cohort)
+local missioncount=self:_CountAliveAuftrags()
self:T(self.lid.." Assets on Mission "..AssetCount)
-if AssetCount<=MaxAliveMissions then
+if missioncount0 then
InterceptAuftrag:AddConditionSuccess(
-function(group,zoneset)
+function(group,zoneset,conflictset)
local success=false
if group and group:IsAlive()then
local coord=group:GetCoordinate()
-if coord and zoneset:IsCoordinateInZone(coord)then
+if coord and zoneset:Count()>0 and zoneset:IsCoordinateInZone(coord)then
success=true
end
+if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then
+success=false
+end
end
return success
end,
contact.group,
-nogozoneset
+nogozoneset,
+conflictzoneset
)
end
table.insert(self.ListOfAuftrag,InterceptAuftrag)
@@ -112132,13 +116405,14 @@ end
function EASYGCICAP:_StartIntel()
self:T(self.lid.."_StartIntel")
local BlueAir_DetectionSetGroup=SET_GROUP:New()
-BlueAir_DetectionSetGroup:FilterPrefixes({self.EWRName})
+BlueAir_DetectionSetGroup:FilterPrefixes(self.EWRName)
BlueAir_DetectionSetGroup:FilterStart()
-local BlueIntel=INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname,self.EWRName)
+local BlueIntel=INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname,self.alias)
BlueIntel:SetClusterAnalysis(true,false,false)
BlueIntel:SetForgetTime(300)
BlueIntel:SetAcceptZones(self.GoZoneSet)
BlueIntel:SetRejectZones(self.NoGoZoneSet)
+BlueIntel:SetConflictZones(self.ConflictZoneSet)
BlueIntel:SetVerbosity(0)
BlueIntel:Start()
if self.debug then
@@ -112147,7 +116421,7 @@ end
local function AssignCluster(Cluster)
self:_AssignIntercept(Cluster)
end
-function BlueIntel:OnAfterNewCluster(From,Event,To,Cluster)
+function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster)
AssignCluster(Cluster)
end
self.Intel=BlueIntel
@@ -112234,12 +116508,14 @@ local threatcount=#self.Intel.Clusters or 0
local text="GCICAP "..self.alias
text=text.."\nWings: "..wings.."\nSquads: "..squads.."\nCapPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock
text=text.."\nThreats: "..threatcount
-text=text.."\nMissions: "..capmission+interceptmission
+text=text.."\nAirWing managed Missions: "..capmission+awacsmission+tankermission+reconmission
text=text.."\n - CAP: "..capmission
-text=text.."\n - Intercept: "..interceptmission
text=text.."\n - AWACS: "..awacsmission
text=text.."\n - TANKER: "..tankermission
text=text.."\n - Recon: "..reconmission
+text=text.."\nSelf managed Missions:"
+text=text.."\n - Mission Limit: "..self.MaxAliveMissions
+text=text.."\n - Alert5+Intercept "..self:_CountAliveAuftrags()
MESSAGE:New(text,15,"GCICAP"):ToAll():ToLogIf(self.debug)
end
self:__Status(30)
@@ -112248,6 +116524,9 @@ end
function EASYGCICAP:onafterStop(From,Event,To)
self:T({From,Event,To})
self.Intel:Stop()
+for _,_wing in pairs(self.wings or{})do
+_wing:Stop()
+end
return self
end
AI_BALANCER={
@@ -112312,7 +116591,7 @@ local SwitchWayPointCommand=AIGroup:CommandSwitchWayPoint(1,WayPointCount,1)
AIGroup:SetCommand(SwitchWayPointCommand)
AIGroup:MessageToRed("Returning to home base ...",30)
else
-local PointVec2=POINT_VEC2:New(AIGroup:GetVec2().x,AIGroup:GetVec2().y)
+local PointVec2=COORDINATE:New(AIGroup:GetVec2().x,0,AIGroup:GetVec2().y)
local ClosestAirbase=self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2(PointVec2)
self:T(ClosestAirbase.AirbaseName)
AIGroup:RouteRTB(ClosestAirbase)
@@ -112597,15 +116876,15 @@ return
end
local FromRTBRoutePoint=FromCoord:WaypointAir(
self.PatrolAltType,
-POINT_VEC3.RoutePointType.TurningPoint,
-POINT_VEC3.RoutePointAction.TurningPoint,
+COORDINATE.WaypointType.TurningPoint,
+COORDINATE.WaypointAction.TurningPoint,
RTBSpeed,
true
)
local ToRTBRoutePoint=ToAirbaseCoord:WaypointAir(
self.PatrolAltType,
-POINT_VEC3.RoutePointType.TurningPoint,
-POINT_VEC3.RoutePointAction.TurningPoint,
+COORDINATE.WaypointType.TurningPoint,
+COORDINATE.WaypointAction.TurningPoint,
RTBSpeed,
true
)
@@ -112654,8 +116933,8 @@ local RefuelRoute={}
local FromRefuelCoord=AIGroup:GetCoordinate()
local ToRefuelCoord=Tanker:GetCoordinate()
local ToRefuelSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed)
-local FromRefuelRoutePoint=FromRefuelCoord:WaypointAir(self.PatrolAltType,POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,ToRefuelSpeed,true)
-local ToRefuelRoutePoint=Tanker:GetCoordinate():WaypointAir(self.PatrolAltType,POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,ToRefuelSpeed,true)
+local FromRefuelRoutePoint=FromRefuelCoord:WaypointAir(self.PatrolAltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,ToRefuelSpeed,true)
+local ToRefuelRoutePoint=Tanker:GetCoordinate():WaypointAir(self.PatrolAltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,ToRefuelSpeed,true)
self:F({ToRefuelSpeed=ToRefuelSpeed})
RefuelRoute[#RefuelRoute+1]=FromRefuelRoutePoint
RefuelRoute[#RefuelRoute+1]=ToRefuelRoutePoint
@@ -112765,7 +117044,7 @@ ToTargetCoord:SetAlt(altitude)
self:SetTargetDistance(ToTargetCoord)
local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed)
local speedkmh=ToTargetSpeed
-local FromWP=CurrentCoord:WaypointAir(self.PatrolAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,ToTargetSpeed,true)
+local FromWP=CurrentCoord:WaypointAir(self.PatrolAltType or"RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,ToTargetSpeed,true)
PatrolRoute[#PatrolRoute+1]=FromWP
if self.racetrack then
local heading=math.random(self.racetrackheadingmin,self.racetrackheadingmax)
@@ -112788,7 +117067,7 @@ local taskCond=AIPatrol:TaskCondition(nil,nil,nil,nil,duration,nil)
local taskCont=AIPatrol:TaskControlled(taskOrbit,taskCond)
PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType,speedkmh,{taskCont,taskPatrol},"CAP Orbit")
else
-local ToWP=ToTargetCoord:WaypointAir(self.PatrolAltType,POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,ToTargetSpeed,true)
+local ToWP=ToTargetCoord:WaypointAir(self.PatrolAltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,ToTargetSpeed,true)
PatrolRoute[#PatrolRoute+1]=ToWP
local Tasks={}
Tasks[#Tasks+1]=AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute",self)
@@ -112891,12 +117170,12 @@ self:__Engage(0.1,AttackSetUnit)
else
local EngageRoute={}
local AttackTasks={}
-local FromWP=DefenderCoord:WaypointAir(self.PatrolAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,EngageSpeed,true)
+local FromWP=DefenderCoord:WaypointAir(self.PatrolAltType or"RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,EngageSpeed,true)
EngageRoute[#EngageRoute+1]=FromWP
self:SetTargetDistance(TargetCoord)
local FromEngageAngle=DefenderCoord:GetAngleDegrees(DefenderCoord:GetDirectionVec3(TargetCoord))
local ToCoord=DefenderCoord:Translate(EngageDistance,FromEngageAngle,true)
-local ToWP=ToCoord:WaypointAir(self.PatrolAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,EngageSpeed,true)
+local ToWP=ToCoord:WaypointAir(self.PatrolAltType or"RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,EngageSpeed,true)
EngageRoute[#EngageRoute+1]=ToWP
AttackTasks[#AttackTasks+1]=DefenderGroup:TaskFunction("AI_AIR_ENGAGE.___EngageRoute",self,AttackSetUnit)
EngageRoute[#EngageRoute].task=DefenderGroup:TaskCombo(AttackTasks)
@@ -112939,12 +117218,12 @@ local TargetDistance=DefenderCoord:Get2DDistance(TargetCoord)
local EngageDistance=(DefenderGroup:IsHelicopter()and 5000)or(DefenderGroup:IsAirPlane()and 10000)
local EngageRoute={}
local AttackTasks={}
-local FromWP=DefenderCoord:WaypointAir(self.EngageAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,EngageSpeed,true)
+local FromWP=DefenderCoord:WaypointAir(self.EngageAltType or"RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,EngageSpeed,true)
EngageRoute[#EngageRoute+1]=FromWP
self:SetTargetDistance(TargetCoord)
local FromEngageAngle=DefenderCoord:GetAngleDegrees(DefenderCoord:GetDirectionVec3(TargetCoord))
local ToCoord=DefenderCoord:Translate(EngageDistance,FromEngageAngle,true)
-local ToWP=ToCoord:WaypointAir(self.EngageAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,EngageSpeed,true)
+local ToWP=ToCoord:WaypointAir(self.EngageAltType or"RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,EngageSpeed,true)
EngageRoute[#EngageRoute+1]=ToWP
if TargetDistance<=EngageDistance*9 then
local AttackUnitTasks=self:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude)
@@ -116491,12 +120770,12 @@ self:T("Not in the air, finding route path within PatrolZone")
local CurrentVec2=self.Controllable:GetVec2()
if not CurrentVec2 then return end
local CurrentAltitude=self.Controllable:GetAltitude()
-local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
+local CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
local ToPatrolZoneSpeed=self.PatrolMaxSpeed
local CurrentRoutePoint=CurrentPointVec3:WaypointAir(
self.PatrolAltType,
-POINT_VEC3.RoutePointType.TakeOffParking,
-POINT_VEC3.RoutePointAction.FromParkingArea,
+COORDINATE.WaypointType.TakeOffParking,
+COORDINATE.WaypointAction.FromParkingArea,
ToPatrolZoneSpeed,
true
)
@@ -116506,12 +120785,12 @@ self:T("In the air, finding route path within PatrolZone")
local CurrentVec2=self.Controllable:GetVec2()
if not CurrentVec2 then return end
local CurrentAltitude=self.Controllable:GetAltitude()
-local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
+local CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
local ToPatrolZoneSpeed=self.PatrolMaxSpeed
local CurrentRoutePoint=CurrentPointVec3:WaypointAir(
self.PatrolAltType,
-POINT_VEC3.RoutePointType.TurningPoint,
-POINT_VEC3.RoutePointAction.TurningPoint,
+COORDINATE.WaypointType.TurningPoint,
+COORDINATE.WaypointAction.TurningPoint,
ToPatrolZoneSpeed,
true
)
@@ -116522,11 +120801,11 @@ self:T2(ToTargetVec2)
local ToTargetAltitude=math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude)
local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed)
self:T2({self.PatrolMinSpeed,self.PatrolMaxSpeed,ToTargetSpeed})
-local ToTargetPointVec3=POINT_VEC3:New(ToTargetVec2.x,ToTargetAltitude,ToTargetVec2.y)
+local ToTargetPointVec3=COORDINATE:New(ToTargetVec2.x,ToTargetAltitude,ToTargetVec2.y)
local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir(
self.PatrolAltType,
-POINT_VEC3.RoutePointType.TurningPoint,
-POINT_VEC3.RoutePointAction.TurningPoint,
+COORDINATE.WaypointType.TurningPoint,
+COORDINATE.WaypointAction.TurningPoint,
ToTargetSpeed,
true
)
@@ -116552,13 +120831,21 @@ local OrbitTask=OldAIControllable:TaskOrbitCircle(math.random(self.PatrolFloorAl
local TimedOrbitTask=OldAIControllable:TaskControlled(OrbitTask,OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil))
OldAIControllable:SetTask(TimedOrbitTask,10)
RTB=true
-else
end
local Damage=self.Controllable:GetLife()
if Damage<=self.PatrolDamageThreshold then
self:T(self.Controllable:GetName().." is damaged:"..Damage..", RTB!")
RTB=true
end
+if self:IsInstanceOf("AI_CAS")or self:IsInstanceOf("AI_BAI")then
+local atotal,shells,rockets,bombs,missiles=self.Controllable:GetAmmunition()
+local arelevant=rockets+bombs
+if arelevant==0 or missiles==0 then
+RTB=true
+self:T({total=atotal,shells=shells,rockets=rockets,bombs=bombs,missiles=missiles})
+self:T(self.Controllable:GetName().." is out of ammo, RTB!")
+end
+end
if RTB==true then
self:RTB()
else
@@ -116575,12 +120862,12 @@ local PatrolRoute={}
local CurrentVec2=self.Controllable:GetVec2()
if not CurrentVec2 then return end
local CurrentAltitude=self.Controllable:GetAltitude()
-local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
+local CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
local ToPatrolZoneSpeed=self.PatrolMaxSpeed
local CurrentRoutePoint=CurrentPointVec3:WaypointAir(
self.PatrolAltType,
-POINT_VEC3.RoutePointType.TurningPoint,
-POINT_VEC3.RoutePointAction.TurningPoint,
+COORDINATE.WaypointType.TurningPoint,
+COORDINATE.WaypointAction.TurningPoint,
ToPatrolZoneSpeed,
true
)
@@ -116682,12 +120969,12 @@ local EngageRoute={}
local CurrentVec2=self.Controllable:GetVec2()
if not CurrentVec2 then return self end
local CurrentAltitude=self.Controllable:GetAltitude()
-local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
+local CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
local ToEngageZoneSpeed=self.PatrolMaxSpeed
local CurrentRoutePoint=CurrentPointVec3:WaypointAir(
self.PatrolAltType,
-POINT_VEC3.RoutePointType.TurningPoint,
-POINT_VEC3.RoutePointAction.TurningPoint,
+COORDINATE.WaypointType.TurningPoint,
+COORDINATE.WaypointAction.TurningPoint,
ToEngageZoneSpeed,
true
)
@@ -116697,11 +120984,11 @@ self:T2(ToTargetVec2)
local ToTargetAltitude=math.random(self.EngageFloorAltitude,self.EngageCeilingAltitude)
local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed)
self:T2({self.PatrolMinSpeed,self.PatrolMaxSpeed,ToTargetSpeed})
-local ToTargetPointVec3=POINT_VEC3:New(ToTargetVec2.x,ToTargetAltitude,ToTargetVec2.y)
+local ToTargetPointVec3=COORDINATE:New(ToTargetVec2.x,ToTargetAltitude,ToTargetVec2.y)
local ToPatrolRoutePoint=ToTargetPointVec3:WaypointAir(
self.PatrolAltType,
-POINT_VEC3.RoutePointType.TurningPoint,
-POINT_VEC3.RoutePointAction.TurningPoint,
+COORDINATE.WaypointType.TurningPoint,
+COORDINATE.WaypointAction.TurningPoint,
ToTargetSpeed,
true
)
@@ -116845,12 +121132,12 @@ Controllable:OptionROTVertical()
local EngageRoute={}
local CurrentVec2=self.Controllable:GetVec2()
local CurrentAltitude=self.Controllable:GetAltitude()
-local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
+local CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
local ToEngageZoneSpeed=self.PatrolMaxSpeed
local CurrentRoutePoint=CurrentPointVec3:WaypointAir(
self.PatrolAltType,
-POINT_VEC3.RoutePointType.TurningPoint,
-POINT_VEC3.RoutePointAction.TurningPoint,
+COORDINATE.WaypointType.TurningPoint,
+COORDINATE.WaypointAction.TurningPoint,
self.EngageSpeed,
true
)
@@ -116877,11 +121164,11 @@ AttackTasks[#AttackTasks+1]=Controllable:TaskFunction("AI_CAS_ZONE.EngageRoute",
EngageRoute[#EngageRoute].task=Controllable:TaskCombo(AttackTasks)
local ToTargetVec2=self.EngageZone:GetRandomVec2()
self:T2(ToTargetVec2)
-local ToTargetPointVec3=POINT_VEC3:New(ToTargetVec2.x,self.EngageAltitude,ToTargetVec2.y)
+local ToTargetPointVec3=COORDINATE:New(ToTargetVec2.x,self.EngageAltitude,ToTargetVec2.y)
local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir(
self.PatrolAltType,
-POINT_VEC3.RoutePointType.TurningPoint,
-POINT_VEC3.RoutePointAction.TurningPoint,
+COORDINATE.WaypointType.TurningPoint,
+COORDINATE.WaypointAction.TurningPoint,
self.EngageSpeed,
true
)
@@ -117016,12 +121303,12 @@ if Controllable:IsAlive()then
local EngageRoute={}
local CurrentVec2=self.Controllable:GetVec2()
local CurrentAltitude=self.Controllable:GetAltitude()
-local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
+local CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
local ToEngageZoneSpeed=self.PatrolMaxSpeed
local CurrentRoutePoint=CurrentPointVec3:WaypointAir(
self.PatrolAltType,
-POINT_VEC3.RoutePointType.TurningPoint,
-POINT_VEC3.RoutePointAction.TurningPoint,
+COORDINATE.WaypointType.TurningPoint,
+COORDINATE.WaypointAction.TurningPoint,
self.EngageSpeed,
true
)
@@ -117061,11 +121348,11 @@ end
EngageRoute[#EngageRoute].task=Controllable:TaskCombo(AttackTasks)
local ToTargetVec2=self.EngageZone:GetRandomVec2()
self:T2(ToTargetVec2)
-local ToTargetPointVec3=POINT_VEC3:New(ToTargetVec2.x,self.EngageAltitude,ToTargetVec2.y)
+local ToTargetPointVec3=COORDINATE:New(ToTargetVec2.x,self.EngageAltitude,ToTargetVec2.y)
local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir(
self.PatrolAltType,
-POINT_VEC3.RoutePointType.TurningPoint,
-POINT_VEC3.RoutePointAction.TurningPoint,
+COORDINATE.WaypointType.TurningPoint,
+COORDINATE.WaypointAction.TurningPoint,
self.EngageSpeed,
true
)
@@ -117193,7 +121480,7 @@ FollowGroupSet:Flush(self)
local FollowSet=FollowGroupSet:GetSet()
local i=1
for FollowID,FollowGroup in pairs(FollowSet)do
-local PointVec3=POINT_VEC3:New()
+local PointVec3=COORDINATE:New()
PointVec3:SetX(XStart+i*XSpace)
PointVec3:SetY(YStart+i*YSpace)
PointVec3:SetZ(ZStart+i*ZSpace)
@@ -117232,7 +121519,7 @@ function AI_FORMATION:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XS
local FollowSet=FollowGroupSet:GetSet()
local i=0
for FollowID,FollowGroup in pairs(FollowSet)do
-local PointVec3=POINT_VEC3:New()
+local PointVec3=COORDINATE:New()
local Side=(i%2==0)and 1 or-1
local Row=i/2+1
PointVec3:SetX(XStart+Row*XSpace)
@@ -117253,7 +121540,7 @@ function AI_FORMATION:onafterFormationBox(FollowGroupSet,From,Event,To,XStart,XS
local FollowSet=FollowGroupSet:GetSet()
local i=0
for FollowID,FollowGroup in pairs(FollowSet)do
-local PointVec3=POINT_VEC3:New()
+local PointVec3=COORDINATE:New()
local ZIndex=i%ZLevels
local XIndex=math.floor(i/ZLevels)
local YIndex=math.floor(i/ZLevels)
@@ -119445,8 +123732,8 @@ local landheight=CoordinateTo:GetLandHeight()
CoordinateTo.y=landheight+50
local WaypointTo=CoordinateTo:WaypointAir(
"RADIO",
-POINT_VEC3.RoutePointType.TurningPoint,
-POINT_VEC3.RoutePointAction.TurningPoint,
+COORDINATE.WaypointType.TurningPoint,
+COORDINATE.WaypointAction.TurningPoint,
50,
true
)
@@ -119472,7 +123759,7 @@ local Route={}
local CoordinateTo=Coordinate
local landheight=CoordinateTo:GetLandHeight()
CoordinateTo.y=landheight+50
-local WaypointTo=CoordinateTo:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,50,true)
+local WaypointTo=CoordinateTo:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,50,true)
Route[#Route+1]=WaypointTo
local Tasks={}
Tasks[#Tasks+1]=Helicopter:TaskOrbitCircle(math.random(30,80),150,CoordinateTo:GetRandomCoordinateInRadius(800,500))
@@ -119500,11 +123787,11 @@ Coordinate.y=Height
local _speed=Speed or Helicopter:GetSpeedMax()*0.5
local Route={}
local CoordinateFrom=Helicopter:GetCoordinate()
-local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,_speed,true)
+local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,_speed,true)
local CoordinateTo=Coordinate
local landheight=CoordinateTo:GetLandHeight()
CoordinateTo.y=landheight+50
-local WaypointTo=CoordinateTo:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,_speed,true)
+local WaypointTo=CoordinateTo:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,_speed,true)
Route[#Route+1]=WaypointFrom
Route[#Route+1]=WaypointTo
Helicopter:WayPointInitialize(Route)
@@ -119528,13 +123815,13 @@ local Route={}
Coordinate.y=Height
local _speed=Speed or Helicopter:GetSpeedMax()*0.5
local CoordinateFrom=Helicopter:GetCoordinate()
-local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,_speed,true)
+local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,_speed,true)
Route[#Route+1]=WaypointFrom
Route[#Route+1]=WaypointFrom
local CoordinateTo=Coordinate
local landheight=CoordinateTo:GetLandHeight()
CoordinateTo.y=landheight+50
-local WaypointTo=CoordinateTo:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,_speed,true)
+local WaypointTo=CoordinateTo:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,_speed,true)
Route[#Route+1]=WaypointTo
Route[#Route+1]=WaypointTo
Helicopter:WayPointInitialize(Route)
@@ -119555,12 +123842,12 @@ local Route={}
Height=Height or 50
Speed=Speed or Helicopter:GetSpeedMax()*0.5
local CoordinateFrom=Helicopter:GetCoordinate()
-local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,Speed,true)
+local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true)
Route[#Route+1]=WaypointFrom
local CoordinateTo=Coordinate
local landheight=CoordinateTo:GetLandHeight()
CoordinateTo.y=landheight+Height
-local WaypointTo=CoordinateTo:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,Speed,true)
+local WaypointTo=CoordinateTo:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true)
Route[#Route+1]=WaypointTo
Helicopter:WayPointInitialize(Route)
local Tasks={}
@@ -119713,7 +124000,7 @@ return
end
local Points={}
local AirbasePointVec2=Airbase:GetPointVec2()
-local ToWaypoint=AirbasePointVec2:WaypointAir(POINT_VEC3.RoutePointAltType.BARO,"Land","Landing",Speed or Airplane:GetSpeedMax()*0.8,true,Airbase)
+local ToWaypoint=AirbasePointVec2:WaypointAir(COORDINATE.WaypointAltType.BARO,"Land","Landing",Speed or Airplane:GetSpeedMax()*0.8,true,Airbase)
if self.Airbase then
Template.route.points[2]=ToWaypoint
Airplane:RespawnAtCurrentAirbase(Template,Takeoff,Uncontrolled)
@@ -120585,7 +124872,7 @@ self.DisplayCount=self.DisplayCount+1
end
return true
end
-function ACT_ACCOUNT:onafterEvent(ProcessUnit,From,Event,To,Event)
+function ACT_ACCOUNT:onafterEvent(ProcessUnit,From,Event,To)
self:__NoMore(1)
end
end
@@ -120978,12 +125265,18 @@ end
return true
end
function CIRCLE:GetRandomVec2()
+math.random()
+math.random()
+math.random()
local angle=math.random()*2*math.pi
local rx=math.random(0,self.Radius)*math.cos(angle)+self.CenterVec2.x
local ry=math.random(0,self.Radius)*math.sin(angle)+self.CenterVec2.y
return{x=rx,y=ry}
end
function CIRCLE:GetRandomVec2OnBorder()
+math.random()
+math.random()
+math.random()
local angle=math.random()*2*math.pi
local rx=self.Radius*math.cos(angle)+self.CenterVec2.x
local ry=self.Radius*math.sin(angle)+self.CenterVec2.y
@@ -121666,6 +125959,9 @@ local has_pos=(d1>0)or(d2>0)or(d3>0)
return not(has_neg and has_pos)
end
function TRIANGLE:GetRandomVec2(points)
+math.random()
+math.random()
+math.random()
points=points or self.Points
local pt={math.random(),math.random()}
table.sort(pt)
@@ -122211,7 +126507,6 @@ local sender=self:_GetRadioSender()
local filename=string.format("%s%s",transmission.path,transmission.filename)
if sender then
self:T(self.lid..string.format("Broadcasting from aircraft %s",sender:GetName()))
-if not self.senderinit then
local commandFrequency={
id="SetFrequency",
params={
@@ -122220,7 +126515,6 @@ modulation=self.modulation,
}}
sender:SetCommand(commandFrequency)
self.senderinit=true
-end
local subtitle=nil
local duration=nil
if transmission.subtitle and transmission.subduration and transmission.subduration>0 then
@@ -122639,6 +126933,135 @@ UsePowerShell=false,
}
MSRS.version="0.3.3"
MSRS.Voices={
+Amazon={
+Generative={
+en_AU={
+Olivia="Olivia",
+},
+en_GB={
+Amy="Amy",
+},
+en_US={
+Danielle="Danielle",
+Joanna="Joanna",
+Ruth="Ruth",
+Stephen="Stephen",
+},
+fr_FR={
+["Léa"]="Léa",
+["Rémi"]="Rémi",
+},
+de_DE={
+Vicki="Vicki",
+Daniel="Daniel",
+},
+it_IT={
+Bianca="Bianca",
+Adriano="Adriano",
+},
+es_ES={
+Lucia="Lucia",
+Sergio="Sergio",
+},
+},
+LongForm={
+en_US={
+Danielle="Danielle",
+Gregory="Gregory",
+Ivy="Ivy",
+Ruth="Ruth",
+Patrick="Patrick",
+},
+es_ES={
+Alba="Alba",
+["Raúl"]="Raúl",
+},
+},
+Neural={
+en_AU={
+Olivia="Olivia",
+},
+en_GB={
+Amy="Amy",
+Emma="Emma",
+Brian="Brian",
+Arthur="Arthur",
+},
+en_US={
+Danielle="Danielle",
+Gregory="Gregory",
+Ivy="Ivy",
+Joanna="Joanna",
+Kendra="Kendra",
+Kimberly="Kimberly",
+Salli="Salli",
+Joey="Joey",
+Kevin="Kevin",
+Ruth="Ruth",
+Stephen="Stephen",
+},
+fr_FR={
+["Léa"]="Léa",
+["Rémi"]="Rémi",
+},
+de_DE={
+Vicki="Vicki",
+Daniel="Daniel",
+},
+it_IT={
+Bianca="Bianca",
+Adriano="Adriano",
+},
+es_ES={
+Lucia="Lucia",
+Sergio="Sergio",
+},
+},
+Standard={
+en_AU={
+Nicole="Nicole",
+Russel="Russel",
+},
+en_GB={
+Amy="Amy",
+Emma="Emma",
+Brian="Brian",
+},
+en_IN={
+Aditi="Aditi",
+Raveena="Raveena",
+},
+en_US={
+Ivy="Ivy",
+Joanna="Joanna",
+Kendra="Kendra",
+Kimberly="Kimberly",
+Salli="Salli",
+Joey="Joey",
+Kevin="Kevin",
+},
+fr_FR={
+Celine="Celine",
+["Léa"]="Léa",
+Mathieu="Mathieu",
+},
+de_DE={
+Marlene="Marlene",
+Vicki="Vicki",
+Hans="Hans",
+},
+it_IT={
+Carla="Carla",
+Bianca="Bianca",
+Giorgio="Giorgio",
+},
+es_ES={
+Conchita="Conchita",
+Lucia="Lucia",
+Enrique="Enrique",
+},
+},
+},
Microsoft={
["Hedda"]="Microsoft Hedda Desktop",
["Hazel"]="Microsoft Hazel Desktop",
@@ -122689,11 +127112,15 @@ Standard={
["en_IN_Standard_B"]='en-IN-Standard-B',
["en_IN_Standard_C"]='en-IN-Standard-C',
["en_IN_Standard_D"]='en-IN-Standard-D',
+["en_IN_Standard_E"]='en-IN-Standard-E',
+["en_IN_Standard_F"]='en-IN-Standard-F',
["en_GB_Standard_A"]='en-GB-Standard-A',
["en_GB_Standard_B"]='en-GB-Standard-B',
["en_GB_Standard_C"]='en-GB-Standard-C',
["en_GB_Standard_D"]='en-GB-Standard-D',
["en_GB_Standard_F"]='en-GB-Standard-F',
+["en_GB_Standard_N"]='en-GB-Standard-N',
+["en_GB_Standard_O"]='en-GB-Standard-O',
["en_US_Standard_A"]='en-US-Standard-A',
["en_US_Standard_B"]='en-US-Standard-B',
["en_US_Standard_C"]='en-US-Standard-C',
@@ -122704,25 +127131,33 @@ Standard={
["en_US_Standard_H"]='en-US-Standard-H',
["en_US_Standard_I"]='en-US-Standard-I',
["en_US_Standard_J"]='en-US-Standard-J',
-["fr_FR_Standard_A"]="fr-FR-Standard-A",
-["fr_FR_Standard_B"]="fr-FR-Standard-B",
-["fr_FR_Standard_C"]="fr-FR-Standard-C",
-["fr_FR_Standard_D"]="fr-FR-Standard-D",
-["fr_FR_Standard_E"]="fr-FR-Standard-E",
-["de_DE_Standard_A"]="de-DE-Standard-A",
-["de_DE_Standard_B"]="de-DE-Standard-B",
-["de_DE_Standard_C"]="de-DE-Standard-C",
-["de_DE_Standard_D"]="de-DE-Standard-D",
-["de_DE_Standard_E"]="de-DE-Standard-E",
-["de_DE_Standard_F"]="de-DE-Standard-F",
-["es_ES_Standard_A"]="es-ES-Standard-A",
-["es_ES_Standard_B"]="es-ES-Standard-B",
-["es_ES_Standard_C"]="es-ES-Standard-C",
-["es_ES_Standard_D"]="es-ES-Standard-D",
-["it_IT_Standard_A"]="it-IT-Standard-A",
-["it_IT_Standard_B"]="it-IT-Standard-B",
-["it_IT_Standard_C"]="it-IT-Standard-C",
-["it_IT_Standard_D"]="it-IT-Standard-D",
+["fr_FR_Standard_A"]="fr-FR-Standard-F",
+["fr_FR_Standard_B"]="fr-FR-Standard-G",
+["fr_FR_Standard_C"]="fr-FR-Standard-F",
+["fr_FR_Standard_D"]="fr-FR-Standard-G",
+["fr_FR_Standard_E"]="fr-FR-Standard-F",
+["fr_FR_Standard_G"]="fr-FR-Standard-G",
+["fr_FR_Standard_F"]="fr-FR-Standard-F",
+["de_DE_Standard_A"]='de-DE-Standard-A',
+["de_DE_Standard_B"]='de-DE-Standard-B',
+["de_DE_Standard_C"]='de-DE-Standard-C',
+["de_DE_Standard_D"]='de-DE-Standard-D',
+["de_DE_Standard_E"]='de-DE-Standard-E',
+["de_DE_Standard_F"]='de-DE-Standard-F',
+["de_DE_Standard_G"]='de-DE-Standard-G',
+["de_DE_Standard_H"]='de-DE-Standard-H',
+["es_ES_Standard_A"]="es-ES-Standard-E",
+["es_ES_Standard_B"]="es-ES-Standard-F",
+["es_ES_Standard_C"]="es-ES-Standard-E",
+["es_ES_Standard_D"]="es-ES-Standard-F",
+["es_ES_Standard_E"]="es-ES-Standard-E",
+["es_ES_Standard_F"]="es-ES-Standard-F",
+["it_IT_Standard_A"]="it-IT-Standard-E",
+["it_IT_Standard_B"]="it-IT-Standard-E",
+["it_IT_Standard_C"]="it-IT-Standard-F",
+["it_IT_Standard_D"]="it-IT-Standard-F",
+["it_IT_Standard_E"]="it-IT-Standard-E",
+["it_IT_Standard_F"]="it-IT-Standard-F",
},
Wavenet={
["en_AU_Wavenet_A"]='en-AU-Wavenet-A',
@@ -122733,11 +127168,15 @@ Wavenet={
["en_IN_Wavenet_B"]='en-IN-Wavenet-B',
["en_IN_Wavenet_C"]='en-IN-Wavenet-C',
["en_IN_Wavenet_D"]='en-IN-Wavenet-D',
+["en_IN_Wavenet_E"]='en-IN-Wavenet-E',
+["en_IN_Wavenet_F"]='en-IN-Wavenet-F',
["en_GB_Wavenet_A"]='en-GB-Wavenet-A',
["en_GB_Wavenet_B"]='en-GB-Wavenet-B',
["en_GB_Wavenet_C"]='en-GB-Wavenet-C',
["en_GB_Wavenet_D"]='en-GB-Wavenet-D',
["en_GB_Wavenet_F"]='en-GB-Wavenet-F',
+["en_GB_Wavenet_O"]='en-GB-Wavenet-O',
+["en_GB_Wavenet_N"]='en-GB-Wavenet-N',
["en_US_Wavenet_A"]='en-US-Wavenet-A',
["en_US_Wavenet_B"]='en-US-Wavenet-B',
["en_US_Wavenet_C"]='en-US-Wavenet-C',
@@ -122748,25 +127187,144 @@ Wavenet={
["en_US_Wavenet_H"]='en-US-Wavenet-H',
["en_US_Wavenet_I"]='en-US-Wavenet-I',
["en_US_Wavenet_J"]='en-US-Wavenet-J',
-["fr_FR_Wavenet_A"]="fr-FR-Wavenet-A",
-["fr_FR_Wavenet_B"]="fr-FR-Wavenet-B",
-["fr_FR_Wavenet_C"]="fr-FR-Wavenet-C",
-["fr_FR_Wavenet_D"]="fr-FR-Wavenet-D",
-["fr_FR_Wavenet_E"]="fr-FR-Wavenet-E",
-["de_DE_Wavenet_A"]="de-DE-Wavenet-A",
-["de_DE_Wavenet_B"]="de-DE-Wavenet-B",
-["de_DE_Wavenet_C"]="de-DE-Wavenet-C",
-["de_DE_Wavenet_D"]="de-DE-Wavenet-D",
-["de_DE_Wavenet_E"]="de-DE-Wavenet-E",
-["de_DE_Wavenet_F"]="de-DE-Wavenet-F",
-["es_ES_Wavenet_B"]="es-ES-Wavenet-B",
-["es_ES_Wavenet_C"]="es-ES-Wavenet-C",
-["es_ES_Wavenet_D"]="es-ES-Wavenet-D",
-["it_IT_Wavenet_A"]="it-IT-Wavenet-A",
-["it_IT_Wavenet_B"]="it-IT-Wavenet-B",
-["it_IT_Wavenet_C"]="it-IT-Wavenet-C",
-["it_IT_Wavenet_D"]="it-IT-Wavenet-D",
+["fr_FR_Wavenet_A"]="fr-FR-Wavenet-F",
+["fr_FR_Wavenet_B"]="fr-FR-Wavenet-G",
+["fr_FR_Wavenet_C"]="fr-FR-Wavenet-F",
+["fr_FR_Wavenet_D"]="fr-FR-Wavenet-G",
+["fr_FR_Wavenet_E"]="fr-FR-Wavenet-F",
+["fr_FR_Wavenet_G"]="fr-FR-Wavenet-G",
+["fr_FR_Wavenet_F"]="fr-FR-Wavenet-F",
+["de_DE_Wavenet_A"]='de-DE-Wavenet-A',
+["de_DE_Wavenet_B"]='de-DE-Wavenet-B',
+["de_DE_Wavenet_C"]='de-DE-Wavenet-C',
+["de_DE_Wavenet_D"]='de-DE-Wavenet-D',
+["de_DE_Wavenet_E"]='de-DE-Wavenet-E',
+["de_DE_Wavenet_F"]='de-DE-Wavenet-F',
+["de_DE_Wavenet_G"]='de-DE-Wavenet-G',
+["de_DE_Wavenet_H"]='de-DE-Wavenet-H',
+["es_ES_Wavenet_B"]="es-ES-Wavenet-E",
+["es_ES_Wavenet_C"]="es-ES-Wavenet-F",
+["es_ES_Wavenet_D"]="es-ES-Wavenet-E",
+["es_ES_Wavenet_E"]="es-ES-Wavenet-E",
+["es_ES_Wavenet_F"]="es-ES-Wavenet-F",
+["it_IT_Wavenet_A"]="it-IT-Wavenet-E",
+["it_IT_Wavenet_B"]="it-IT-Wavenet-E",
+["it_IT_Wavenet_C"]="it-IT-Wavenet-F",
+["it_IT_Wavenet_D"]="it-IT-Wavenet-F",
+["it_IT_Wavenet_E"]="it-IT-Wavenet-E",
+["it_IT_Wavenet_F"]="it-IT-Wavenet-F",
},
+Chirp3HD={
+["en_GB_Chirp3_HD_Aoede"]='en-GB-Chirp3-HD-Aoede',
+["en_GB_Chirp3_HD_Charon"]='en-GB-Chirp3-HD-Charon',
+["en_GB_Chirp3_HD_Fenrir"]='en-GB-Chirp3-HD-Fenrir',
+["en_GB_Chirp3_HD_Kore"]='en-GB-Chirp3-HD-Kore',
+["en_GB_Chirp3_HD_Leda"]='en-GB-Chirp3-HD-Leda',
+["en_GB_Chirp3_HD_Orus"]='en-GB-Chirp3-HD-Orus',
+["en_GB_Chirp3_HD_Puck"]='en-GB-Chirp3-HD-Puck',
+["en_GB_Chirp3_HD_Zephyr"]='en-GB-Chirp3-HD-Zephyr',
+["en_US_Chirp3_HD_Charon"]='en-US-Chirp3-HD-Charon',
+["en_US_Chirp3_HD_Fenrir"]='en-US-Chirp3-HD-Fenrir',
+["en_US_Chirp3_HD_Kore"]='en-US-Chirp3-HD-Kore',
+["en_US_Chirp3_HD_Leda"]='en-US-Chirp3-HD-Leda',
+["en_US_Chirp3_HD_Orus"]='en-US-Chirp3-HD-Orus',
+["en_US_Chirp3_HD_Puck"]='en-US-Chirp3-HD-Puck',
+["de_DE_Chirp3_HD_Aoede"]='de-DE-Chirp3-HD-Aoede',
+["de_DE_Chirp3_HD_Charon"]='de-DE-Chirp3-HD-Charon',
+["de_DE_Chirp3_HD_Fenrir"]='de-DE-Chirp3-HD-Fenrir',
+["de_DE_Chirp3_HD_Kore"]='de-DE-Chirp3-HD-Kore',
+["de_DE_Chirp3_HD_Leda"]='de-DE-Chirp3-HD-Leda',
+["de_DE_Chirp3_HD_Orus"]='de-DE-Chirp3-HD-Orus',
+["de_DE_Chirp3_HD_Puck"]='de-DE-Chirp3-HD-Puck',
+["de_DE_Chirp3_HD_Zephyr"]='de-DE-Chirp3-HD-Zephyr',
+["en_AU_Chirp3_HD_Aoede"]='en-AU-Chirp3-HD-Aoede',
+["en_AU_Chirp3_HD_Charon"]='en-AU-Chirp3-HD-Charon',
+["en_AU_Chirp3_HD_Fenrir"]='en-AU-Chirp3-HD-Fenrir',
+["en_AU_Chirp3_HD_Kore"]='en-AU-Chirp3-HD-Kore',
+["en_AU_Chirp3_HD_Leda"]='en-AU-Chirp3-HD-Leda',
+["en_AU_Chirp3_HD_Orus"]='en-AU-Chirp3-HD-Orus',
+["en_AU_Chirp3_HD_Puck"]='en-AU-Chirp3-HD-Puck',
+["en_AU_Chirp3_HD_Zephyr"]='en-AU-Chirp3-HD-Zephyr',
+["en_IN_Chirp3_HD_Aoede"]='en-IN-Chirp3-HD-Aoede',
+["en_IN_Chirp3_HD_Charon"]='en-IN-Chirp3-HD-Charon',
+["en_IN_Chirp3_HD_Fenrir"]='en-IN-Chirp3-HD-Fenrir',
+["en_IN_Chirp3_HD_Kore"]='en-IN-Chirp3-HD-Kore',
+["en_IN_Chirp3_HD_Leda"]='en-IN-Chirp3-HD-Leda',
+["en_IN_Chirp3_HD_Orus"]='en-IN-Chirp3-HD-Orus',
+},
+ChirpHD={
+["en_US_Chirp_HD_D"]='en-US-Chirp-HD-D',
+["en_US_Chirp_HD_F"]='en-US-Chirp-HD-F',
+["en_US_Chirp_HD_O"]='en-US-Chirp-HD-O',
+["de_DE_Chirp_HD_D"]='de-DE-Chirp-HD-D',
+["de_DE_Chirp_HD_F"]='de-DE-Chirp-HD-F',
+["de_DE_Chirp_HD_O"]='de-DE-Chirp-HD-O',
+["en_AU_Chirp_HD_D"]='en-AU-Chirp-HD-D',
+["en_AU_Chirp_HD_F"]='en-AU-Chirp-HD-F',
+["en_AU_Chirp_HD_O"]='en-AU-Chirp-HD-O',
+["en_IN_Chirp_HD_D"]='en-IN-Chirp-HD-D',
+["en_IN_Chirp_HD_F"]='en-IN-Chirp-HD-F',
+["en_IN_Chirp_HD_O"]='en-IN-Chirp-HD-O',
+},
+},
+Neural2={
+["en_GB_Neural2_A"]='en-GB-Neural2-A',
+["en_GB_Neural2_B"]='en-GB-Neural2-B',
+["en_GB_Neural2_C"]='en-GB-Neural2-C',
+["en_GB_Neural2_D"]='en-GB-Neural2-D',
+["en_GB_Neural2_F"]='en-GB-Neural2-F',
+["en_GB_Neural2_N"]='en-GB-Neural2-N',
+["en_GB_Neural2_O"]='en-GB-Neural2-O',
+["en_US_Neural2_A"]='en-US-Neural2-A',
+["en_US_Neural2_C"]='en-US-Neural2-C',
+["en_US_Neural2_D"]='en-US-Neural2-D',
+["en_US_Neural2_E"]='en-US-Neural2-E',
+["en_US_Neural2_F"]='en-US-Neural2-F',
+["en_US_Neural2_G"]='en-US-Neural2-G',
+["en_US_Neural2_H"]='en-US-Neural2-H',
+["en_US_Neural2_I"]='en-US-Neural2-I',
+["en_US_Neural2_J"]='en-US-Neural2-J',
+["de_DE_Neural2_G"]='de-DE-Neural2-G',
+["de_DE_Neural2_H"]='de-DE-Neural2-H',
+["en_AU_Neural2_A"]='en-AU-Neural2-A',
+["en_AU_Neural2_B"]='en-AU-Neural2-B',
+["en_AU_Neural2_C"]='en-AU-Neural2-C',
+["en_AU_Neural2_D"]='en-AU-Neural2-D',
+["en_IN_Neural2_A"]='en-IN-Neural2-A',
+["en_IN_Neural2_B"]='en-IN-Neural2-B',
+["en_IN_Neural2_C"]='en-IN-Neural2-C',
+["en_IN_Neural2_D"]='en-IN-Neural2-D',
+},
+News={
+["en_GB_News_G"]='en-GB-News-G',
+["en_GB_News_H"]='en-GB-News-H',
+["en_GB_News_I"]='en-GB-News-I',
+["en_GB_News_J"]='en-GB-News-J',
+["en_GB_News_K"]='en-GB-News-K',
+["en_GB_News_L"]='en-GB-News-L',
+["en_GB_News_M"]='en-GB-News-M',
+["en_US_News_K"]='en-US-News-K',
+["en_US_News_L"]='en-US-News-L',
+["en_US_News_N"]='en-US-News-N',
+["en_AU_News_E"]='en-AU-News-E',
+["en_AU_News_F"]='en-AU-News-F',
+["en_AU_News_G"]='en-AU-News-G',
+},
+Casual={
+["en_US_Casual_K"]='en-US-Casual-K',
+},
+Polyglot={
+["en_US_Polyglot_1"]='en-US-Polyglot-1',
+["de_DE_Polyglot_1"]='de-DE-Polyglot-1',
+["en_AU_Polyglot_1"]='en-AU-Polyglot-1',
+},
+Studio={
+["en_GB_Studio_B"]='en-GB-Studio-B',
+["en_GB_Studio_C"]='en-GB-Studio-C',
+["en_US_Studio_O"]='en-US-Studio-O',
+["en_US_Studio_Q"]='en-US-Studio-Q',
+["de_DE_Studio_B"]='de-DE-Studio-B',
+["de_DE_Studio_C"]='de-DE-Studio-C',
},
}
MSRS.Backend={
@@ -122861,7 +127419,7 @@ return self.backend
end
function MSRS:SetPath(Path)
self:F({Path=Path})
-self.path=Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
+self.path=Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
local n=1;local nmax=1000
while(self.path:sub(-1)=="/"or self.path:sub(-1)==[[\]])and n<=nmax do
self.path=self.path:sub(1,#self.path-1)
@@ -123119,6 +127677,7 @@ self:ScheduleOnce(Delay,MSRS.PlaySoundFile,self,Soundfile,0)
else
local command=self:_GetCommand()
command=command..' --file="'..tostring(soundfile)..'"'
+command=string.gsub(command,"--ssml","-h")
self:_ExecCommand(command)
end
return self
@@ -123242,7 +127801,7 @@ command=command..string.format(' --ssml -G "%s"',pops.credentials)
pwsh=pwsh..string.format(' --ssml -G "%s"',pops.credentials)
elseif self.provider==MSRS.Provider.WINDOWS then
else
-self:E("ERROR: SRS only supports WINWOWS and GOOGLE as TTS providers! Use DCS-gRPC backend for other providers such as ")
+self:E("ERROR: SRS only supports WINDOWS and GOOGLE as TTS providers! Use DCS-gRPC backend for other providers such as AWS and Azure.")
end
if not UTILS.FileExists(fullPath)then
self:E("ERROR: MSRS SRS executable does not exist! FullPath="..fullPath)
@@ -123263,7 +127822,7 @@ local filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".bat"
if self.UsePowerShell==true then
filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".ps1"
batContent=command.."\'"
-self:I({batContent=batContent})
+self:T({batContent=batContent})
end
local script=io.open(filename,"w+")
script:write(batContent)
@@ -123335,9 +127894,9 @@ ssml=string.format("%s",gender,language,Text)
end
end
for _,freq in pairs(Frequencies)do
-self:F("Calling GRPC.tts with the following parameter:")
-self:F({ssml=ssml,freq=freq,options=options})
-self:F(options.provider[provider])
+self:T("Calling GRPC.tts with the following parameter:")
+self:T({ssml=ssml,freq=freq,options=options})
+self:T(options.provider[provider])
GRPC.tts(ssml,freq*1e6,options)
end
end
@@ -123355,7 +127914,7 @@ env.info("FF reading config file")
assert(loadfile(path..file))()
if MSRS_Config then
local Self=self or MSRS
-Self.path=MSRS_Config.Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
+Self.path=MSRS_Config.Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
Self.port=MSRS_Config.Port or 5002
Self.backend=MSRS_Config.Backend or MSRS.Backend.SRSEXE
Self.frequencies=MSRS_Config.Frequency or{127,243}
@@ -123450,6 +128009,7 @@ end
return self
end
function MSRSQUEUE:NewTransmission(text,duration,msrs,tstart,interval,subgroups,subtitle,subduration,frequency,modulation,gender,culture,voice,volume,label,coordinate)
+self:T({Text=text,Dur=duration,start=tstart,int=interval,sub=subgroups,subt=subtitle,sudb=subduration,F=frequency,M=modulation,G=gender,C=culture,V=voice,Vol=volume,L=label})
if self.TransmitOnlyWithPlayers then
if self.PlayerSet and self.PlayerSet:CountAlive()==0 then
return self