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