diff --git a/Moose Development/Documentation/Scoring/Scoring Sample.csv b/Moose Development/Documentation/Scoring/Scoring Sample.csv
new file mode 100644
index 000000000..49c85640d
--- /dev/null
+++ b/Moose Development/Documentation/Scoring/Scoring Sample.csv
@@ -0,0 +1,190 @@
+Game;Date;Time;Source Player Name;Target Player Name;Event;Source Coalition;Source Category;Source Type;Source Unit;Target Coalition;Target Category;Target Type;Target Name;Nr;Score
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:19:37;PL8;;DESTROY_SCORE;Red;Plane;Su-25T;Pilot #058;Blue;Vehicle;Hawk cwar;Unit #114;1;5
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;HIT_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier M249;Unit #453;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:14:30;PL1;PL2;DESTROY_PENALTY;Blue;Plane;F-15C;Pilot #069;Blue;Plane;F-15C;Pilot #071;1;-15
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:40:14;PL3;PL4;HIT_SCORE;Blue;Plane;F-15C;Pilot #018;Red;Plane;AJS37;Pilot #112;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:25:22;PL5;;HIT_PENALTY;Red;Helicopter;Ka-50;MEDEVAC RED #13;Red;Vehicle;Infantry AK;Wounded Pilot #1143;1;-10
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:18:34;PL6;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #007;1;3
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:21:35;PL7;PL2;HIT_SCORE;Red;Plane;M-2000C;Pilot #037;Blue;Plane;F-15C;Pilot #071;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:42;PL8;;HIT_SCORE;Red;Plane;Su-25T;Pilot #058;Blue;Vehicle;Strela-1 9P31;Unit #429;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:30:09;PL9;PL10;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;Su-27;Pilot #039;1;5
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:46;PL11;;HIT_SCORE;Red;;;;;Scenery;Infantry AK;Wounded Pilot #1129;1;0
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:11:46;PL6;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;MLRS;Unit #011;1;2
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:53:10;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;MLRS;Unit #510;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:10:54;PL12;;HIT_SCORE;Red;;;;;Scenery;UKRYTIE;280468;1;0
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:09:28;PL13;PL9;HIT_SCORE;Red;Plane;Su-27;Pilot #086;Blue;Plane;F-15C;Pilot #017;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:52:59;PL13;PL14;HIT_SCORE;Red;Plane;Su-27;Pilot #086;Blue;Plane;M-2000C;Pilot #030;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:27;PL12;;DESTROY_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier stinger;Unit #454;1;4
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:53:21;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;MLRS;Unit #510;1;2
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:51:37;PL15;PL9;HIT_SCORE;Red;Plane;Su-27;Pilot #041;Blue;Plane;F-15C;Pilot #017;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:22:05;PL16;PL2;DESTROY_SCORE;Red;Plane;Su-27;Pilot #044;Blue;Plane;F-15C;Pilot #071;1;3
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:28;PL6;PL3;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Plane;F-15C;Pilot #018;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:38:21;PL17;PL18;HIT_SCORE;Blue;Plane;M-2000C;Pilot #029;Red;Plane;Su-25T;Pilot #059;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:14:34;PL7;PL17;DESTROY_SCORE;Red;Plane;M-2000C;Pilot #037;Blue;Plane;M-2000C;Pilot #029;1;2,5
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:22;PL19;PL20;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;A-10C;Pilot #057;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:24:21;PL5;PL21;HIT_PENALTY;Red;Helicopter;Mi-8MT;MEDEVAC RED #10;Red;Helicopter;SA342Mistral;helicargo4;1;-10
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:36;PL6;PL19;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Plane;F-15C;Pilot #024;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:51;PL6;PL19;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Plane;F-15C;Pilot #024;1;6
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:14;PL19;PL20;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;A-10C;Pilot #057;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:13:59;PL2;;HIT_SCORE;Blue;;;;;Scenery;F-15C;Pilot #069;1;0
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:48:55;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;M-1 Abrams;Unit #514;1;3
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:48;PL6;PL3;DESTROY_SCORE;Red;;;;Blue;Plane;F-15C;Pilot #018;1;6
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:18:19;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #007;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:25:19;PL5;;HIT_PENALTY;Red;Helicopter;Ka-50;MEDEVAC RED #13;Red;Vehicle;Infantry AK;Wounded Pilot #1143;1;-10
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:12;PL9;PL20;HIT_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;A-10C;Pilot #057;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:51:35;PL15;PL9;HIT_SCORE;Red;Plane;Su-27;Pilot #041;Blue;Plane;F-15C;Pilot #017;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:19:18;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-113;Unit #006;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:54:22;PL1;PL13;HIT_SCORE;Blue;Plane;F-15C;Pilot #069;Red;Plane;Su-27;Pilot #086;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:37:18;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Osa 9A33 ln;Unit #218;1;5
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:21:14;PL16;PL1;DESTROY_SCORE;Red;Plane;Su-27;Pilot #044;Blue;Plane;F-15C;Pilot #069;1;5
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:55:00;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;M1128 Stryker MGS;Unit #010;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:13;PL9;PL20;HIT_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;A-10C;Pilot #057;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:19:55;PL2;PL18;HIT_SCORE;Blue;Plane;F-15C;Pilot #071;Red;Plane;Su-25T;Pilot #059;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:14:34;PL16;PL17;DESTROY_SCORE;Red;Plane;Su-27;Pilot #044;Blue;Plane;M-2000C;Pilot #029;1;2,5
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:42;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Soldier M4;Unit #470;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:11:46;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;MLRS;Unit #011;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:20;PL19;PL20;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;A-10C;Pilot #057;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:10:01;PL6;PL3;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Plane;F-15C;Pilot #018;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:38:22;PL18;PL17;DESTROY_SCORE;Red;Plane;Su-25T;Pilot #059;Blue;Plane;M-2000C;Pilot #029;1;5
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:25;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #007;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:19:53;PL24;PL18;HIT_SCORE;Blue;Plane;F-15C;Pilot #033;Red;Plane;Su-25T;Pilot #059;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:51:55;PL15;PL9;DESTROY_SCORE;Red;Plane;Su-27;Pilot #041;Blue;Plane;F-15C;Pilot #017;1;5
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:48:11;PL2;PL10;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #071;Red;Plane;Su-27;Pilot #039;1;5
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;DESTROY_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier M249;X33;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:13:13;PL6;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-1 Abrams;Unit #009;1;3
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:17:07;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #007;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:18:29;PL19;PL18;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;Su-25T;Pilot #059;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:22:05;PL16;PL2;HIT_SCORE;Red;Plane;Su-27;Pilot #044;Blue;Plane;F-15C;Pilot #071;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:26;PL19;PL20;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;A-10C;Pilot #057;1;2
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:55:13;PL6;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M1128 Stryker MGS;Unit #010;1;1,5
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:55:54;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;AAV7;Unit #005;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:26;PL6;PL19;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Plane;F-15C;Pilot #024;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:09:27;PL3;PL13;HIT_SCORE;Blue;Plane;F-15C;Pilot #018;Red;Plane;Su-27;Pilot #086;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:57:05;PL15;PL1;DESTROY_SCORE;Red;Plane;Su-27;Pilot #041;Blue;Plane;F-15C;Pilot #069;1;5
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:53:23;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;MLRS;Unit #511;1;2
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:47:44;PL16;PL26;HIT_SCORE;Red;Plane;Su-27;Pilot #044;Blue;Plane;F-15C;Pilot #068;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:40:04;PL19;PL12;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;AJS37;Pilot #110;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:24;PL20;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Soldier M4;X36;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:07:56;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-109;Unit #512;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:24;PL20;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Soldier M249;Unit #310;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;HIT_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier M4;Unit #450;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:45;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Soldier M4;Unit #469;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:54:26;PL1;PL13;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #069;Red;Plane;Su-27;Pilot #086;1;5
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:13:29;PL4;;DESTROY_SCORE;Red;Plane;AJS37;Pilot #112;Blue;Structure;Warehouse;AMMO SENAKI;1;50
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:56:32;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;M-109;Unit #014;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:55:49;PL16;PL2;HIT_SCORE;Red;Plane;Su-27;Pilot #044;Blue;Plane;F-15C;Pilot #071;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:17:00;PL6;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;Vulcan;Unit #004;1;4
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:47:55;PL10;PL19;HIT_SCORE;Red;Plane;Su-27;Pilot #039;Blue;Plane;F-15C;Pilot #024;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:29:20;PL9;PL10;HIT_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;Su-27;Pilot #039;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:45;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Soldier M4;Unit #470;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:54:58;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;AAV7;Unit #005;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:48:15;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Vulcan;Unit #503;1;4
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:22:05;PL7;PL2;DESTROY_SCORE;Red;Plane;M-2000C;Pilot #037;Blue;Plane;F-15C;Pilot #071;1;3
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:19:55;PL24;PL18;HIT_SCORE;Blue;Plane;F-15C;Pilot #033;Red;Plane;Su-25T;Pilot #059;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:45:32;PL7;;DESTROY_SCORE;Red;Plane;M-2000C;Pilot #037;Blue;Structure;Windsock;logistic10;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:22;PL20;;HIT_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Soldier M4;Unit #308;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:47:30;;;CAPTURE KOBULETI;Red;;;;NA;NA;NA;NA;1;50
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:55:13;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;M1128 Stryker MGS;Unit #010;1;1,5
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:20:05;PL17;PL18;DESTROY_SCORE;Blue;;;;Red;Plane;Su-25T;Pilot #059;1;1,67
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:22:42;PL23;PL22;HIT_SCORE;Red;Plane;M-2000C;Pilot #038;Blue;Plane;F-15C;Pilot #070;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:42;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Soldier M4;Unit #469;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:37:08;PL18;;DESTROY_SCORE;Red;Plane;Su-25T;Pilot #059;Blue;Vehicle;Hawk sr;Unit #118;1;5
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:40:12;PL19;PL12;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;AJS37;Pilot #110;1;4
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:50:48;PL20;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Vulcan;Unit #095;1;4
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:45:31;PL7;;HIT_SCORE;Red;Plane;M-2000C;Pilot #037;Blue;Vehicle;outpost;outpost1;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:25:15;PL5;;HIT_PENALTY;Red;Helicopter;Ka-50;MEDEVAC RED #13;Red;Vehicle;Infantry AK;Wounded Pilot #1143;1;-10
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:13:29;PL4;;HIT_SCORE;Red;;;;;Scenery;Warehouse;AMMO SENAKI;1;0
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:20:05;PL2;PL18;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #071;Red;Plane;Su-25T;Pilot #059;1;1,67
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:45:32;PL7;;HIT_SCORE;Red;;;;;Scenery;Windsock;logistic10;1;0
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;HIT_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier M249;X33;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:52:37;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Vulcan;Unit #508;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:47:58;PL2;PL10;HIT_SCORE;Blue;Plane;F-15C;Pilot #071;Red;Plane;Su-27;Pilot #039;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:56:35;PL15;PL1;HIT_SCORE;Red;Plane;Su-27;Pilot #041;Blue;Plane;F-15C;Pilot #069;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:25;PL20;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Soldier M4;Unit #308;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:32:57;PL9;PL25;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;Su-27;Pilot #043;1;5
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:18:23;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #007;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:21:14;PL16;PL1;HIT_SCORE;Red;Plane;Su-27;Pilot #044;Blue;Plane;F-15C;Pilot #069;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:47;PL9;PL6;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;A-10C;Pilot #056;1;2
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:23:16;PL5;PL5;HIT_PENALTY;Red;Helicopter;Mi-8MT;MEDEVAC RED #10;Red;Helicopter;Mi-8MT;MEDEVAC RED #10;1;-10
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:03;PL19;PL6;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;A-10C;Pilot #056;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:24;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #007;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:17;PL11;PL27;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Plane;F-15C;Pilot #025;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:18:22;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #007;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:52:37;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Vulcan;Unit #508;1;4
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:19:30;PL6;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-113;Unit #006;1;2
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:22;PL20;;HIT_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Soldier M249;Unit #310;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:46;PL9;PL11;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;A-10C;Pilot #054;1;4
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:36:57;PL18;;HIT_SCORE;Red;Plane;Su-25T;Pilot #059;Blue;Vehicle;Hawk sr;Unit #118;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:22;PL20;;HIT_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Soldier M249;Unit #311;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:26;PL11;;HIT_SCORE;Red;;;;;Scenery;KAZARMA2;270090725;1;0
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:37:07;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Osa 9A33 ln;Unit #218;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:18:46;;;CAPTURE KUTAISI;Red;;;;NA;NA;NA;NA;1;50
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:33:20;PL9;PL15;HIT_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;Su-27;Pilot #041;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:10:10;PL6;PL3;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Plane;F-15C;Pilot #018;1;6
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:13:23;PL6;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #008;1;3
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:56:08;PL11;PL26;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Plane;F-15C;Pilot #025;1;6
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:14:23;PL7;PL17;HIT_SCORE;Red;Plane;M-2000C;Pilot #037;Blue;Plane;M-2000C;Pilot #029;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:47;PL19;PL6;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;A-10C;Pilot #056;1;2
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:50:37;PL20;;HIT_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Vulcan;Unit #015;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:26;PL9;PL20;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;A-10C;Pilot #057;1;2
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:57:05;PL2;PL1;DESTROY_PENALTY;Blue;Plane;F-15C;Pilot #071;Blue;Plane;F-15C;Pilot #069;1;-15
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:56:44;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;M-109;Unit #014;1;2
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:36;PL9;PL6;HIT_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;A-10C;Pilot #056;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:53:12;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;MLRS;Unit #511;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:47:56;PL20;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;M-109;Unit #013;1;2
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:22:37;PL9;PL6;HIT_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;A-10C;Pilot #056;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:48:04;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;M-113;Unit #505;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:13:23;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-2 Bradley;Unit #008;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:42;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Vulcan;Unit #515;1;4
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:38:22;PL18;;HIT_SCORE;Red;;;;;Scenery;M-2000C;Pilot #029;1;0
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:09:28;PL13;PL9;DESTROY_SCORE;Red;Plane;Su-27;Pilot #086;Blue;Plane;F-15C;Pilot #017;1;5
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:13:29;PL4;;DESTROY_SCORE;Red;Plane;AJS37;Pilot #112;Blue;Structure;Warehouse;AMMO SENAKI;1;50
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:48:49;PL28;;HIT_SCORE;;;;;;Scenery;Ural-4320T;Unit #150;1;0
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:40:22;PL3;PL4;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #018;Red;Plane;AJS37;Pilot #112;1;4
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;DESTROY_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier M4;Unit #450;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:14:24;PL16;PL17;HIT_SCORE;Red;Plane;Su-27;Pilot #044;Blue;Plane;M-2000C;Pilot #029;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:48:04;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Vulcan;Unit #503;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:00:00;;;CAPTURE GUDAUTA;Blue;;;;NA;NA;NA;NA;1;50
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;HIT_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier stinger;Unit #454;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:13:13;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-1 Abrams;Unit #009;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:42;PL8;;DESTROY_SCORE;Red;Plane;Su-25T;Pilot #058;Blue;Vehicle;Strela-1 9P31;Unit #429;1;4
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:56:06;PL11;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;AAV7;Unit #005;1;2
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:48:55;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;M-1 Abrams;Unit #514;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:53:06;PL20;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;MLRS;Unit #012;1;2
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:13:59;PL1;;HIT_SCORE;Blue;;;;;Scenery;F-15C;Pilot #071;1;0
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:25:17;PL5;;HIT_PENALTY;Red;Helicopter;Ka-50;MEDEVAC RED #13;Red;Vehicle;Infantry AK;Wounded Pilot #1143;1;-10
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;HIT_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier M4;Unit #451;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:25:20;PL5;;HIT_PENALTY;Red;Helicopter;Ka-50;MEDEVAC RED #13;Red;Vehicle;Infantry AK;Wounded Pilot #1143;1;-10
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:53:14;PL13;PL14;DESTROY_SCORE;Red;Plane;Su-27;Pilot #086;Blue;Plane;M-2000C;Pilot #030;1;5
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:50:48;PL20;;HIT_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Vulcan;Unit #095;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:45:31;PL7;;DESTROY_SCORE;Red;Plane;M-2000C;Pilot #037;Blue;Vehicle;outpost;outpost1;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;HIT_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;outpost;outpost2;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:19:37;PL8;;HIT_SCORE;Red;Plane;Su-25T;Pilot #058;Blue;Vehicle;Hawk cwar;Unit #114;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:18:36;PL19;PL18;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;Su-25T;Pilot #059;1;4
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:07:56;PL6;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M-109;Unit #512;1;2
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;;;SEALS 4;Blue;;;;NA;NA;NA;NA;1;50
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:19;PL12;;HIT_SCORE;Red;;;;;Scenery;BLK_LIGHT_POLE;269633408;1;0
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:24;PL20;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Soldier M249;Unit #311;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:20:05;PL24;PL18;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #033;Red;Plane;Su-25T;Pilot #059;1;1,67
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:46:42;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;Vulcan;Unit #515;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:09:37;PL3;PL13;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #018;Red;Plane;Su-27;Pilot #086;1;5
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:48:04;PL11;;HIT_SCORE;Red;;;;;Scenery;BLK_LIGHT_POLE;269963637;1;0
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:24;;;SEALS 1;Blue;;;;NA;NA;NA;NA;1;50
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:11:14;PL4;;HIT_SCORE;Red;;;;;Scenery;UKRYTIE;280476;1;0
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:53:06;PL20;;HIT_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;MLRS;Unit #012;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:32:54;PL9;PL25;HIT_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;Su-27;Pilot #043;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:55:01;PL11;;HIT_SCORE;Red;Plane;A-10C;Pilot #054;Blue;Vehicle;M1128 Stryker MGS;Unit #010;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:25;PL12;;DESTROY_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier M4;Unit #451;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:47:56;PL20;;HIT_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;M-109;Unit #013;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:18:25;PL19;PL18;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;Su-25T;Pilot #059;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:41:22;PL20;;HIT_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Soldier M4;X36;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:18:24;PL19;PL18;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;Su-25T;Pilot #059;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:17;PL19;PL20;HIT_SCORE;Blue;Plane;F-15C;Pilot #024;Red;Plane;A-10C;Pilot #057;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:23:03;PL23;PL22;DESTROY_SCORE;Red;Plane;M-2000C;Pilot #038;Blue;Plane;F-15C;Pilot #070;1;6
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:17:00;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;Vulcan;Unit #004;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:45:40;PL6;;HIT_SCORE;Red;Plane;A-10C;Pilot #056;Blue;Vehicle;M1128 Stryker MGS;Unit #010;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:09:18;PL13;;HIT_SCORE;Red;;;;;Scenery;KORPUS_B1;269962308;1;0
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:33:24;PL9;PL15;DESTROY_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;Su-27;Pilot #041;1;5
+TAW_CSARz_PALACE_3.6136;7/05/2017;1:39:27;PL12;;DESTROY_SCORE;Red;Plane;AJS37;Pilot #110;Blue;Vehicle;Soldier M249;Unit #453;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:21:53;PL9;PL11;HIT_SCORE;Blue;Plane;F-15C;Pilot #017;Red;Plane;A-10C;Pilot #054;1;1
+TAW_CSARz_PALACE_3.6136;7/05/2017;12:50:37;PL20;;DESTROY_SCORE;Red;Plane;A-10C;Pilot #057;Blue;Vehicle;Vulcan;Unit #015;1;4
diff --git a/Moose Development/Documentation/Scoring/Scoring Sample.xlsx b/Moose Development/Documentation/Scoring/Scoring Sample.xlsx
new file mode 100644
index 000000000..4aca4870c
Binary files /dev/null and b/Moose Development/Documentation/Scoring/Scoring Sample.xlsx differ
diff --git a/Moose Development/Moose/AI/AI_A2A_Cap.lua b/Moose Development/Moose/AI/AI_A2A_Cap.lua
index 7b4fb69c0..a38ac5b58 100644
--- a/Moose Development/Moose/AI/AI_A2A_Cap.lua
+++ b/Moose Development/Moose/AI/AI_A2A_Cap.lua
@@ -4,7 +4,7 @@
--
-- ===
--
--- AI CAP classes makes AI Controllables execute a Combat Air Patrol.
+-- AI CAP classes makes AI Groups execute a Combat Air Patrol.
--
-- There are the following types of CAP classes defined:
--
@@ -34,7 +34,7 @@
--- # AI_A2A_CAP class, extends @{AI_CAP#AI_PATROL_ZONE}
--
--- The AI_A2A_CAP class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group}
+-- The AI_A2A_CAP class implements the core functions to patrol a @{Zone} by an AI @{Group} or @{Group}
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
--
-- 
@@ -120,20 +120,20 @@ AI_A2A_CAP = {
--- Creates a new AI_A2A_CAP object
-- @param #AI_A2A_CAP self
--- @param Wrapper.Group#GROUP AIGroup
+-- @param Wrapper.Group#GROUP AICap
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
--- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
--- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
--- @param Dcs.DCSTypes#Speed EngageMinSpeed The minimum speed of the @{Controllable} in km/h when engaging a target.
--- @param Dcs.DCSTypes#Speed EngageMaxSpeed The maximum speed of the @{Controllable} in km/h when engaging a target.
+-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h.
+-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h.
+-- @param Dcs.DCSTypes#Speed EngageMinSpeed The minimum speed of the @{Group} in km/h when engaging a target.
+-- @param Dcs.DCSTypes#Speed EngageMaxSpeed The maximum speed of the @{Group} in km/h when engaging a target.
-- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_A2A_CAP
-function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType )
+function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType )
-- Inherits from BASE
- local self = BASE:Inherit( self, AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_A2A_CAP
+ local self = BASE:Inherit( self, AI_A2A_PATROL:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_A2A_CAP
self.Accomplished = false
self.Engaging = false
@@ -146,7 +146,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_A2A_CAP] OnBeforeEngage
-- @param #AI_A2A_CAP self
- -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -155,7 +155,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnAfter Transition Handler for Event Engage.
-- @function [parent=#AI_A2A_CAP] OnAfterEngage
-- @param #AI_A2A_CAP self
- -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -172,7 +172,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_A2A_CAP] OnLeaveEngaging
-- @param #AI_A2A_CAP self
--- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -181,7 +181,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnEnter Transition Handler for State Engaging.
-- @function [parent=#AI_A2A_CAP] OnEnterEngaging
-- @param #AI_A2A_CAP self
--- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -191,7 +191,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnBefore Transition Handler for Event Fired.
-- @function [parent=#AI_A2A_CAP] OnBeforeFired
-- @param #AI_A2A_CAP self
- -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -200,7 +200,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnAfter Transition Handler for Event Fired.
-- @function [parent=#AI_A2A_CAP] OnAfterFired
-- @param #AI_A2A_CAP self
- -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -219,7 +219,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnBefore Transition Handler for Event Destroy.
-- @function [parent=#AI_A2A_CAP] OnBeforeDestroy
-- @param #AI_A2A_CAP self
- -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -228,7 +228,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnAfter Transition Handler for Event Destroy.
-- @function [parent=#AI_A2A_CAP] OnAfterDestroy
-- @param #AI_A2A_CAP self
- -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -248,7 +248,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnBefore Transition Handler for Event Abort.
-- @function [parent=#AI_A2A_CAP] OnBeforeAbort
-- @param #AI_A2A_CAP self
- -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -257,7 +257,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnAfter Transition Handler for Event Abort.
-- @function [parent=#AI_A2A_CAP] OnAfterAbort
-- @param #AI_A2A_CAP self
- -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -276,7 +276,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnBefore Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2A_CAP] OnBeforeAccomplish
-- @param #AI_A2A_CAP self
- -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -285,7 +285,7 @@ function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeiling
--- OnAfter Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2A_CAP] OnAfterAccomplish
-- @param #AI_A2A_CAP self
- -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -304,13 +304,13 @@ end
--- onafter State Transition for Event Patrol.
-- @param #AI_A2A_GCI self
--- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
+-- @param Wrapper.Group#GROUP AICap The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-function AI_A2A_CAP:onafterStart( AIGroup, From, Event, To )
+function AI_A2A_CAP:onafterStart( AICap, From, Event, To )
- AIGroup:HandleEvent( EVENTS.Takeoff, nil, self )
+ AICap:HandleEvent( EVENTS.Takeoff, nil, self )
end
@@ -344,36 +344,36 @@ end
--- onafter State Transition for Event Patrol.
-- @param #AI_A2A_CAP self
--- @param Wrapper.Controllable#CONTROLLABLE AIGroup The AI Group managed by the FSM.
+-- @param Wrapper.Group#GROUP AICap The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-function AI_A2A_CAP:onafterPatrol( AIGroup, From, Event, To )
+function AI_A2A_CAP:onafterPatrol( AICap, From, Event, To )
-- Call the parent Start event handler
- self:GetParent(self).onafterPatrol( self, AIGroup, From, Event, To )
+ self:GetParent(self).onafterPatrol( self, AICap, From, Event, To )
self:HandleEvent( EVENTS.Dead )
end
-- todo: need to fix this global function
---- @param Wrapper.Group#GROUP AIGroup
-function AI_A2A_CAP.AttackRoute( AIGroup, Fsm )
+--- @param Wrapper.Group#GROUP AICap
+function AI_A2A_CAP.AttackRoute( AICap, Fsm )
- AIGroup:F( { "AI_A2A_CAP.AttackRoute:", AIGroup:GetName() } )
+ AICap:F( { "AI_A2A_CAP.AttackRoute:", AICap:GetName() } )
- if AIGroup:IsAlive() then
+ if AICap:IsAlive() then
Fsm:__Engage( 0.5 )
end
end
--- @param #AI_A2A_CAP self
--- @param Wrapper.Controllable#CONTROLLABLE AIGroup The Controllable Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-function AI_A2A_CAP:onbeforeEngage( AIGroup, From, Event, To )
+function AI_A2A_CAP:onbeforeEngage( AICap, From, Event, To )
if self.Accomplished == true then
return false
@@ -381,24 +381,24 @@ function AI_A2A_CAP:onbeforeEngage( AIGroup, From, Event, To )
end
--- @param #AI_A2A_CAP self
--- @param Wrapper.Controllable#CONTROLLABLE AIGroup The AI Group managed by the FSM.
+-- @param Wrapper.Group#GROUP AICap The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-function AI_A2A_CAP:onafterAbort( AIGroup, From, Event, To )
- AIGroup:ClearTasks()
+function AI_A2A_CAP:onafterAbort( AICap, From, Event, To )
+ AICap:ClearTasks()
self:__Route( 0.5 )
end
--- @param #AI_A2A_CAP self
--- @param Wrapper.Controllable#CONTROLLABLE AIGroup The Controllable Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AICap The AICap Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-function AI_A2A_CAP:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
+function AI_A2A_CAP:onafterEngage( AICap, From, Event, To, AttackSetUnit )
- self:F( { AIGroup, From, Event, To, AttackSetUnit} )
+ self:F( { AICap, From, Event, To, AttackSetUnit} )
self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT
@@ -406,12 +406,12 @@ function AI_A2A_CAP:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
if FirstAttackUnit and FirstAttackUnit:IsAlive() then -- If there is no attacker anymore, stop the engagement.
- if AIGroup:IsAlive() then
+ if AICap:IsAlive() then
local EngageRoute = {}
--- Calculate the target route point.
- local CurrentCoord = AIGroup:GetCoordinate()
+ local CurrentCoord = AICap:GetCoordinate()
local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate()
local ToTargetSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
local ToInterceptAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) )
@@ -437,7 +437,7 @@ function AI_A2A_CAP:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
if AttackUnit:IsAlive() and AttackUnit:IsAir() then
- AttackTasks[#AttackTasks+1] = AIGroup:TaskAttackUnit( AttackUnit )
+ AttackTasks[#AttackTasks+1] = AICap:TaskAttackUnit( AttackUnit )
end
end
@@ -445,14 +445,14 @@ function AI_A2A_CAP:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
self:E("No targets found -> Going back to Patrolling")
self:__Abort( 0.5 )
else
- AIGroup:OptionROEOpenFire()
- AIGroup:OptionROTPassiveDefense()
+ AICap:OptionROEOpenFire()
+ AICap:OptionROTEvadeFire()
- AttackTasks[#AttackTasks+1] = AIGroup:TaskFunction( "AI_A2A_CAP.AttackRoute", self )
- EngageRoute[#EngageRoute].task = AIGroup:TaskCombo( AttackTasks )
+ AttackTasks[#AttackTasks+1] = AICap:TaskFunction( "AI_A2A_CAP.AttackRoute", self )
+ EngageRoute[#EngageRoute].task = AICap:TaskCombo( AttackTasks )
end
- AIGroup:Route( EngageRoute, 0.5 )
+ AICap:Route( EngageRoute, 0.5 )
end
else
self:E("No targets found -> Going back to Patrolling")
@@ -461,22 +461,22 @@ function AI_A2A_CAP:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
end
--- @param #AI_A2A_CAP self
--- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-function AI_A2A_CAP:onafterAccomplish( Controllable, From, Event, To )
+function AI_A2A_CAP:onafterAccomplish( AICap, From, Event, To )
self.Accomplished = true
self:SetDetectionOff()
end
--- @param #AI_A2A_CAP self
--- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Core.Event#EVENTDATA EventData
-function AI_A2A_CAP:onafterDestroy( Controllable, From, Event, To, EventData )
+function AI_A2A_CAP:onafterDestroy( AICap, From, Event, To, EventData )
if EventData.IniUnit then
self.AttackUnits[EventData.IniUnit] = nil
@@ -495,12 +495,12 @@ function AI_A2A_CAP:OnEventDead( EventData )
end
end
---- @param Wrapper.Group#GROUP AIGroup
-function AI_A2A_CAP.Resume( AIGroup )
+--- @param Wrapper.Group#GROUP AICap
+function AI_A2A_CAP.Resume( AICap )
- AIGroup:F( { "AI_A2A_CAP.Resume:", AIGroup:GetName() } )
- if AIGroup:IsAlive() then
- local _AI_A2A = AIGroup:GetState( AIGroup, "AI_A2A" ) -- #AI_A2A
+ AICap:F( { "AI_A2A_CAP.Resume:", AICap:GetName() } )
+ if AICap:IsAlive() then
+ local _AI_A2A = AICap:GetState( AICap, "AI_A2A" ) -- #AI_A2A
_AI_A2A:__Reset( 1 )
_AI_A2A:__Route( 5 )
end
diff --git a/Moose Development/Moose/AI/AI_A2A_Gci.lua b/Moose Development/Moose/AI/AI_A2A_Gci.lua
index 7791ea72d..ac868630f 100644
--- a/Moose Development/Moose/AI/AI_A2A_Gci.lua
+++ b/Moose Development/Moose/AI/AI_A2A_Gci.lua
@@ -117,12 +117,12 @@ AI_A2A_GCI = {
--- Creates a new AI_A2A_GCI object
-- @param #AI_A2A_GCI self
--- @param Wrapper.Group#GROUP AIGroup
+-- @param Wrapper.Group#GROUP AIIntercept
-- @return #AI_A2A_GCI
-function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
+function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
-- Inherits from BASE
- local self = BASE:Inherit( self, AI_A2A:New( AIGroup ) ) -- #AI_A2A_GCI
+ local self = BASE:Inherit( self, AI_A2A:New( AIIntercept ) ) -- #AI_A2A_GCI
self.Accomplished = false
self.Engaging = false
@@ -139,7 +139,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_A2A_GCI] OnBeforeEngage
-- @param #AI_A2A_GCI self
- -- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -148,7 +148,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnAfter Transition Handler for Event Engage.
-- @function [parent=#AI_A2A_GCI] OnAfterEngage
-- @param #AI_A2A_GCI self
- -- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -165,7 +165,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_A2A_GCI] OnLeaveEngaging
-- @param #AI_A2A_GCI self
--- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -174,7 +174,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnEnter Transition Handler for State Engaging.
-- @function [parent=#AI_A2A_GCI] OnEnterEngaging
-- @param #AI_A2A_GCI self
--- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -184,7 +184,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnBefore Transition Handler for Event Fired.
-- @function [parent=#AI_A2A_GCI] OnBeforeFired
-- @param #AI_A2A_GCI self
- -- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -193,7 +193,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnAfter Transition Handler for Event Fired.
-- @function [parent=#AI_A2A_GCI] OnAfterFired
-- @param #AI_A2A_GCI self
- -- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -212,7 +212,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnBefore Transition Handler for Event Destroy.
-- @function [parent=#AI_A2A_GCI] OnBeforeDestroy
-- @param #AI_A2A_GCI self
- -- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -221,7 +221,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnAfter Transition Handler for Event Destroy.
-- @function [parent=#AI_A2A_GCI] OnAfterDestroy
-- @param #AI_A2A_GCI self
- -- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -241,7 +241,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnBefore Transition Handler for Event Abort.
-- @function [parent=#AI_A2A_GCI] OnBeforeAbort
-- @param #AI_A2A_GCI self
- -- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -250,7 +250,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnAfter Transition Handler for Event Abort.
-- @function [parent=#AI_A2A_GCI] OnAfterAbort
-- @param #AI_A2A_GCI self
- -- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -269,7 +269,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnBefore Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2A_GCI] OnBeforeAccomplish
-- @param #AI_A2A_GCI self
- -- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -278,7 +278,7 @@ function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
--- OnAfter Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2A_GCI] OnAfterAccomplish
-- @param #AI_A2A_GCI self
- -- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
+ -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -297,13 +297,13 @@ end
--- onafter State Transition for Event Patrol.
-- @param #AI_A2A_GCI self
--- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
+-- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-function AI_A2A_GCI:onafterStart( AIGroup, From, Event, To )
+function AI_A2A_GCI:onafterStart( AIIntercept, From, Event, To )
- AIGroup:HandleEvent( EVENTS.Takeoff, nil, self )
+ AIIntercept:HandleEvent( EVENTS.Takeoff, nil, self )
end
@@ -311,11 +311,11 @@ end
--- onafter State Transition for Event Patrol.
-- @param #AI_A2A_GCI self
--- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
+-- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-function AI_A2A_GCI:onafterEngage( AIGroup, From, Event, To )
+function AI_A2A_GCI:onafterEngage( AIIntercept, From, Event, To )
self:HandleEvent( EVENTS.Dead )
@@ -324,24 +324,24 @@ end
-- todo: need to fix this global function
--- @param Wrapper.Group#GROUP AIControllable
-function AI_A2A_GCI.InterceptRoute( AIGroup, Fsm )
+function AI_A2A_GCI.InterceptRoute( AIIntercept, Fsm )
- AIGroup:F( { "AI_A2A_GCI.InterceptRoute:", AIGroup:GetName() } )
+ AIIntercept:F( { "AI_A2A_GCI.InterceptRoute:", AIIntercept:GetName() } )
- if AIGroup:IsAlive() then
+ if AIIntercept:IsAlive() then
Fsm:__Engage( 0.5 )
- --local Task = AIGroup:TaskOrbitCircle( 4000, 400 )
- --AIGroup:SetTask( Task )
+ --local Task = AIIntercept:TaskOrbitCircle( 4000, 400 )
+ --AIIntercept:SetTask( Task )
end
end
--- @param #AI_A2A_GCI self
--- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-function AI_A2A_GCI:onbeforeEngage( AIGroup, From, Event, To )
+function AI_A2A_GCI:onbeforeEngage( AIIntercept, From, Event, To )
if self.Accomplished == true then
return false
@@ -349,25 +349,25 @@ function AI_A2A_GCI:onbeforeEngage( AIGroup, From, Event, To )
end
--- @param #AI_A2A_GCI self
--- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
+-- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-function AI_A2A_GCI:onafterAbort( AIGroup, From, Event, To )
- AIGroup:ClearTasks()
+function AI_A2A_GCI:onafterAbort( AIIntercept, From, Event, To )
+ AIIntercept:ClearTasks()
self:Return()
self:__RTB( 0.5 )
end
--- @param #AI_A2A_GCI self
--- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AIIntercept The GroupGroup managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-function AI_A2A_GCI:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
+function AI_A2A_GCI:onafterEngage( AIIntercept, From, Event, To, AttackSetUnit )
- self:F( { AIGroup, From, Event, To, AttackSetUnit} )
+ self:F( { AIIntercept, From, Event, To, AttackSetUnit} )
self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT
@@ -375,15 +375,15 @@ function AI_A2A_GCI:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
if FirstAttackUnit and FirstAttackUnit:IsAlive() then
- if AIGroup:IsAlive() then
+ if AIIntercept:IsAlive() then
local EngageRoute = {}
- local CurrentCoord = AIGroup:GetCoordinate()
+ local CurrentCoord = AIIntercept:GetCoordinate()
--- Calculate the target route point.
- local CurrentCoord = AIGroup:GetCoordinate()
+ local CurrentCoord = AIIntercept:GetCoordinate()
local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate()
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
@@ -412,7 +412,7 @@ function AI_A2A_GCI:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
if AttackUnit:IsAlive() and AttackUnit:IsAir() then
self:T( { "Intercepting Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
- AttackTasks[#AttackTasks+1] = AIGroup:TaskAttackUnit( AttackUnit )
+ AttackTasks[#AttackTasks+1] = AIIntercept:TaskAttackUnit( AttackUnit )
end
end
@@ -421,14 +421,14 @@ function AI_A2A_GCI:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
self:Return()
self:__RTB( 0.5 )
else
- AIGroup:OptionROEOpenFire()
- AIGroup:OptionROTPassiveDefense()
+ AIIntercept:OptionROEOpenFire()
+ AIIntercept:OptionROTEvadeFire()
- AttackTasks[#AttackTasks+1] = AIGroup:TaskFunction( "AI_A2A_GCI.InterceptRoute", self )
- EngageRoute[#EngageRoute].task = AIGroup:TaskCombo( AttackTasks )
+ AttackTasks[#AttackTasks+1] = AIIntercept:TaskFunction( "AI_A2A_GCI.InterceptRoute", self )
+ EngageRoute[#EngageRoute].task = AIIntercept:TaskCombo( AttackTasks )
end
- AIGroup:Route( EngageRoute, 0.5 )
+ AIIntercept:Route( EngageRoute, 0.5 )
end
else
@@ -439,22 +439,22 @@ function AI_A2A_GCI:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
end
--- @param #AI_A2A_GCI self
--- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-function AI_A2A_GCI:onafterAccomplish( AIGroup, From, Event, To )
+function AI_A2A_GCI:onafterAccomplish( AIIntercept, From, Event, To )
self.Accomplished = true
self:SetDetectionOff()
end
--- @param #AI_A2A_GCI self
--- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Core.Event#EVENTDATA EventData
-function AI_A2A_GCI:onafterDestroy( AIGroup, From, Event, To, EventData )
+function AI_A2A_GCI:onafterDestroy( AIIntercept, From, Event, To, EventData )
if EventData.IniUnit then
self.AttackUnits[EventData.IniUnit] = nil
diff --git a/Moose Development/Moose/AI/AI_A2A_Patrol.lua b/Moose Development/Moose/AI/AI_A2A_Patrol.lua
index fa9888584..5ca779b0b 100644
--- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua
+++ b/Moose Development/Moose/AI/AI_A2A_Patrol.lua
@@ -4,7 +4,7 @@
--
-- ===
--
--- AI PATROL classes makes AI Controllables execute an Patrol.
+-- AI PATROL classes makes AI Groups execute an Patrol.
--
-- There are the following types of PATROL classes defined:
--
@@ -44,7 +44,7 @@
--- # AI_A2A_PATROL class, extends @{Fsm#FSM_CONTROLLABLE}
--
--- The AI_A2A_PATROL class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group}.
+-- The AI_A2A_PATROL class implements the core functions to patrol a @{Zone} by an AI @{Group} or @{Group}.
--
-- 
--
@@ -139,7 +139,7 @@
--
-- ## 7. Manage "damage" behaviour of the AI in the AI_A2A_PATROL
--
--- When the AI is damaged, it is required that a new AIControllable is started. However, damage cannon be foreseen early on.
+-- When the AI is damaged, it is required that a new Patrol is started. However, damage cannon be foreseen early on.
-- Therefore, when the damage treshold is reached, the AI will return immediately to the home base (RTB).
-- Use the method @{#AI_A2A_PATROL.ManageDamage}() to have this proces in place.
--
@@ -152,23 +152,23 @@ AI_A2A_PATROL = {
--- Creates a new AI_A2A_PATROL object
-- @param #AI_A2A_PATROL self
--- @param Wrapper.Group#GROUP AIGroup
+-- @param Wrapper.Group#GROUP AIPatrol
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
--- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
--- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
+-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h.
+-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h.
-- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_A2A_PATROL self
-- @usage
--- -- Define a new AI_A2A_PATROL Object. This PatrolArea will patrol an AIControllable within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h.
+-- -- Define a new AI_A2A_PATROL Object. This PatrolArea will patrol a Group within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h.
-- PatrolZone = ZONE:New( 'PatrolZone' )
-- PatrolSpawn = SPAWN:New( 'Patrol Group' )
-- PatrolArea = AI_A2A_PATROL:New( PatrolZone, 3000, 6000, 600, 900 )
-function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
+function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
-- Inherits from BASE
- local self = BASE:Inherit( self, AI_A2A:New( AIGroup ) ) -- #AI_A2A_PATROL
+ local self = BASE:Inherit( self, AI_A2A:New( AIPatrol ) ) -- #AI_A2A_PATROL
self.PatrolZone = PatrolZone
self.PatrolFloorAltitude = PatrolFloorAltitude
@@ -184,7 +184,7 @@ function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeil
--- OnBefore Transition Handler for Event Patrol.
-- @function [parent=#AI_A2A_PATROL] OnBeforePatrol
-- @param #AI_A2A_PATROL self
--- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -193,7 +193,7 @@ function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeil
--- OnAfter Transition Handler for Event Patrol.
-- @function [parent=#AI_A2A_PATROL] OnAfterPatrol
-- @param #AI_A2A_PATROL self
--- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -210,7 +210,7 @@ function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeil
--- OnLeave Transition Handler for State Patrolling.
-- @function [parent=#AI_A2A_PATROL] OnLeavePatrolling
-- @param #AI_A2A_PATROL self
--- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -219,7 +219,7 @@ function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeil
--- OnEnter Transition Handler for State Patrolling.
-- @function [parent=#AI_A2A_PATROL] OnEnterPatrolling
-- @param #AI_A2A_PATROL self
--- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -229,7 +229,7 @@ function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeil
--- OnBefore Transition Handler for Event Route.
-- @function [parent=#AI_A2A_PATROL] OnBeforeRoute
-- @param #AI_A2A_PATROL self
--- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -238,7 +238,7 @@ function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeil
--- OnAfter Transition Handler for Event Route.
-- @function [parent=#AI_A2A_PATROL] OnAfterRoute
-- @param #AI_A2A_PATROL self
--- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -264,8 +264,8 @@ end
--- Sets (modifies) the minimum and maximum speed of the patrol.
-- @param #AI_A2A_PATROL self
--- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
--- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
+-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h.
+-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h.
-- @return #AI_A2A_PATROL self
function AI_A2A_PATROL:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
@@ -292,18 +292,18 @@ end
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
-- @param #AI_A2A_PATROL self
-- @return #AI_A2A_PATROL self
--- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
+-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-function AI_A2A_PATROL:onafterPatrol( Controllable, From, Event, To )
+function AI_A2A_PATROL:onafterPatrol( AIPatrol, From, Event, To )
self:F2()
self:ClearTargetDistance()
self:__Route( 1 )
- self.Controllable:OnReSpawn(
+ self.AIPatrol:OnReSpawn(
function( PatrolGroup )
self:E( "ReSpawn" )
self:__Reset( 1 )
@@ -314,14 +314,14 @@ end
---- @param Wrapper.Group#GROUP AIGroup
--- This statis method is called from the route path within the last task at the last waaypoint of the Controllable.
--- Note that this method is required, as triggers the next route when patrolling for the Controllable.
-function AI_A2A_PATROL.PatrolRoute( AIGroup, Fsm )
+--- @param Wrapper.Group#GROUP AIPatrol
+-- This statis method is called from the route path within the last task at the last waaypoint of the AIPatrol.
+-- Note that this method is required, as triggers the next route when patrolling for the AIPatrol.
+function AI_A2A_PATROL.PatrolRoute( AIPatrol, Fsm )
- AIGroup:F( { "AI_A2A_PATROL.PatrolRoute:", AIGroup:GetName() } )
+ AIPatrol:F( { "AI_A2A_PATROL.PatrolRoute:", AIPatrol:GetName() } )
- if AIGroup:IsAlive() then
+ if AIPatrol:IsAlive() then
Fsm:Route()
end
@@ -330,11 +330,11 @@ end
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
-- @param #AI_A2A_PATROL self
--- @param Wrapper.Group#GROUP AIGroup The AIGroup managed by the FSM.
+-- @param Wrapper.Group#GROUP AIPatrol The Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-function AI_A2A_PATROL:onafterRoute( AIGroup, From, Event, To )
+function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To )
self:F2()
@@ -344,13 +344,13 @@ function AI_A2A_PATROL:onafterRoute( AIGroup, From, Event, To )
end
- if AIGroup:IsAlive() then
+ if AIPatrol:IsAlive() then
local PatrolRoute = {}
--- Calculate the target route point.
- local CurrentCoord = AIGroup:GetCoordinate()
+ local CurrentCoord = AIPatrol:GetCoordinate()
local ToTargetCoord = self.PatrolZone:GetRandomPointVec2()
ToTargetCoord:SetAlt( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) )
@@ -371,23 +371,23 @@ function AI_A2A_PATROL:onafterRoute( AIGroup, From, Event, To )
PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint
local Tasks = {}
- Tasks[#Tasks+1] = AIGroup:TaskFunction( "AI_A2A_PATROL.PatrolRoute", self )
- PatrolRoute[#PatrolRoute].task = AIGroup:TaskCombo( Tasks )
+ Tasks[#Tasks+1] = AIPatrol:TaskFunction( "AI_A2A_PATROL.PatrolRoute", self )
+ PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks )
- AIGroup:OptionROEReturnFire()
- AIGroup:OptionROTPassiveDefense()
+ AIPatrol:OptionROEReturnFire()
+ AIPatrol:OptionROTEvadeFire()
- AIGroup:Route( PatrolRoute, 0.5 )
+ AIPatrol:Route( PatrolRoute, 0.5 )
end
end
---- @param Wrapper.Group#GROUP AIGroup
-function AI_A2A_PATROL.Resume( AIGroup )
+--- @param Wrapper.Group#GROUP AIPatrol
+function AI_A2A_PATROL.Resume( AIPatrol )
- AIGroup:F( { "AI_A2A_PATROL.Resume:", AIGroup:GetName() } )
- if AIGroup:IsAlive() then
- local _AI_A2A = AIGroup:GetState( AIGroup, "AI_A2A" ) -- #AI_A2A
+ AIPatrol:F( { "AI_A2A_PATROL.Resume:", AIPatrol:GetName() } )
+ if AIPatrol:IsAlive() then
+ local _AI_A2A = AIPatrol:GetState( AIPatrol, "AI_A2A" ) -- #AI_A2A
_AI_A2A:__Reset( 1 )
_AI_A2A:__Route( 5 )
end
diff --git a/Moose Development/Moose/AI/AI_CAP.lua b/Moose Development/Moose/AI/AI_CAP.lua
index e15a0dd7a..58b8e3191 100644
--- a/Moose Development/Moose/AI/AI_CAP.lua
+++ b/Moose Development/Moose/AI/AI_CAP.lua
@@ -479,7 +479,7 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
Controllable:OptionROEOpenFire()
- Controllable:OptionROTPassiveDefense()
+ Controllable:OptionROTEvadeFire()
local AttackTasks = {}
diff --git a/Moose Development/Moose/AI/AI_Formation.lua b/Moose Development/Moose/AI/AI_Formation.lua
index d11687396..cc2dafad9 100644
--- a/Moose Development/Moose/AI/AI_Formation.lua
+++ b/Moose Development/Moose/AI/AI_Formation.lua
@@ -958,7 +958,7 @@ function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1
-- @param Wrapper.Unit#UNIT ClientUnit
function( FollowGroup, Formation, ClientUnit, CT1, CV1, CT2, CV2 )
- FollowGroup:OptionROTPassiveDefense()
+ FollowGroup:OptionROTEvadeFire()
FollowGroup:OptionROEReturnFire()
local GroupUnit = FollowGroup:GetUnit( 1 )
diff --git a/Moose Development/Moose/Actions/Act_Account.lua b/Moose Development/Moose/Actions/Act_Account.lua
index 0cc641623..c8412b1d9 100644
--- a/Moose Development/Moose/Actions/Act_Account.lua
+++ b/Moose Development/Moose/Actions/Act_Account.lua
@@ -285,26 +285,5 @@ do -- ACT_ACCOUNT_DEADS
end
end
- --- @param #ACT_ACCOUNT_DEADS self
- -- @param Event#EVENTDATA EventData
- function ACT_ACCOUNT_DEADS:onfuncEventDead( EventData )
- self:T( { "EventDead", EventData } )
-
- if EventData.IniDCSUnit then
- self:Event( EventData )
- end
- end
-
- --- DCS Events
-
- --- @param #ACT_ACCOUNT_DEADS self
- -- @param Event#EVENTDATA EventData
- function ACT_ACCOUNT_DEADS:onfuncEventCrash( EventData )
- self:T( { "EventDead", EventData } )
-
- if EventData.IniDCSUnit then
- self:Event( EventData )
- end
- end
end -- ACT_ACCOUNT DEADS
diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua
index c36ea3405..4519a61f3 100644
--- a/Moose Development/Moose/Core/Base.lua
+++ b/Moose Development/Moose/Core/Base.lua
@@ -269,6 +269,23 @@ function BASE:Inherit( Child, Parent )
return Child
end
+
+local function getParent( Child )
+ local Parent = nil
+
+ if Child.ClassName == 'BASE' then
+ Parent = nil
+ else
+ if rawget( Child, "__" ) then
+ Parent = getmetatable( Child.__ ).__index
+ else
+ Parent = getmetatable( Child ).__index
+ end
+ end
+ return Parent
+end
+
+
--- This is the worker method to retrieve the Parent class.
-- Note that the Parent class must be passed to call the parent class method.
--
@@ -278,17 +295,31 @@ end
-- @param #BASE self
-- @param #BASE Child is the Child class from which the Parent class needs to be retrieved.
-- @return #BASE
-function BASE:GetParent( Child )
+function BASE:GetParent( Child, FromClass )
+
+
local Parent
-- BASE class has no parent
if Child.ClassName == 'BASE' then
Parent = nil
- elseif rawget( Child, "__" ) then
- Parent = getmetatable( Child.__ ).__index
- else
- Parent = getmetatable( Child ).__index
- end
- return Parent
+ else
+
+ self:E({FromClass = FromClass})
+ self:E({Child = Child.ClassName})
+ if FromClass then
+ while( Child.ClassName ~= "BASE" and Child.ClassName ~= FromClass.ClassName ) do
+ Child = getParent( Child )
+ self:E({Child.ClassName})
+ end
+ end
+ if Child.ClassName == 'BASE' then
+ Parent = nil
+ else
+ Parent = getParent( Child )
+ end
+ end
+ self:E({Parent.ClassName})
+ return Parent
end
--- This is the worker method to check if an object is an (sub)instance of a class.
@@ -334,7 +365,7 @@ function BASE:IsInstanceOf( ClassName )
return true
end
- local Parent = self:GetParent(self)
+ local Parent = getParent(self)
while Parent do
@@ -342,7 +373,7 @@ function BASE:IsInstanceOf( ClassName )
return true
end
- Parent = Parent:GetParent(Parent)
+ Parent = getParent( Parent )
end
diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua
index 81c8f13c5..218384985 100644
--- a/Moose Development/Moose/Core/Database.lua
+++ b/Moose Development/Moose/Core/Database.lua
@@ -70,6 +70,8 @@ DATABASE = {
NavPoints = {},
PLAYERSETTINGS = {},
ZONENAMES = {},
+ HITS = {},
+ DESTROYS = {},
}
local _DATABASECoalition =
@@ -104,6 +106,7 @@ function DATABASE:New()
self:HandleEvent( EVENTS.Birth, self._EventOnBirth )
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
+ self:HandleEvent( EVENTS.Hit, self.AccountHits )
self:HandleEvent( EVENTS.NewCargo )
self:HandleEvent( EVENTS.DeleteCargo )
@@ -722,6 +725,8 @@ function DATABASE:_EventOnDeadOrCrash( Event )
end
end
end
+
+ self:AccountDestroys( Event )
end
@@ -1053,6 +1058,99 @@ function DATABASE:_RegisterTemplates()
return self
end
+ --- Account the Hits of the Players.
+ -- @param #DATABASE self
+ -- @param Core.Event#EVENTDATA Event
+ function DATABASE:AccountHits( Event )
+ self:F( { Event } )
+
+ if Event.IniPlayerName ~= nil then -- It is a player that is hitting something
+ self:T( "Hitting Something" )
+
+ -- What is he hitting?
+ if Event.TgtCategory then
+
+ -- A target got hit
+ self.HITS[Event.TgtUnitName] = self.HITS[Event.TgtUnitName] or {}
+ local Hit = self.HITS[Event.TgtUnitName]
+
+ Hit.Players = Hit.Players or {}
+ Hit.Players[Event.IniPlayerName] = true
+ end
+ end
+
+ -- It is a weapon initiated by a player, that is hitting something
+ -- This seems to occur only with scenery and static objects.
+ if Event.WeaponPlayerName ~= nil then
+ self:T( "Hitting Scenery" )
+
+ -- What is he hitting?
+ if Event.TgtCategory then
+
+ if Event.IniCoalition then -- A coalition object was hit, probably a static.
+ -- A target got hit
+ self.HITS[Event.TgtUnitName] = self.HITS[Event.TgtUnitName] or {}
+ local Hit = self.HITS[Event.TgtUnitName]
+
+ Hit.Players = Hit.Players or {}
+ Hit.Players[Event.WeaponPlayerName] = true
+ else -- A scenery object was hit.
+ end
+ end
+ end
+ end
+
+ --- Account the destroys.
+ -- @param #DATABASE self
+ -- @param Core.Event#EVENTDATA Event
+ function DATABASE:AccountDestroys( Event )
+ self:F( { Event } )
+
+ local TargetUnit = nil
+ local TargetGroup = nil
+ local TargetUnitName = ""
+ local TargetGroupName = ""
+ local TargetPlayerName = ""
+ local TargetCoalition = nil
+ local TargetCategory = nil
+ local TargetType = nil
+ local TargetUnitCoalition = nil
+ local TargetUnitCategory = nil
+ local TargetUnitType = nil
+
+ if Event.IniDCSUnit then
+
+ TargetUnit = Event.IniUnit
+ TargetUnitName = Event.IniDCSUnitName
+ TargetGroup = Event.IniDCSGroup
+ TargetGroupName = Event.IniDCSGroupName
+ TargetPlayerName = Event.IniPlayerName
+
+ TargetCoalition = Event.IniCoalition
+ --TargetCategory = TargetUnit:getCategory()
+ --TargetCategory = TargetUnit:getDesc().category -- Workaround
+ TargetCategory = Event.IniCategory
+ TargetType = Event.IniTypeName
+
+ TargetUnitType = TargetType
+
+ self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } )
+ end
+
+ self:T( "Something got destroyed" )
+
+ local Destroyed = false
+
+ -- What is the player destroying?
+ if self.HITS[Event.IniUnitName] then -- Was there a hit for this unit for this player before registered???
+
+
+ self.DESTROYS[Event.IniUnitName] = self.DESTROYS[Event.IniUnitName] or {}
+
+ self.DESTROYS[Event.IniUnitName] = true
+
+ end
+ end
diff --git a/Moose Development/Moose/Core/Goal.lua b/Moose Development/Moose/Core/Goal.lua
new file mode 100644
index 000000000..6b2a98b3c
--- /dev/null
+++ b/Moose Development/Moose/Core/Goal.lua
@@ -0,0 +1,146 @@
+--- **Core (WIP)** -- Base class to allow the modeling of processes to achieve Goals.
+--
+-- ====
+--
+-- GOAL models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized.
+--
+-- ====
+--
+-- ### Author: **Sven Van de Velde (FlightControl)**
+--
+-- ====
+--
+-- @module Goal
+
+do -- Goal
+
+ --- @type GOAL
+ -- @extends Core.Fsm#FSM
+
+
+ --- # GOAL class, extends @{Fsm#FSM}
+ --
+ -- GOAL models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized.
+ --
+ -- ## 1. GOAL constructor
+ --
+ -- * @{#GOAL.New}(): Creates a new GOAL object.
+ --
+ -- ## 2. GOAL is a finite state machine (FSM).
+ --
+ -- ### 2.1 GOAL States
+ --
+ -- * **Pending**: The goal object is in progress.
+ -- * **Achieved**: The goal objective is Achieved.
+ --
+ -- ### 2.2 GOAL Events
+ --
+ -- * **Achieved**: Set the goal objective to Achieved.
+ --
+ -- @field #GOAL
+ GOAL = {
+ ClassName = "GOAL",
+ }
+
+ --- @field #table GOAL.Players
+ GOAL.Players = {}
+
+ --- @field #number GOAL.TotalContributions
+ GOAL.TotalContributions = 0
+
+ --- GOAL Constructor.
+ -- @param #GOAL self
+ -- @return #GOAL
+ function GOAL:New()
+
+ local self = BASE:Inherit( self, FSM:New() ) -- #GOAL
+ self:F( {} )
+
+ --- Achieved State for GOAL
+ -- @field GOAL.Achieved
+
+ --- Achieved State Handler OnLeave for GOAL
+ -- @function [parent=#GOAL] OnLeaveAchieved
+ -- @param #GOAL self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+ -- @return #boolean
+
+ --- Achieved State Handler OnEnter for GOAL
+ -- @function [parent=#GOAL] OnEnterAchieved
+ -- @param #GOAL self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+
+
+ self:SetStartState( "Pending" )
+ self:AddTransition( "*", "Achieved", "Achieved" )
+
+ --- Achieved Handler OnBefore for GOAL
+ -- @function [parent=#GOAL] OnBeforeAchieved
+ -- @param #GOAL self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+ -- @return #boolean
+
+ --- Achieved Handler OnAfter for GOAL
+ -- @function [parent=#GOAL] OnAfterAchieved
+ -- @param #GOAL self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+
+ --- Achieved Trigger for GOAL
+ -- @function [parent=#GOAL] Achieved
+ -- @param #GOAL self
+
+ --- Achieved Asynchronous Trigger for GOAL
+ -- @function [parent=#GOAL] __Achieved
+ -- @param #GOAL self
+ -- @param #number Delay
+
+ self:SetEventPriority( 5 )
+
+ return self
+ end
+
+
+ --- @param #GOAL self
+ -- @param #string PlayerName
+ function GOAL:AddPlayerContribution( PlayerName )
+ self.Players[PlayerName] = self.Players[PlayerName] or 0
+ self.Players[PlayerName] = self.Players[PlayerName] + 1
+ self.TotalContributions = self.TotalContributions + 1
+ end
+
+
+ --- @param #GOAL self
+ -- @param #number Player contribution.
+ function GOAL:GetPlayerContribution( PlayerName )
+ return self.Players[PlayerName] or 0
+ end
+
+
+ --- @param #GOAL self
+ function GOAL:GetPlayerContributions()
+ return self.Players or {}
+ end
+
+
+ --- @param #GOAL self
+ function GOAL:GetTotalContributions()
+ return self.TotalContributions or 0
+ end
+
+
+
+ --- @param #GOAL self
+ -- @return #boolean true if the goal is Achieved
+ function GOAL:IsAchieved()
+ return self:Is( "Achieved" )
+ end
+
+end
\ No newline at end of file
diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua
index 6b0ac9adb..1551b5821 100644
--- a/Moose Development/Moose/Core/Message.lua
+++ b/Moose Development/Moose/Core/Message.lua
@@ -167,7 +167,7 @@ function MESSAGE:ToClient( Client, Settings )
if self.MessageType then
local Settings = Settings or ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
- self.MessageCategory = self.MessageType .. ": "
+ self.MessageCategory = "" -- self.MessageType .. ": "
end
if self.MessageDuration ~= 0 then
@@ -192,7 +192,7 @@ function MESSAGE:ToGroup( Group, Settings )
if self.MessageType then
local Settings = Settings or ( Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
- self.MessageCategory = self.MessageType .. ": "
+ self.MessageCategory = "" -- self.MessageType .. ": "
end
if self.MessageDuration ~= 0 then
@@ -259,7 +259,7 @@ function MESSAGE:ToCoalition( CoalitionSide, Settings )
if self.MessageType then
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
- self.MessageCategory = self.MessageType .. ": "
+ self.MessageCategory = "" -- self.MessageType .. ": "
end
if CoalitionSide then
@@ -303,7 +303,7 @@ function MESSAGE:ToAll()
if self.MessageType then
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
- self.MessageCategory = self.MessageType .. ": "
+ self.MessageCategory = "" -- self.MessageType .. ": "
end
if self.MessageDuration ~= 0 then
diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua
index eabb09c87..db575a2ab 100644
--- a/Moose Development/Moose/Core/Point.lua
+++ b/Moose Development/Moose/Core/Point.lua
@@ -1081,7 +1081,7 @@ do -- COORDINATE
local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS
- local ModeA2A = true
+ local ModeA2A = false
if Task then
if Task:IsInstanceOf( TASK_A2A ) then
@@ -1092,6 +1092,8 @@ do -- COORDINATE
else
if Task:IsInstanceOf( TASK_CARGO ) then
ModeA2A = false
+ else
+ ModeA2A = false
end
end
end
diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua
index c7dda4192..8fefad5d5 100644
--- a/Moose Development/Moose/Core/Set.lua
+++ b/Moose Development/Moose/Core/Set.lua
@@ -1271,872 +1271,1616 @@ function SET_GROUP:IsIncludeObject( MooseGroup )
return MooseGroupInclude
end
---- @type SET_UNIT
--- @extends Core.Set#SET_BASE
---- # 3) SET_UNIT class, extends @{Set#SET_BASE}
---
--- Mission designers can use the SET_UNIT class to build sets of units belonging to certain:
---
--- * Coalitions
--- * Categories
--- * Countries
--- * Unit types
--- * Starting with certain prefix strings.
---
--- ## 3.1) SET_UNIT constructor
---
--- Create a new SET_UNIT object with the @{#SET_UNIT.New} method:
---
--- * @{#SET_UNIT.New}: Creates a new SET_UNIT object.
---
--- ## 3.2) Add or Remove UNIT(s) from SET_UNIT
---
--- UNITs can be added and removed using the @{Set#SET_UNIT.AddUnitsByName} and @{Set#SET_UNIT.RemoveUnitsByName} respectively.
--- These methods take a single UNIT name or an array of UNIT names to be added or removed from SET_UNIT.
---
--- ## 3.3) SET_UNIT filter criteria
---
--- You can set filter criteria to define the set of units within the SET_UNIT.
--- Filter criteria are defined by:
---
--- * @{#SET_UNIT.FilterCoalitions}: Builds the SET_UNIT with the units belonging to the coalition(s).
--- * @{#SET_UNIT.FilterCategories}: Builds the SET_UNIT with the units belonging to the category(ies).
--- * @{#SET_UNIT.FilterTypes}: Builds the SET_UNIT with the units belonging to the unit type(s).
--- * @{#SET_UNIT.FilterCountries}: Builds the SET_UNIT with the units belonging to the country(ies).
--- * @{#SET_UNIT.FilterPrefixes}: Builds the SET_UNIT with the units starting with the same prefix string(s).
---
--- Once the filter criteria have been set for the SET_UNIT, you can start filtering using:
---
--- * @{#SET_UNIT.FilterStart}: Starts the filtering of the units within the SET_UNIT.
---
--- Planned filter criteria within development are (so these are not yet available):
---
--- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Zone#ZONE}.
---
--- ## 3.4) SET_UNIT iterators
---
--- Once the filters have been defined and the SET_UNIT has been built, you can iterate the SET_UNIT with the available iterator methods.
--- The iterator methods will walk the SET_UNIT set, and call for each element within the set a function that you provide.
--- The following iterator methods are currently available within the SET_UNIT:
---
--- * @{#SET_UNIT.ForEachUnit}: Calls a function for each alive unit it finds within the SET_UNIT.
--- * @{#SET_GROUP.ForEachGroupCompletelyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function.
--- * @{#SET_GROUP.ForEachGroupNotInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function.
---
--- Planned iterators methods in development are (so these are not yet available):
---
--- * @{#SET_UNIT.ForEachUnitInUnit}: Calls a function for each unit contained within the SET_UNIT.
--- * @{#SET_UNIT.ForEachUnitCompletelyInZone}: Iterate and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function.
--- * @{#SET_UNIT.ForEachUnitNotInZone}: Iterate and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function.
---
--- ## 3.5 ) SET_UNIT atomic methods
---
--- Various methods exist for a SET_UNIT to perform actions or calculations and retrieve results from the SET_UNIT:
---
--- * @{#SET_UNIT.GetTypeNames}(): Retrieve the type names of the @{Unit}s in the SET, delimited by a comma.
---
--- ===
--- @field #SET_UNIT SET_UNIT
-SET_UNIT = {
- ClassName = "SET_UNIT",
- Units = {},
- Filter = {
- Coalitions = nil,
- Categories = nil,
- Types = nil,
- Countries = nil,
- UnitPrefixes = nil,
- },
- FilterMeta = {
- Coalitions = {
- red = coalition.side.RED,
- blue = coalition.side.BLUE,
- neutral = coalition.side.NEUTRAL,
- },
- Categories = {
- plane = Unit.Category.AIRPLANE,
- helicopter = Unit.Category.HELICOPTER,
- ground = Unit.Category.GROUND_UNIT,
- ship = Unit.Category.SHIP,
- structure = Unit.Category.STRUCTURE,
- },
- },
-}
+do -- SET_UNIT
-
---- Get the first unit from the set.
--- @function [parent=#SET_UNIT] GetFirst
--- @param #SET_UNIT self
--- @return Wrapper.Unit#UNIT The UNIT object.
-
---- Creates a new SET_UNIT object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names.
--- @param #SET_UNIT self
--- @return #SET_UNIT
--- @usage
--- -- Define a new SET_UNIT Object. This DBObject will contain a reference to all alive Units.
--- DBObject = SET_UNIT:New()
-function SET_UNIT:New()
-
- -- Inherits from BASE
- local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.UNITS ) ) -- Core.Set#SET_UNIT
-
- return self
-end
-
---- Add UNIT(s) to SET_UNIT.
--- @param #SET_UNIT self
--- @param #string AddUnit A single UNIT.
--- @return #SET_UNIT self
-function SET_UNIT:AddUnit( AddUnit )
- self:F2( AddUnit:GetName() )
-
- self:Add( AddUnit:GetName(), AddUnit )
-
- return self
-end
-
-
---- Add UNIT(s) to SET_UNIT.
--- @param #SET_UNIT self
--- @param #string AddUnitNames A single name or an array of UNIT names.
--- @return #SET_UNIT self
-function SET_UNIT:AddUnitsByName( AddUnitNames )
-
- local AddUnitNamesArray = ( type( AddUnitNames ) == "table" ) and AddUnitNames or { AddUnitNames }
+ --- @type SET_UNIT
+ -- @extends Core.Set#SET_BASE
- self:T( AddUnitNamesArray )
- for AddUnitID, AddUnitName in pairs( AddUnitNamesArray ) do
- self:Add( AddUnitName, UNIT:FindByName( AddUnitName ) )
- end
-
- return self
-end
-
---- Remove UNIT(s) from SET_UNIT.
--- @param Core.Set#SET_UNIT self
--- @param Wrapper.Unit#UNIT RemoveUnitNames A single name or an array of UNIT names.
--- @return self
-function SET_UNIT:RemoveUnitsByName( RemoveUnitNames )
-
- local RemoveUnitNamesArray = ( type( RemoveUnitNames ) == "table" ) and RemoveUnitNames or { RemoveUnitNames }
+ --- # 3) SET_UNIT class, extends @{Set#SET_BASE}
+ --
+ -- Mission designers can use the SET_UNIT class to build sets of units belonging to certain:
+ --
+ -- * Coalitions
+ -- * Categories
+ -- * Countries
+ -- * Unit types
+ -- * Starting with certain prefix strings.
+ --
+ -- ## 3.1) SET_UNIT constructor
+ --
+ -- Create a new SET_UNIT object with the @{#SET_UNIT.New} method:
+ --
+ -- * @{#SET_UNIT.New}: Creates a new SET_UNIT object.
+ --
+ -- ## 3.2) Add or Remove UNIT(s) from SET_UNIT
+ --
+ -- UNITs can be added and removed using the @{Set#SET_UNIT.AddUnitsByName} and @{Set#SET_UNIT.RemoveUnitsByName} respectively.
+ -- These methods take a single UNIT name or an array of UNIT names to be added or removed from SET_UNIT.
+ --
+ -- ## 3.3) SET_UNIT filter criteria
+ --
+ -- You can set filter criteria to define the set of units within the SET_UNIT.
+ -- Filter criteria are defined by:
+ --
+ -- * @{#SET_UNIT.FilterCoalitions}: Builds the SET_UNIT with the units belonging to the coalition(s).
+ -- * @{#SET_UNIT.FilterCategories}: Builds the SET_UNIT with the units belonging to the category(ies).
+ -- * @{#SET_UNIT.FilterTypes}: Builds the SET_UNIT with the units belonging to the unit type(s).
+ -- * @{#SET_UNIT.FilterCountries}: Builds the SET_UNIT with the units belonging to the country(ies).
+ -- * @{#SET_UNIT.FilterPrefixes}: Builds the SET_UNIT with the units starting with the same prefix string(s).
+ --
+ -- Once the filter criteria have been set for the SET_UNIT, you can start filtering using:
+ --
+ -- * @{#SET_UNIT.FilterStart}: Starts the filtering of the units within the SET_UNIT.
+ --
+ -- Planned filter criteria within development are (so these are not yet available):
+ --
+ -- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Zone#ZONE}.
+ --
+ -- ## 3.4) SET_UNIT iterators
+ --
+ -- Once the filters have been defined and the SET_UNIT has been built, you can iterate the SET_UNIT with the available iterator methods.
+ -- The iterator methods will walk the SET_UNIT set, and call for each element within the set a function that you provide.
+ -- The following iterator methods are currently available within the SET_UNIT:
+ --
+ -- * @{#SET_UNIT.ForEachUnit}: Calls a function for each alive unit it finds within the SET_UNIT.
+ -- * @{#SET_GROUP.ForEachGroupCompletelyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function.
+ -- * @{#SET_GROUP.ForEachGroupNotInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function.
+ --
+ -- Planned iterators methods in development are (so these are not yet available):
+ --
+ -- * @{#SET_UNIT.ForEachUnitInUnit}: Calls a function for each unit contained within the SET_UNIT.
+ -- * @{#SET_UNIT.ForEachUnitCompletelyInZone}: Iterate and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function.
+ -- * @{#SET_UNIT.ForEachUnitNotInZone}: Iterate and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function.
+ --
+ -- ## 3.5 ) SET_UNIT atomic methods
+ --
+ -- Various methods exist for a SET_UNIT to perform actions or calculations and retrieve results from the SET_UNIT:
+ --
+ -- * @{#SET_UNIT.GetTypeNames}(): Retrieve the type names of the @{Unit}s in the SET, delimited by a comma.
+ --
+ -- ===
+ -- @field #SET_UNIT SET_UNIT
+ SET_UNIT = {
+ ClassName = "SET_UNIT",
+ Units = {},
+ Filter = {
+ Coalitions = nil,
+ Categories = nil,
+ Types = nil,
+ Countries = nil,
+ UnitPrefixes = nil,
+ },
+ FilterMeta = {
+ Coalitions = {
+ red = coalition.side.RED,
+ blue = coalition.side.BLUE,
+ neutral = coalition.side.NEUTRAL,
+ },
+ Categories = {
+ plane = Unit.Category.AIRPLANE,
+ helicopter = Unit.Category.HELICOPTER,
+ ground = Unit.Category.GROUND_UNIT,
+ ship = Unit.Category.SHIP,
+ structure = Unit.Category.STRUCTURE,
+ },
+ },
+ }
- for RemoveUnitID, RemoveUnitName in pairs( RemoveUnitNamesArray ) do
- self:Remove( RemoveUnitName )
+
+ --- Get the first unit from the set.
+ -- @function [parent=#SET_UNIT] GetFirst
+ -- @param #SET_UNIT self
+ -- @return Wrapper.Unit#UNIT The UNIT object.
+
+ --- Creates a new SET_UNIT object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names.
+ -- @param #SET_UNIT self
+ -- @return #SET_UNIT
+ -- @usage
+ -- -- Define a new SET_UNIT Object. This DBObject will contain a reference to all alive Units.
+ -- DBObject = SET_UNIT:New()
+ function SET_UNIT:New()
+
+ -- Inherits from BASE
+ local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.UNITS ) ) -- Core.Set#SET_UNIT
+
+ return self
end
+
+ --- Add UNIT(s) to SET_UNIT.
+ -- @param #SET_UNIT self
+ -- @param #string AddUnit A single UNIT.
+ -- @return #SET_UNIT self
+ function SET_UNIT:AddUnit( AddUnit )
+ self:F2( AddUnit:GetName() )
+
+ self:Add( AddUnit:GetName(), AddUnit )
+
+ return self
+ end
+
+
+ --- Add UNIT(s) to SET_UNIT.
+ -- @param #SET_UNIT self
+ -- @param #string AddUnitNames A single name or an array of UNIT names.
+ -- @return #SET_UNIT self
+ function SET_UNIT:AddUnitsByName( AddUnitNames )
+
+ local AddUnitNamesArray = ( type( AddUnitNames ) == "table" ) and AddUnitNames or { AddUnitNames }
- return self
-end
+ self:T( AddUnitNamesArray )
+ for AddUnitID, AddUnitName in pairs( AddUnitNamesArray ) do
+ self:Add( AddUnitName, UNIT:FindByName( AddUnitName ) )
+ end
+
+ return self
+ end
+
+ --- Remove UNIT(s) from SET_UNIT.
+ -- @param Core.Set#SET_UNIT self
+ -- @param Wrapper.Unit#UNIT RemoveUnitNames A single name or an array of UNIT names.
+ -- @return self
+ function SET_UNIT:RemoveUnitsByName( RemoveUnitNames )
+
+ local RemoveUnitNamesArray = ( type( RemoveUnitNames ) == "table" ) and RemoveUnitNames or { RemoveUnitNames }
+
+ for RemoveUnitID, RemoveUnitName in pairs( RemoveUnitNamesArray ) do
+ self:Remove( RemoveUnitName )
+ end
+
+ return self
+ end
+
+
+ --- Finds a Unit based on the Unit Name.
+ -- @param #SET_UNIT self
+ -- @param #string UnitName
+ -- @return Wrapper.Unit#UNIT The found Unit.
+ function SET_UNIT:FindUnit( UnitName )
+
+ local UnitFound = self.Set[UnitName]
+ return UnitFound
+ end
+
+
+
+ --- Builds a set of units of coalitions.
+ -- Possible current coalitions are red, blue and neutral.
+ -- @param #SET_UNIT self
+ -- @param #string Coalitions Can take the following values: "red", "blue", "neutral".
+ -- @return #SET_UNIT self
+ function SET_UNIT:FilterCoalitions( Coalitions )
-
---- Finds a Unit based on the Unit Name.
--- @param #SET_UNIT self
--- @param #string UnitName
--- @return Wrapper.Unit#UNIT The found Unit.
-function SET_UNIT:FindUnit( UnitName )
-
- local UnitFound = self.Set[UnitName]
- return UnitFound
-end
-
-
-
---- Builds a set of units of coalitions.
--- Possible current coalitions are red, blue and neutral.
--- @param #SET_UNIT self
--- @param #string Coalitions Can take the following values: "red", "blue", "neutral".
--- @return #SET_UNIT self
-function SET_UNIT:FilterCoalitions( Coalitions )
- if not self.Filter.Coalitions then
self.Filter.Coalitions = {}
- end
- if type( Coalitions ) ~= "table" then
- Coalitions = { Coalitions }
- end
- for CoalitionID, Coalition in pairs( Coalitions ) do
- self.Filter.Coalitions[Coalition] = Coalition
- end
- return self
-end
-
-
---- Builds a set of units out of categories.
--- Possible current categories are plane, helicopter, ground, ship.
--- @param #SET_UNIT self
--- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship".
--- @return #SET_UNIT self
-function SET_UNIT:FilterCategories( Categories )
- if not self.Filter.Categories then
- self.Filter.Categories = {}
- end
- if type( Categories ) ~= "table" then
- Categories = { Categories }
- end
- for CategoryID, Category in pairs( Categories ) do
- self.Filter.Categories[Category] = Category
- end
- return self
-end
-
-
---- Builds a set of units of defined unit types.
--- Possible current types are those types known within DCS world.
--- @param #SET_UNIT self
--- @param #string Types Can take those type strings known within DCS world.
--- @return #SET_UNIT self
-function SET_UNIT:FilterTypes( Types )
- if not self.Filter.Types then
- self.Filter.Types = {}
- end
- if type( Types ) ~= "table" then
- Types = { Types }
- end
- for TypeID, Type in pairs( Types ) do
- self.Filter.Types[Type] = Type
- end
- return self
-end
-
-
---- Builds a set of units of defined countries.
--- Possible current countries are those known within DCS world.
--- @param #SET_UNIT self
--- @param #string Countries Can take those country strings known within DCS world.
--- @return #SET_UNIT self
-function SET_UNIT:FilterCountries( Countries )
- if not self.Filter.Countries then
- self.Filter.Countries = {}
- end
- if type( Countries ) ~= "table" then
- Countries = { Countries }
- end
- for CountryID, Country in pairs( Countries ) do
- self.Filter.Countries[Country] = Country
- end
- return self
-end
-
-
---- Builds a set of units of defined unit prefixes.
--- All the units starting with the given prefixes will be included within the set.
--- @param #SET_UNIT self
--- @param #string Prefixes The prefix of which the unit name starts with.
--- @return #SET_UNIT self
-function SET_UNIT:FilterPrefixes( Prefixes )
- if not self.Filter.UnitPrefixes then
- self.Filter.UnitPrefixes = {}
- end
- if type( Prefixes ) ~= "table" then
- Prefixes = { Prefixes }
- end
- for PrefixID, Prefix in pairs( Prefixes ) do
- self.Filter.UnitPrefixes[Prefix] = Prefix
- end
- return self
-end
-
---- Builds a set of units having a radar of give types.
--- All the units having a radar of a given type will be included within the set.
--- @param #SET_UNIT self
--- @param #table RadarTypes The radar types.
--- @return #SET_UNIT self
-function SET_UNIT:FilterHasRadar( RadarTypes )
-
- self.Filter.RadarTypes = self.Filter.RadarTypes or {}
- if type( RadarTypes ) ~= "table" then
- RadarTypes = { RadarTypes }
- end
- for RadarTypeID, RadarType in pairs( RadarTypes ) do
- self.Filter.RadarTypes[RadarType] = RadarType
- end
- return self
-end
-
---- Builds a set of SEADable units.
--- @param #SET_UNIT self
--- @return #SET_UNIT self
-function SET_UNIT:FilterHasSEAD()
-
- self.Filter.SEAD = true
- return self
-end
-
-
-
---- Starts the filtering.
--- @param #SET_UNIT self
--- @return #SET_UNIT self
-function SET_UNIT:FilterStart()
-
- if _DATABASE then
- self:_FilterStart()
- end
-
- return self
-end
-
---- Handles the Database to check on an event (birth) that the Object was added in the Database.
--- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event!
--- @param #SET_UNIT self
--- @param Core.Event#EVENTDATA Event
--- @return #string The name of the UNIT
--- @return #table The UNIT
-function SET_UNIT:AddInDatabase( Event )
- self:F3( { Event } )
-
- if Event.IniObjectCategory == 1 then
- if not self.Database[Event.IniDCSUnitName] then
- self.Database[Event.IniDCSUnitName] = UNIT:Register( Event.IniDCSUnitName )
- self:T3( self.Database[Event.IniDCSUnitName] )
+ if type( Coalitions ) ~= "table" then
+ Coalitions = { Coalitions }
end
+ for CoalitionID, Coalition in pairs( Coalitions ) do
+ self.Filter.Coalitions[Coalition] = Coalition
+ end
+ return self
end
- return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName]
-end
-
---- Handles the Database to check on any event that Object exists in the Database.
--- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa!
--- @param #SET_UNIT self
--- @param Core.Event#EVENTDATA Event
--- @return #string The name of the UNIT
--- @return #table The UNIT
-function SET_UNIT:FindInDatabase( Event )
- self:F2( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } )
-
-
- return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName]
-end
-
---- Iterate the SET_UNIT and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters.
--- @param #SET_UNIT self
--- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter.
--- @return #SET_UNIT self
-function SET_UNIT:ForEachUnit( IteratorFunction, ... )
- self:F2( arg )
- self:ForEach( IteratorFunction, arg, self.Set )
-
- return self
-end
-
---- Iterate the SET_UNIT **sorted *per Threat Level** and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters.
---
--- @param #SET_UNIT self
--- @param #number FromThreatLevel The TreatLevel to start the evaluation **From** (this must be a value between 0 and 10).
--- @param #number ToThreatLevel The TreatLevel to stop the evaluation **To** (this must be a value between 0 and 10).
--- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter.
--- @return #SET_UNIT self
--- @usage
---
--- UnitSet:ForEachUnitPerThreatLevel( 10, 0,
--- -- @param Wrapper.Unit#UNIT UnitObject The UNIT object in the UnitSet, that will be passed to the local function for evaluation.
--- function( UnitObject )
--- .. logic ..
--- end
--- )
---
-function SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) --R2.1 Threat Level implementation
- self:F2( arg )
+ --- Builds a set of units out of categories.
+ -- Possible current categories are plane, helicopter, ground, ship.
+ -- @param #SET_UNIT self
+ -- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship".
+ -- @return #SET_UNIT self
+ function SET_UNIT:FilterCategories( Categories )
+ if not self.Filter.Categories then
+ self.Filter.Categories = {}
+ end
+ if type( Categories ) ~= "table" then
+ Categories = { Categories }
+ end
+ for CategoryID, Category in pairs( Categories ) do
+ self.Filter.Categories[Category] = Category
+ end
+ return self
+ end
- local ThreatLevelSet = {}
- if self:Count() ~= 0 then
- for UnitName, UnitObject in pairs( self.Set ) do
- local Unit = UnitObject -- Wrapper.Unit#UNIT
-
- local ThreatLevel = Unit:GetThreatLevel()
- ThreatLevelSet[ThreatLevel] = ThreatLevelSet[ThreatLevel] or {}
- ThreatLevelSet[ThreatLevel].Set = ThreatLevelSet[ThreatLevel].Set or {}
- ThreatLevelSet[ThreatLevel].Set[UnitName] = UnitObject
- self:E( { ThreatLevel = ThreatLevel, ThreatLevelSet = ThreatLevelSet[ThreatLevel].Set } )
+ --- Builds a set of units of defined unit types.
+ -- Possible current types are those types known within DCS world.
+ -- @param #SET_UNIT self
+ -- @param #string Types Can take those type strings known within DCS world.
+ -- @return #SET_UNIT self
+ function SET_UNIT:FilterTypes( Types )
+ if not self.Filter.Types then
+ self.Filter.Types = {}
+ end
+ if type( Types ) ~= "table" then
+ Types = { Types }
+ end
+ for TypeID, Type in pairs( Types ) do
+ self.Filter.Types[Type] = Type
+ end
+ return self
+ end
+
+
+ --- Builds a set of units of defined countries.
+ -- Possible current countries are those known within DCS world.
+ -- @param #SET_UNIT self
+ -- @param #string Countries Can take those country strings known within DCS world.
+ -- @return #SET_UNIT self
+ function SET_UNIT:FilterCountries( Countries )
+ if not self.Filter.Countries then
+ self.Filter.Countries = {}
+ end
+ if type( Countries ) ~= "table" then
+ Countries = { Countries }
+ end
+ for CountryID, Country in pairs( Countries ) do
+ self.Filter.Countries[Country] = Country
+ end
+ return self
+ end
+
+
+ --- Builds a set of units of defined unit prefixes.
+ -- All the units starting with the given prefixes will be included within the set.
+ -- @param #SET_UNIT self
+ -- @param #string Prefixes The prefix of which the unit name starts with.
+ -- @return #SET_UNIT self
+ function SET_UNIT:FilterPrefixes( Prefixes )
+ if not self.Filter.UnitPrefixes then
+ self.Filter.UnitPrefixes = {}
+ end
+ if type( Prefixes ) ~= "table" then
+ Prefixes = { Prefixes }
+ end
+ for PrefixID, Prefix in pairs( Prefixes ) do
+ self.Filter.UnitPrefixes[Prefix] = Prefix
+ end
+ return self
+ end
+
+ --- Builds a set of units having a radar of give types.
+ -- All the units having a radar of a given type will be included within the set.
+ -- @param #SET_UNIT self
+ -- @param #table RadarTypes The radar types.
+ -- @return #SET_UNIT self
+ function SET_UNIT:FilterHasRadar( RadarTypes )
+
+ self.Filter.RadarTypes = self.Filter.RadarTypes or {}
+ if type( RadarTypes ) ~= "table" then
+ RadarTypes = { RadarTypes }
+ end
+ for RadarTypeID, RadarType in pairs( RadarTypes ) do
+ self.Filter.RadarTypes[RadarType] = RadarType
+ end
+ return self
+ end
+
+ --- Builds a set of SEADable units.
+ -- @param #SET_UNIT self
+ -- @return #SET_UNIT self
+ function SET_UNIT:FilterHasSEAD()
+
+ self.Filter.SEAD = true
+ return self
+ end
+
+
+
+ --- Starts the filtering.
+ -- @param #SET_UNIT self
+ -- @return #SET_UNIT self
+ function SET_UNIT:FilterStart()
+
+ if _DATABASE then
+ self:_FilterStart()
end
- local ThreatLevelIncrement = FromThreatLevel <= ToThreatLevel and 1 or -1
+ return self
+ end
+
+ --- Handles the Database to check on an event (birth) that the Object was added in the Database.
+ -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event!
+ -- @param #SET_UNIT self
+ -- @param Core.Event#EVENTDATA Event
+ -- @return #string The name of the UNIT
+ -- @return #table The UNIT
+ function SET_UNIT:AddInDatabase( Event )
+ self:F3( { Event } )
+
+ if Event.IniObjectCategory == 1 then
+ if not self.Database[Event.IniDCSUnitName] then
+ self.Database[Event.IniDCSUnitName] = UNIT:Register( Event.IniDCSUnitName )
+ self:T3( self.Database[Event.IniDCSUnitName] )
+ end
+ end
- for ThreatLevel = FromThreatLevel, ToThreatLevel, ThreatLevelIncrement do
- self:E( { ThreatLevel = ThreatLevel } )
- local ThreatLevelItem = ThreatLevelSet[ThreatLevel]
- if ThreatLevelItem then
- self:ForEach( IteratorFunction, arg, ThreatLevelItem.Set )
- end
- end
+ return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName]
end
- return self
-end
-
-
-
---- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function.
--- @param #SET_UNIT self
--- @param Core.Zone#ZONE ZoneObject The Zone to be tested for.
--- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter.
--- @return #SET_UNIT self
-function SET_UNIT:ForEachUnitCompletelyInZone( ZoneObject, IteratorFunction, ... )
- self:F2( arg )
+ --- Handles the Database to check on any event that Object exists in the Database.
+ -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa!
+ -- @param #SET_UNIT self
+ -- @param Core.Event#EVENTDATA Event
+ -- @return #string The name of the UNIT
+ -- @return #table The UNIT
+ function SET_UNIT:FindInDatabase( Event )
+ self:F2( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } )
- self:ForEach( IteratorFunction, arg, self.Set,
- --- @param Core.Zone#ZONE_BASE ZoneObject
- -- @param Wrapper.Unit#UNIT UnitObject
- function( ZoneObject, UnitObject )
- if UnitObject:IsInZone( ZoneObject ) then
- return true
- else
- return false
- end
- end, { ZoneObject } )
-
- return self
-end
-
---- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function.
--- @param #SET_UNIT self
--- @param Core.Zone#ZONE ZoneObject The Zone to be tested for.
--- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter.
--- @return #SET_UNIT self
-function SET_UNIT:ForEachUnitNotInZone( ZoneObject, IteratorFunction, ... )
- self:F2( arg )
- self:ForEach( IteratorFunction, arg, self.Set,
- --- @param Core.Zone#ZONE_BASE ZoneObject
- -- @param Wrapper.Unit#UNIT UnitObject
- function( ZoneObject, UnitObject )
- if UnitObject:IsNotInZone( ZoneObject ) then
- return true
- else
- return false
- end
- end, { ZoneObject } )
-
- return self
-end
-
---- Returns map of unit types.
--- @param #SET_UNIT self
--- @return #map<#string,#number> A map of the unit types found. The key is the UnitTypeName and the value is the amount of unit types found.
-function SET_UNIT:GetUnitTypes()
- self:F2()
-
- local MT = {} -- Message Text
- local UnitTypes = {}
-
- for UnitID, UnitData in pairs( self:GetSet() ) do
- local TextUnit = UnitData -- Wrapper.Unit#UNIT
- if TextUnit:IsAlive() then
- local UnitType = TextUnit:GetTypeName()
-
- if not UnitTypes[UnitType] then
- UnitTypes[UnitType] = 1
- else
- UnitTypes[UnitType] = UnitTypes[UnitType] + 1
- end
- end
+ return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName]
end
-
- for UnitTypeID, UnitType in pairs( UnitTypes ) do
- MT[#MT+1] = UnitType .. " of " .. UnitTypeID
- end
-
- return UnitTypes
-end
-
-
---- Returns a comma separated string of the unit types with a count in the @{Set}.
--- @param #SET_UNIT self
--- @return #string The unit types string
-function SET_UNIT:GetUnitTypesText()
- self:F2()
-
- local MT = {} -- Message Text
- local UnitTypes = self:GetUnitTypes()
- for UnitTypeID, UnitType in pairs( UnitTypes ) do
- MT[#MT+1] = UnitType .. " of " .. UnitTypeID
- end
-
- return table.concat( MT, ", " )
-end
-
---- Returns map of unit threat levels.
--- @param #SET_UNIT self
--- @return #table.
-function SET_UNIT:GetUnitThreatLevels()
- self:F2()
-
- local UnitThreatLevels = {}
- for UnitID, UnitData in pairs( self:GetSet() ) do
- local ThreatUnit = UnitData -- Wrapper.Unit#UNIT
- if ThreatUnit:IsAlive() then
- local UnitThreatLevel, UnitThreatLevelText = ThreatUnit:GetThreatLevel()
- local ThreatUnitName = ThreatUnit:GetName()
+ do -- Is Zone methods
- UnitThreatLevels[UnitThreatLevel] = UnitThreatLevels[UnitThreatLevel] or {}
- UnitThreatLevels[UnitThreatLevel].UnitThreatLevelText = UnitThreatLevelText
- UnitThreatLevels[UnitThreatLevel].Units = UnitThreatLevels[UnitThreatLevel].Units or {}
- UnitThreatLevels[UnitThreatLevel].Units[ThreatUnitName] = ThreatUnit
- end
- end
-
- return UnitThreatLevels
-end
-
---- Calculate the maxium A2G threat level of the SET_UNIT.
--- @param #SET_UNIT self
--- @return #number The maximum threatlevel
-function SET_UNIT:CalculateThreatLevelA2G()
-
- local MaxThreatLevelA2G = 0
- local MaxThreatText = ""
- for UnitName, UnitData in pairs( self:GetSet() ) do
- local ThreatUnit = UnitData -- Wrapper.Unit#UNIT
- local ThreatLevelA2G, ThreatText = ThreatUnit:GetThreatLevel()
- if ThreatLevelA2G > MaxThreatLevelA2G then
- MaxThreatLevelA2G = ThreatLevelA2G
- MaxThreatText = ThreatText
- end
- end
-
- self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } )
- return MaxThreatLevelA2G, MaxThreatText
-
-end
-
---- Get the center coordinate of the SET_UNIT.
--- @param #SET_UNIT self
--- @return Core.Point#COORDINATE The center coordinate of all the units in the set, including heading in degrees and speed in mps in case of moving units.
-function SET_UNIT:GetCoordinate()
-
- local Coordinate = self:GetFirst():GetCoordinate()
-
- local x1 = Coordinate.x
- local x2 = Coordinate.x
- local y1 = Coordinate.y
- local y2 = Coordinate.y
- local z1 = Coordinate.z
- local z2 = Coordinate.z
- local MaxVelocity = 0
- local AvgHeading = nil
- local MovingCount = 0
-
- for UnitName, UnitData in pairs( self:GetSet() ) do
-
- local Unit = UnitData -- Wrapper.Unit#UNIT
- local Coordinate = Unit:GetCoordinate()
-
- x1 = ( Coordinate.x < x1 ) and Coordinate.x or x1
- x2 = ( Coordinate.x > x2 ) and Coordinate.x or x2
- y1 = ( Coordinate.y < y1 ) and Coordinate.y or y1
- y2 = ( Coordinate.y > y2 ) and Coordinate.y or y2
- z1 = ( Coordinate.y < z1 ) and Coordinate.z or z1
- z2 = ( Coordinate.y > z2 ) and Coordinate.z or z2
-
- local Velocity = Coordinate:GetVelocity()
- if Velocity ~= 0 then
- MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity
- local Heading = Coordinate:GetHeading()
- AvgHeading = AvgHeading and ( AvgHeading + Heading ) or Heading
- MovingCount = MovingCount + 1
- end
- end
-
- AvgHeading = AvgHeading and ( AvgHeading / MovingCount )
-
- Coordinate.x = ( x2 - x1 ) / 2 + x1
- Coordinate.y = ( y2 - y1 ) / 2 + y1
- Coordinate.z = ( z2 - z1 ) / 2 + z1
- Coordinate:SetHeading( AvgHeading )
- Coordinate:SetVelocity( MaxVelocity )
-
- self:F( { Coordinate = Coordinate } )
- return Coordinate
-
-end
-
---- Get the maximum velocity of the SET_UNIT.
--- @param #SET_UNIT self
--- @return #number The speed in mps in case of moving units.
-function SET_UNIT:GetVelocity()
-
- local Coordinate = self:GetFirst():GetCoordinate()
-
- local MaxVelocity = 0
-
- for UnitName, UnitData in pairs( self:GetSet() ) do
-
- local Unit = UnitData -- Wrapper.Unit#UNIT
- local Coordinate = Unit:GetCoordinate()
-
- local Velocity = Coordinate:GetVelocity()
- if Velocity ~= 0 then
- MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity
- end
- end
-
- self:F( { MaxVelocity = MaxVelocity } )
- return MaxVelocity
-
-end
-
---- Get the average heading of the SET_UNIT.
--- @param #SET_UNIT self
--- @return #number Heading Heading in degrees and speed in mps in case of moving units.
-function SET_UNIT:GetHeading()
-
- local HeadingSet = nil
- local MovingCount = 0
-
- for UnitName, UnitData in pairs( self:GetSet() ) do
-
- local Unit = UnitData -- Wrapper.Unit#UNIT
- local Coordinate = Unit:GetCoordinate()
-
- local Velocity = Coordinate:GetVelocity()
- if Velocity ~= 0 then
- local Heading = Coordinate:GetHeading()
- if HeadingSet == nil then
- HeadingSet = Heading
- else
- local HeadingDiff = ( HeadingSet - Heading + 180 + 360 ) % 360 - 180
- HeadingDiff = math.abs( HeadingDiff )
- if HeadingDiff > 5 then
- HeadingSet = nil
- break
+ --- Check if minimal one element of the SET_UNIT is in the Zone.
+ -- @param #SET_UNIT self
+ -- @param Core.Zone#ZONE ZoneTest The Zone to be tested for.
+ -- @return #boolean
+ function SET_UNIT:IsPartiallyInZone( ZoneTest )
+
+ local IsPartiallyInZone = false
+
+ local function EvaluateZone( ZoneUnit )
+
+ local ZoneUnitName = ZoneUnit:GetName()
+ self:E( { ZoneUnitName = ZoneUnitName } )
+ if self:FindUnit( ZoneUnitName ) then
+ IsPartiallyInZone = true
+ self:E( { Found = true } )
+ return false
end
- end
- end
- end
-
- return HeadingSet
-
-end
-
-
-
---- Returns if the @{Set} has targets having a radar (of a given type).
--- @param #SET_UNIT self
--- @param Dcs.DCSWrapper.Unit#Unit.RadarType RadarType
--- @return #number The amount of radars in the Set with the given type
-function SET_UNIT:HasRadar( RadarType )
- self:F2( RadarType )
-
- local RadarCount = 0
- for UnitID, UnitData in pairs( self:GetSet()) do
- local UnitSensorTest = UnitData -- Wrapper.Unit#UNIT
- local HasSensors
- if RadarType then
- HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR, RadarType )
- else
- HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR )
- end
- self:T3(HasSensors)
- if HasSensors then
- RadarCount = RadarCount + 1
- end
- end
-
- return RadarCount
-end
-
---- Returns if the @{Set} has targets that can be SEADed.
--- @param #SET_UNIT self
--- @return #number The amount of SEADable units in the Set
-function SET_UNIT:HasSEAD()
- self:F2()
-
- local SEADCount = 0
- for UnitID, UnitData in pairs( self:GetSet()) do
- local UnitSEAD = UnitData -- Wrapper.Unit#UNIT
- if UnitSEAD:IsAlive() then
- local UnitSEADAttributes = UnitSEAD:GetDesc().attributes
-
- local HasSEAD = UnitSEAD:HasSEAD()
-
- self:T3(HasSEAD)
- if HasSEAD then
- SEADCount = SEADCount + 1
+
+ return true
end
+
+ ZoneTest:SearchZone( EvaluateZone )
+
+ return IsPartiallyInZone
end
- end
-
- return SEADCount
-end
-
---- Returns if the @{Set} has ground targets.
--- @param #SET_UNIT self
--- @return #number The amount of ground targets in the Set.
-function SET_UNIT:HasGroundUnits()
- self:F2()
-
- local GroundUnitCount = 0
- for UnitID, UnitData in pairs( self:GetSet()) do
- local UnitTest = UnitData -- Wrapper.Unit#UNIT
- if UnitTest:IsGround() then
- GroundUnitCount = GroundUnitCount + 1
- end
- end
-
- return GroundUnitCount
-end
-
---- Returns if the @{Set} has friendly ground units.
--- @param #SET_UNIT self
--- @return #number The amount of ground targets in the Set.
-function SET_UNIT:HasFriendlyUnits( FriendlyCoalition )
- self:F2()
-
- local FriendlyUnitCount = 0
- for UnitID, UnitData in pairs( self:GetSet()) do
- local UnitTest = UnitData -- Wrapper.Unit#UNIT
- if UnitTest:IsFriendly( FriendlyCoalition ) then
- FriendlyUnitCount = FriendlyUnitCount + 1
- end
- end
-
- return FriendlyUnitCount
-end
-
-
-
------ Iterate the SET_UNIT and call an interator function for each **alive** player, providing the Unit of the player and optional parameters.
----- @param #SET_UNIT self
----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a UNIT parameter.
----- @return #SET_UNIT self
---function SET_UNIT:ForEachPlayer( IteratorFunction, ... )
--- self:F2( arg )
---
--- self:ForEach( IteratorFunction, arg, self.PlayersAlive )
---
--- return self
---end
---
---
------ Iterate the SET_UNIT and call an interator function for each client, providing the Client to the function and optional parameters.
----- @param #SET_UNIT self
----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a CLIENT parameter.
----- @return #SET_UNIT self
---function SET_UNIT:ForEachClient( IteratorFunction, ... )
--- self:F2( arg )
---
--- self:ForEach( IteratorFunction, arg, self.Clients )
---
--- return self
---end
-
-
----
--- @param #SET_UNIT self
--- @param Wrapper.Unit#UNIT MUnit
--- @return #SET_UNIT self
-function SET_UNIT:IsIncludeObject( MUnit )
- self:F2( MUnit )
- local MUnitInclude = true
-
- if self.Filter.Coalitions then
- local MUnitCoalition = false
- for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do
- self:T3( { "Coalition:", MUnit:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } )
- if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MUnit:GetCoalition() then
- MUnitCoalition = true
- end
- end
- MUnitInclude = MUnitInclude and MUnitCoalition
- end
-
- if self.Filter.Categories then
- local MUnitCategory = false
- for CategoryID, CategoryName in pairs( self.Filter.Categories ) do
- self:T3( { "Category:", MUnit:GetDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } )
- if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MUnit:GetDesc().category then
- MUnitCategory = true
- end
- end
- MUnitInclude = MUnitInclude and MUnitCategory
- end
-
- if self.Filter.Types then
- local MUnitType = false
- for TypeID, TypeName in pairs( self.Filter.Types ) do
- self:T3( { "Type:", MUnit:GetTypeName(), TypeName } )
- if TypeName == MUnit:GetTypeName() then
- MUnitType = true
- end
- end
- MUnitInclude = MUnitInclude and MUnitType
- end
-
- if self.Filter.Countries then
- local MUnitCountry = false
- for CountryID, CountryName in pairs( self.Filter.Countries ) do
- self:T3( { "Country:", MUnit:GetCountry(), CountryName } )
- if country.id[CountryName] == MUnit:GetCountry() then
- MUnitCountry = true
- end
- end
- MUnitInclude = MUnitInclude and MUnitCountry
- end
-
- if self.Filter.UnitPrefixes then
- local MUnitPrefix = false
- for UnitPrefixId, UnitPrefix in pairs( self.Filter.UnitPrefixes ) do
- self:T3( { "Prefix:", string.find( MUnit:GetName(), UnitPrefix, 1 ), UnitPrefix } )
- if string.find( MUnit:GetName(), UnitPrefix, 1 ) then
- MUnitPrefix = true
- end
- end
- MUnitInclude = MUnitInclude and MUnitPrefix
- end
-
- if self.Filter.RadarTypes then
- local MUnitRadar = false
- for RadarTypeID, RadarType in pairs( self.Filter.RadarTypes ) do
- self:T3( { "Radar:", RadarType } )
- if MUnit:HasSensors( Unit.SensorType.RADAR, RadarType ) == true then
- if MUnit:GetRadar() == true then -- This call is necessary to evaluate the SEAD capability.
- self:T3( "RADAR Found" )
- end
- MUnitRadar = true
- end
- end
- MUnitInclude = MUnitInclude and MUnitRadar
- end
-
- if self.Filter.SEAD then
- local MUnitSEAD = false
- if MUnit:HasSEAD() == true then
- self:T3( "SEAD Found" )
- MUnitSEAD = true
- end
- MUnitInclude = MUnitInclude and MUnitSEAD
- end
-
- self:T2( MUnitInclude )
- return MUnitInclude
-end
-
-
---- Retrieve the type names of the @{Unit}s in the SET, delimited by an optional delimiter.
--- @param #SET_UNIT self
--- @param #string Delimiter (optional) The delimiter, which is default a comma.
--- @return #string The types of the @{Unit}s delimited.
-function SET_UNIT:GetTypeNames( Delimiter )
-
- Delimiter = Delimiter or ", "
- local TypeReport = REPORT:New()
- local Types = {}
-
- for UnitName, UnitData in pairs( self:GetSet() ) do
-
- local Unit = UnitData -- Wrapper.Unit#UNIT
- local UnitTypeName = Unit:GetTypeName()
- if not Types[UnitTypeName] then
- Types[UnitTypeName] = UnitTypeName
- TypeReport:Add( UnitTypeName )
+
+ --- Check if no element of the SET_UNIT is in the Zone.
+ -- @param #SET_UNIT self
+ -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for.
+ -- @return #boolean
+ function SET_UNIT:IsNotInZone( Zone )
+
+ local IsNotInZone = true
+
+ local function EvaluateZone( ZoneUnit )
+
+ local ZoneUnitName = ZoneUnit:GetName()
+ if self:FindUnit( ZoneUnitName ) then
+ IsNotInZone = false
+ return false
+ end
+
+ return true
+ end
+
+ Zone:SearchZone( EvaluateZone )
+
+ return IsNotInZone
end
+
+
+ --- Check if minimal one element of the SET_UNIT is in the Zone.
+ -- @param #SET_UNIT self
+ -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter.
+ -- @return #SET_UNIT self
+ function SET_UNIT:ForEachUnitInZone( IteratorFunction, ... )
+ self:F2( arg )
+
+ self:ForEach( IteratorFunction, arg, self.Set )
+
+ return self
+ end
+
+
+ end
+
+
+ --- Iterate the SET_UNIT and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters.
+ -- @param #SET_UNIT self
+ -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter.
+ -- @return #SET_UNIT self
+ function SET_UNIT:ForEachUnit( IteratorFunction, ... )
+ self:F2( arg )
+
+ self:ForEach( IteratorFunction, arg, self.Set )
+
+ return self
+ end
+
+ --- Iterate the SET_UNIT **sorted *per Threat Level** and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters.
+ --
+ -- @param #SET_UNIT self
+ -- @param #number FromThreatLevel The TreatLevel to start the evaluation **From** (this must be a value between 0 and 10).
+ -- @param #number ToThreatLevel The TreatLevel to stop the evaluation **To** (this must be a value between 0 and 10).
+ -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter.
+ -- @return #SET_UNIT self
+ -- @usage
+ --
+ -- UnitSet:ForEachUnitPerThreatLevel( 10, 0,
+ -- -- @param Wrapper.Unit#UNIT UnitObject The UNIT object in the UnitSet, that will be passed to the local function for evaluation.
+ -- function( UnitObject )
+ -- .. logic ..
+ -- end
+ -- )
+ --
+ function SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) --R2.1 Threat Level implementation
+ self:F2( arg )
+
+ local ThreatLevelSet = {}
+
+ if self:Count() ~= 0 then
+ for UnitName, UnitObject in pairs( self.Set ) do
+ local Unit = UnitObject -- Wrapper.Unit#UNIT
+
+ local ThreatLevel = Unit:GetThreatLevel()
+ ThreatLevelSet[ThreatLevel] = ThreatLevelSet[ThreatLevel] or {}
+ ThreatLevelSet[ThreatLevel].Set = ThreatLevelSet[ThreatLevel].Set or {}
+ ThreatLevelSet[ThreatLevel].Set[UnitName] = UnitObject
+ self:E( { ThreatLevel = ThreatLevel, ThreatLevelSet = ThreatLevelSet[ThreatLevel].Set } )
+ end
+
+ local ThreatLevelIncrement = FromThreatLevel <= ToThreatLevel and 1 or -1
+
+ for ThreatLevel = FromThreatLevel, ToThreatLevel, ThreatLevelIncrement do
+ self:E( { ThreatLevel = ThreatLevel } )
+ local ThreatLevelItem = ThreatLevelSet[ThreatLevel]
+ if ThreatLevelItem then
+ self:ForEach( IteratorFunction, arg, ThreatLevelItem.Set )
+ end
+ end
+ end
+
+ return self
+ end
+
+
+
+ --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function.
+ -- @param #SET_UNIT self
+ -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for.
+ -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter.
+ -- @return #SET_UNIT self
+ function SET_UNIT:ForEachUnitCompletelyInZone( ZoneObject, IteratorFunction, ... )
+ self:F2( arg )
+
+ self:ForEach( IteratorFunction, arg, self.Set,
+ --- @param Core.Zone#ZONE_BASE ZoneObject
+ -- @param Wrapper.Unit#UNIT UnitObject
+ function( ZoneObject, UnitObject )
+ if UnitObject:IsInZone( ZoneObject ) then
+ return true
+ else
+ return false
+ end
+ end, { ZoneObject } )
+
+ return self
+ end
+
+ --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function.
+ -- @param #SET_UNIT self
+ -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for.
+ -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter.
+ -- @return #SET_UNIT self
+ function SET_UNIT:ForEachUnitNotInZone( ZoneObject, IteratorFunction, ... )
+ self:F2( arg )
+
+ self:ForEach( IteratorFunction, arg, self.Set,
+ --- @param Core.Zone#ZONE_BASE ZoneObject
+ -- @param Wrapper.Unit#UNIT UnitObject
+ function( ZoneObject, UnitObject )
+ if UnitObject:IsNotInZone( ZoneObject ) then
+ return true
+ else
+ return false
+ end
+ end, { ZoneObject } )
+
+ return self
+ end
+
+ --- Returns map of unit types.
+ -- @param #SET_UNIT self
+ -- @return #map<#string,#number> A map of the unit types found. The key is the UnitTypeName and the value is the amount of unit types found.
+ function SET_UNIT:GetUnitTypes()
+ self:F2()
+
+ local MT = {} -- Message Text
+ local UnitTypes = {}
+
+ for UnitID, UnitData in pairs( self:GetSet() ) do
+ local TextUnit = UnitData -- Wrapper.Unit#UNIT
+ if TextUnit:IsAlive() then
+ local UnitType = TextUnit:GetTypeName()
+
+ if not UnitTypes[UnitType] then
+ UnitTypes[UnitType] = 1
+ else
+ UnitTypes[UnitType] = UnitTypes[UnitType] + 1
+ end
+ end
+ end
+
+ for UnitTypeID, UnitType in pairs( UnitTypes ) do
+ MT[#MT+1] = UnitType .. " of " .. UnitTypeID
+ end
+
+ return UnitTypes
+ end
+
+
+ --- Returns a comma separated string of the unit types with a count in the @{Set}.
+ -- @param #SET_UNIT self
+ -- @return #string The unit types string
+ function SET_UNIT:GetUnitTypesText()
+ self:F2()
+
+ local MT = {} -- Message Text
+ local UnitTypes = self:GetUnitTypes()
+
+ for UnitTypeID, UnitType in pairs( UnitTypes ) do
+ MT[#MT+1] = UnitType .. " of " .. UnitTypeID
+ end
+
+ return table.concat( MT, ", " )
+ end
+
+ --- Returns map of unit threat levels.
+ -- @param #SET_UNIT self
+ -- @return #table.
+ function SET_UNIT:GetUnitThreatLevels()
+ self:F2()
+
+ local UnitThreatLevels = {}
+
+ for UnitID, UnitData in pairs( self:GetSet() ) do
+ local ThreatUnit = UnitData -- Wrapper.Unit#UNIT
+ if ThreatUnit:IsAlive() then
+ local UnitThreatLevel, UnitThreatLevelText = ThreatUnit:GetThreatLevel()
+ local ThreatUnitName = ThreatUnit:GetName()
+
+ UnitThreatLevels[UnitThreatLevel] = UnitThreatLevels[UnitThreatLevel] or {}
+ UnitThreatLevels[UnitThreatLevel].UnitThreatLevelText = UnitThreatLevelText
+ UnitThreatLevels[UnitThreatLevel].Units = UnitThreatLevels[UnitThreatLevel].Units or {}
+ UnitThreatLevels[UnitThreatLevel].Units[ThreatUnitName] = ThreatUnit
+ end
+ end
+
+ return UnitThreatLevels
+ end
+
+ --- Calculate the maxium A2G threat level of the SET_UNIT.
+ -- @param #SET_UNIT self
+ -- @return #number The maximum threatlevel
+ function SET_UNIT:CalculateThreatLevelA2G()
+
+ local MaxThreatLevelA2G = 0
+ local MaxThreatText = ""
+ for UnitName, UnitData in pairs( self:GetSet() ) do
+ local ThreatUnit = UnitData -- Wrapper.Unit#UNIT
+ local ThreatLevelA2G, ThreatText = ThreatUnit:GetThreatLevel()
+ if ThreatLevelA2G > MaxThreatLevelA2G then
+ MaxThreatLevelA2G = ThreatLevelA2G
+ MaxThreatText = ThreatText
+ end
+ end
+
+ self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } )
+ return MaxThreatLevelA2G, MaxThreatText
+
+ end
+
+ --- Get the center coordinate of the SET_UNIT.
+ -- @param #SET_UNIT self
+ -- @return Core.Point#COORDINATE The center coordinate of all the units in the set, including heading in degrees and speed in mps in case of moving units.
+ function SET_UNIT:GetCoordinate()
+
+ local Coordinate = self:GetFirst():GetCoordinate()
+
+ local x1 = Coordinate.x
+ local x2 = Coordinate.x
+ local y1 = Coordinate.y
+ local y2 = Coordinate.y
+ local z1 = Coordinate.z
+ local z2 = Coordinate.z
+ local MaxVelocity = 0
+ local AvgHeading = nil
+ local MovingCount = 0
+
+ for UnitName, UnitData in pairs( self:GetSet() ) do
+
+ local Unit = UnitData -- Wrapper.Unit#UNIT
+ local Coordinate = Unit:GetCoordinate()
+
+ x1 = ( Coordinate.x < x1 ) and Coordinate.x or x1
+ x2 = ( Coordinate.x > x2 ) and Coordinate.x or x2
+ y1 = ( Coordinate.y < y1 ) and Coordinate.y or y1
+ y2 = ( Coordinate.y > y2 ) and Coordinate.y or y2
+ z1 = ( Coordinate.y < z1 ) and Coordinate.z or z1
+ z2 = ( Coordinate.y > z2 ) and Coordinate.z or z2
+
+ local Velocity = Coordinate:GetVelocity()
+ if Velocity ~= 0 then
+ MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity
+ local Heading = Coordinate:GetHeading()
+ AvgHeading = AvgHeading and ( AvgHeading + Heading ) or Heading
+ MovingCount = MovingCount + 1
+ end
+ end
+
+ AvgHeading = AvgHeading and ( AvgHeading / MovingCount )
+
+ Coordinate.x = ( x2 - x1 ) / 2 + x1
+ Coordinate.y = ( y2 - y1 ) / 2 + y1
+ Coordinate.z = ( z2 - z1 ) / 2 + z1
+ Coordinate:SetHeading( AvgHeading )
+ Coordinate:SetVelocity( MaxVelocity )
+
+ self:F( { Coordinate = Coordinate } )
+ return Coordinate
+
+ end
+
+ --- Get the maximum velocity of the SET_UNIT.
+ -- @param #SET_UNIT self
+ -- @return #number The speed in mps in case of moving units.
+ function SET_UNIT:GetVelocity()
+
+ local Coordinate = self:GetFirst():GetCoordinate()
+
+ local MaxVelocity = 0
+
+ for UnitName, UnitData in pairs( self:GetSet() ) do
+
+ local Unit = UnitData -- Wrapper.Unit#UNIT
+ local Coordinate = Unit:GetCoordinate()
+
+ local Velocity = Coordinate:GetVelocity()
+ if Velocity ~= 0 then
+ MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity
+ end
+ end
+
+ self:F( { MaxVelocity = MaxVelocity } )
+ return MaxVelocity
+
+ end
+
+ --- Get the average heading of the SET_UNIT.
+ -- @param #SET_UNIT self
+ -- @return #number Heading Heading in degrees and speed in mps in case of moving units.
+ function SET_UNIT:GetHeading()
+
+ local HeadingSet = nil
+ local MovingCount = 0
+
+ for UnitName, UnitData in pairs( self:GetSet() ) do
+
+ local Unit = UnitData -- Wrapper.Unit#UNIT
+ local Coordinate = Unit:GetCoordinate()
+
+ local Velocity = Coordinate:GetVelocity()
+ if Velocity ~= 0 then
+ local Heading = Coordinate:GetHeading()
+ if HeadingSet == nil then
+ HeadingSet = Heading
+ else
+ local HeadingDiff = ( HeadingSet - Heading + 180 + 360 ) % 360 - 180
+ HeadingDiff = math.abs( HeadingDiff )
+ if HeadingDiff > 5 then
+ HeadingSet = nil
+ break
+ end
+ end
+ end
+ end
+
+ return HeadingSet
+
+ end
+
+
+
+ --- Returns if the @{Set} has targets having a radar (of a given type).
+ -- @param #SET_UNIT self
+ -- @param Dcs.DCSWrapper.Unit#Unit.RadarType RadarType
+ -- @return #number The amount of radars in the Set with the given type
+ function SET_UNIT:HasRadar( RadarType )
+ self:F2( RadarType )
+
+ local RadarCount = 0
+ for UnitID, UnitData in pairs( self:GetSet()) do
+ local UnitSensorTest = UnitData -- Wrapper.Unit#UNIT
+ local HasSensors
+ if RadarType then
+ HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR, RadarType )
+ else
+ HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR )
+ end
+ self:T3(HasSensors)
+ if HasSensors then
+ RadarCount = RadarCount + 1
+ end
+ end
+
+ return RadarCount
+ end
+
+ --- Returns if the @{Set} has targets that can be SEADed.
+ -- @param #SET_UNIT self
+ -- @return #number The amount of SEADable units in the Set
+ function SET_UNIT:HasSEAD()
+ self:F2()
+
+ local SEADCount = 0
+ for UnitID, UnitData in pairs( self:GetSet()) do
+ local UnitSEAD = UnitData -- Wrapper.Unit#UNIT
+ if UnitSEAD:IsAlive() then
+ local UnitSEADAttributes = UnitSEAD:GetDesc().attributes
+
+ local HasSEAD = UnitSEAD:HasSEAD()
+
+ self:T3(HasSEAD)
+ if HasSEAD then
+ SEADCount = SEADCount + 1
+ end
+ end
+ end
+
+ return SEADCount
+ end
+
+ --- Returns if the @{Set} has ground targets.
+ -- @param #SET_UNIT self
+ -- @return #number The amount of ground targets in the Set.
+ function SET_UNIT:HasGroundUnits()
+ self:F2()
+
+ local GroundUnitCount = 0
+ for UnitID, UnitData in pairs( self:GetSet()) do
+ local UnitTest = UnitData -- Wrapper.Unit#UNIT
+ if UnitTest:IsGround() then
+ GroundUnitCount = GroundUnitCount + 1
+ end
+ end
+
+ return GroundUnitCount
+ end
+
+ --- Returns if the @{Set} has friendly ground units.
+ -- @param #SET_UNIT self
+ -- @return #number The amount of ground targets in the Set.
+ function SET_UNIT:HasFriendlyUnits( FriendlyCoalition )
+ self:F2()
+
+ local FriendlyUnitCount = 0
+ for UnitID, UnitData in pairs( self:GetSet()) do
+ local UnitTest = UnitData -- Wrapper.Unit#UNIT
+ if UnitTest:IsFriendly( FriendlyCoalition ) then
+ FriendlyUnitCount = FriendlyUnitCount + 1
+ end
+ end
+
+ return FriendlyUnitCount
+ end
+
+
+
+ ----- Iterate the SET_UNIT and call an interator function for each **alive** player, providing the Unit of the player and optional parameters.
+ ---- @param #SET_UNIT self
+ ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a UNIT parameter.
+ ---- @return #SET_UNIT self
+ --function SET_UNIT:ForEachPlayer( IteratorFunction, ... )
+ -- self:F2( arg )
+ --
+ -- self:ForEach( IteratorFunction, arg, self.PlayersAlive )
+ --
+ -- return self
+ --end
+ --
+ --
+ ----- Iterate the SET_UNIT and call an interator function for each client, providing the Client to the function and optional parameters.
+ ---- @param #SET_UNIT self
+ ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a CLIENT parameter.
+ ---- @return #SET_UNIT self
+ --function SET_UNIT:ForEachClient( IteratorFunction, ... )
+ -- self:F2( arg )
+ --
+ -- self:ForEach( IteratorFunction, arg, self.Clients )
+ --
+ -- return self
+ --end
+
+
+ ---
+ -- @param #SET_UNIT self
+ -- @param Wrapper.Unit#UNIT MUnit
+ -- @return #SET_UNIT self
+ function SET_UNIT:IsIncludeObject( MUnit )
+ self:F2( MUnit )
+ local MUnitInclude = true
+
+ if self.Filter.Coalitions then
+ local MUnitCoalition = false
+ for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do
+ self:E( { "Coalition:", MUnit:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } )
+ if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MUnit:GetCoalition() then
+ MUnitCoalition = true
+ end
+ end
+ MUnitInclude = MUnitInclude and MUnitCoalition
+ end
+
+ if self.Filter.Categories then
+ local MUnitCategory = false
+ for CategoryID, CategoryName in pairs( self.Filter.Categories ) do
+ self:T3( { "Category:", MUnit:GetDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } )
+ if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MUnit:GetDesc().category then
+ MUnitCategory = true
+ end
+ end
+ MUnitInclude = MUnitInclude and MUnitCategory
+ end
+
+ if self.Filter.Types then
+ local MUnitType = false
+ for TypeID, TypeName in pairs( self.Filter.Types ) do
+ self:T3( { "Type:", MUnit:GetTypeName(), TypeName } )
+ if TypeName == MUnit:GetTypeName() then
+ MUnitType = true
+ end
+ end
+ MUnitInclude = MUnitInclude and MUnitType
+ end
+
+ if self.Filter.Countries then
+ local MUnitCountry = false
+ for CountryID, CountryName in pairs( self.Filter.Countries ) do
+ self:T3( { "Country:", MUnit:GetCountry(), CountryName } )
+ if country.id[CountryName] == MUnit:GetCountry() then
+ MUnitCountry = true
+ end
+ end
+ MUnitInclude = MUnitInclude and MUnitCountry
+ end
+
+ if self.Filter.UnitPrefixes then
+ local MUnitPrefix = false
+ for UnitPrefixId, UnitPrefix in pairs( self.Filter.UnitPrefixes ) do
+ self:T3( { "Prefix:", string.find( MUnit:GetName(), UnitPrefix, 1 ), UnitPrefix } )
+ if string.find( MUnit:GetName(), UnitPrefix, 1 ) then
+ MUnitPrefix = true
+ end
+ end
+ MUnitInclude = MUnitInclude and MUnitPrefix
+ end
+
+ if self.Filter.RadarTypes then
+ local MUnitRadar = false
+ for RadarTypeID, RadarType in pairs( self.Filter.RadarTypes ) do
+ self:T3( { "Radar:", RadarType } )
+ if MUnit:HasSensors( Unit.SensorType.RADAR, RadarType ) == true then
+ if MUnit:GetRadar() == true then -- This call is necessary to evaluate the SEAD capability.
+ self:T3( "RADAR Found" )
+ end
+ MUnitRadar = true
+ end
+ end
+ MUnitInclude = MUnitInclude and MUnitRadar
+ end
+
+ if self.Filter.SEAD then
+ local MUnitSEAD = false
+ if MUnit:HasSEAD() == true then
+ self:T3( "SEAD Found" )
+ MUnitSEAD = true
+ end
+ MUnitInclude = MUnitInclude and MUnitSEAD
+ end
+
+ self:T2( MUnitInclude )
+ return MUnitInclude
+ end
+
+
+ --- Retrieve the type names of the @{Unit}s in the SET, delimited by an optional delimiter.
+ -- @param #SET_UNIT self
+ -- @param #string Delimiter (optional) The delimiter, which is default a comma.
+ -- @return #string The types of the @{Unit}s delimited.
+ function SET_UNIT:GetTypeNames( Delimiter )
+
+ Delimiter = Delimiter or ", "
+ local TypeReport = REPORT:New()
+ local Types = {}
+
+ for UnitName, UnitData in pairs( self:GetSet() ) do
+
+ local Unit = UnitData -- Wrapper.Unit#UNIT
+ local UnitTypeName = Unit:GetTypeName()
+
+ if not Types[UnitTypeName] then
+ Types[UnitTypeName] = UnitTypeName
+ TypeReport:Add( UnitTypeName )
+ end
+ end
+
+ return TypeReport:Text( Delimiter )
+ end
+
+end
+
+do -- SET_STATIC
+
+ --- @type SET_STATIC
+ -- @extends Core.Set#SET_BASE
+
+ --- # 3) SET_STATIC class, extends @{Set#SET_BASE}
+ --
+ -- Mission designers can use the SET_STATIC class to build sets of Statics belonging to certain:
+ --
+ -- * Coalitions
+ -- * Categories
+ -- * Countries
+ -- * Static types
+ -- * Starting with certain prefix strings.
+ --
+ -- ## 3.1) SET_STATIC constructor
+ --
+ -- Create a new SET_STATIC object with the @{#SET_STATIC.New} method:
+ --
+ -- * @{#SET_STATIC.New}: Creates a new SET_STATIC object.
+ --
+ -- ## 3.2) Add or Remove STATIC(s) from SET_STATIC
+ --
+ -- STATICs can be added and removed using the @{Set#SET_STATIC.AddStaticsByName} and @{Set#SET_STATIC.RemoveStaticsByName} respectively.
+ -- These methods take a single STATIC name or an array of STATIC names to be added or removed from SET_STATIC.
+ --
+ -- ## 3.3) SET_STATIC filter criteria
+ --
+ -- You can set filter criteria to define the set of units within the SET_STATIC.
+ -- Filter criteria are defined by:
+ --
+ -- * @{#SET_STATIC.FilterCoalitions}: Builds the SET_STATIC with the units belonging to the coalition(s).
+ -- * @{#SET_STATIC.FilterCategories}: Builds the SET_STATIC with the units belonging to the category(ies).
+ -- * @{#SET_STATIC.FilterTypes}: Builds the SET_STATIC with the units belonging to the unit type(s).
+ -- * @{#SET_STATIC.FilterCountries}: Builds the SET_STATIC with the units belonging to the country(ies).
+ -- * @{#SET_STATIC.FilterPrefixes}: Builds the SET_STATIC with the units starting with the same prefix string(s).
+ --
+ -- Once the filter criteria have been set for the SET_STATIC, you can start filtering using:
+ --
+ -- * @{#SET_STATIC.FilterStart}: Starts the filtering of the units within the SET_STATIC.
+ --
+ -- Planned filter criteria within development are (so these are not yet available):
+ --
+ -- * @{#SET_STATIC.FilterZones}: Builds the SET_STATIC with the units within a @{Zone#ZONE}.
+ --
+ -- ## 3.4) SET_STATIC iterators
+ --
+ -- Once the filters have been defined and the SET_STATIC has been built, you can iterate the SET_STATIC with the available iterator methods.
+ -- The iterator methods will walk the SET_STATIC set, and call for each element within the set a function that you provide.
+ -- The following iterator methods are currently available within the SET_STATIC:
+ --
+ -- * @{#SET_STATIC.ForEachStatic}: Calls a function for each alive unit it finds within the SET_STATIC.
+ -- * @{#SET_GROUP.ForEachGroupCompletelyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function.
+ -- * @{#SET_GROUP.ForEachGroupNotInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function.
+ --
+ -- Planned iterators methods in development are (so these are not yet available):
+ --
+ -- * @{#SET_STATIC.ForEachStaticInZone}: Calls a function for each unit contained within the SET_STATIC.
+ -- * @{#SET_STATIC.ForEachStaticCompletelyInZone}: Iterate and call an iterator function for each **alive** STATIC presence completely in a @{Zone}, providing the STATIC and optional parameters to the called function.
+ -- * @{#SET_STATIC.ForEachStaticNotInZone}: Iterate and call an iterator function for each **alive** STATIC presence not in a @{Zone}, providing the STATIC and optional parameters to the called function.
+ --
+ -- ## 3.5 ) SET_STATIC atomic methods
+ --
+ -- Various methods exist for a SET_STATIC to perform actions or calculations and retrieve results from the SET_STATIC:
+ --
+ -- * @{#SET_STATIC.GetTypeNames}(): Retrieve the type names of the @{Static}s in the SET, delimited by a comma.
+ --
+ -- ===
+ -- @field #SET_STATIC SET_STATIC
+ SET_STATIC = {
+ ClassName = "SET_STATIC",
+ Statics = {},
+ Filter = {
+ Coalitions = nil,
+ Categories = nil,
+ Types = nil,
+ Countries = nil,
+ StaticPrefixes = nil,
+ },
+ FilterMeta = {
+ Coalitions = {
+ red = coalition.side.RED,
+ blue = coalition.side.BLUE,
+ neutral = coalition.side.NEUTRAL,
+ },
+ Categories = {
+ plane = Unit.Category.AIRPLANE,
+ helicopter = Unit.Category.HELICOPTER,
+ ground = Unit.Category.GROUND_STATIC,
+ ship = Unit.Category.SHIP,
+ structure = Unit.Category.STRUCTURE,
+ },
+ },
+ }
+
+
+ --- Get the first unit from the set.
+ -- @function [parent=#SET_STATIC] GetFirst
+ -- @param #SET_STATIC self
+ -- @return Wrapper.Static#STATIC The STATIC object.
+
+ --- Creates a new SET_STATIC object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names.
+ -- @param #SET_STATIC self
+ -- @return #SET_STATIC
+ -- @usage
+ -- -- Define a new SET_STATIC Object. This DBObject will contain a reference to all alive Statics.
+ -- DBObject = SET_STATIC:New()
+ function SET_STATIC:New()
+
+ -- Inherits from BASE
+ local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.STATICS ) ) -- Core.Set#SET_STATIC
+
+ return self
+ end
+
+ --- Add STATIC(s) to SET_STATIC.
+ -- @param #SET_STATIC self
+ -- @param #string AddStatic A single STATIC.
+ -- @return #SET_STATIC self
+ function SET_STATIC:AddStatic( AddStatic )
+ self:F2( AddStatic:GetName() )
+
+ self:Add( AddStatic:GetName(), AddStatic )
+
+ return self
+ end
+
+
+ --- Add STATIC(s) to SET_STATIC.
+ -- @param #SET_STATIC self
+ -- @param #string AddStaticNames A single name or an array of STATIC names.
+ -- @return #SET_STATIC self
+ function SET_STATIC:AddStaticsByName( AddStaticNames )
+
+ local AddStaticNamesArray = ( type( AddStaticNames ) == "table" ) and AddStaticNames or { AddStaticNames }
+
+ self:T( AddStaticNamesArray )
+ for AddStaticID, AddStaticName in pairs( AddStaticNamesArray ) do
+ self:Add( AddStaticName, STATIC:FindByName( AddStaticName ) )
+ end
+
+ return self
+ end
+
+ --- Remove STATIC(s) from SET_STATIC.
+ -- @param Core.Set#SET_STATIC self
+ -- @param Wrapper.Static#STATIC RemoveStaticNames A single name or an array of STATIC names.
+ -- @return self
+ function SET_STATIC:RemoveStaticsByName( RemoveStaticNames )
+
+ local RemoveStaticNamesArray = ( type( RemoveStaticNames ) == "table" ) and RemoveStaticNames or { RemoveStaticNames }
+
+ for RemoveStaticID, RemoveStaticName in pairs( RemoveStaticNamesArray ) do
+ self:Remove( RemoveStaticName )
+ end
+
+ return self
+ end
+
+
+ --- Finds a Static based on the Static Name.
+ -- @param #SET_STATIC self
+ -- @param #string StaticName
+ -- @return Wrapper.Static#STATIC The found Static.
+ function SET_STATIC:FindStatic( StaticName )
+
+ local StaticFound = self.Set[StaticName]
+ return StaticFound
+ end
+
+
+
+ --- Builds a set of units of coalitions.
+ -- Possible current coalitions are red, blue and neutral.
+ -- @param #SET_STATIC self
+ -- @param #string Coalitions Can take the following values: "red", "blue", "neutral".
+ -- @return #SET_STATIC self
+ function SET_STATIC:FilterCoalitions( Coalitions )
+ if not self.Filter.Coalitions then
+ self.Filter.Coalitions = {}
+ end
+ if type( Coalitions ) ~= "table" then
+ Coalitions = { Coalitions }
+ end
+ for CoalitionID, Coalition in pairs( Coalitions ) do
+ self.Filter.Coalitions[Coalition] = Coalition
+ end
+ return self
+ end
+
+
+ --- Builds a set of units out of categories.
+ -- Possible current categories are plane, helicopter, ground, ship.
+ -- @param #SET_STATIC self
+ -- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship".
+ -- @return #SET_STATIC self
+ function SET_STATIC:FilterCategories( Categories )
+ if not self.Filter.Categories then
+ self.Filter.Categories = {}
+ end
+ if type( Categories ) ~= "table" then
+ Categories = { Categories }
+ end
+ for CategoryID, Category in pairs( Categories ) do
+ self.Filter.Categories[Category] = Category
+ end
+ return self
+ end
+
+
+ --- Builds a set of units of defined unit types.
+ -- Possible current types are those types known within DCS world.
+ -- @param #SET_STATIC self
+ -- @param #string Types Can take those type strings known within DCS world.
+ -- @return #SET_STATIC self
+ function SET_STATIC:FilterTypes( Types )
+ if not self.Filter.Types then
+ self.Filter.Types = {}
+ end
+ if type( Types ) ~= "table" then
+ Types = { Types }
+ end
+ for TypeID, Type in pairs( Types ) do
+ self.Filter.Types[Type] = Type
+ end
+ return self
+ end
+
+
+ --- Builds a set of units of defined countries.
+ -- Possible current countries are those known within DCS world.
+ -- @param #SET_STATIC self
+ -- @param #string Countries Can take those country strings known within DCS world.
+ -- @return #SET_STATIC self
+ function SET_STATIC:FilterCountries( Countries )
+ if not self.Filter.Countries then
+ self.Filter.Countries = {}
+ end
+ if type( Countries ) ~= "table" then
+ Countries = { Countries }
+ end
+ for CountryID, Country in pairs( Countries ) do
+ self.Filter.Countries[Country] = Country
+ end
+ return self
+ end
+
+
+ --- Builds a set of units of defined unit prefixes.
+ -- All the units starting with the given prefixes will be included within the set.
+ -- @param #SET_STATIC self
+ -- @param #string Prefixes The prefix of which the unit name starts with.
+ -- @return #SET_STATIC self
+ function SET_STATIC:FilterPrefixes( Prefixes )
+ if not self.Filter.StaticPrefixes then
+ self.Filter.StaticPrefixes = {}
+ end
+ if type( Prefixes ) ~= "table" then
+ Prefixes = { Prefixes }
+ end
+ for PrefixID, Prefix in pairs( Prefixes ) do
+ self.Filter.StaticPrefixes[Prefix] = Prefix
+ end
+ return self
+ end
+
+
+ --- Starts the filtering.
+ -- @param #SET_STATIC self
+ -- @return #SET_STATIC self
+ function SET_STATIC:FilterStart()
+
+ if _DATABASE then
+ self:_FilterStart()
+ end
+
+ return self
+ end
+
+ --- Handles the Database to check on an event (birth) that the Object was added in the Database.
+ -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event!
+ -- @param #SET_STATIC self
+ -- @param Core.Event#EVENTDATA Event
+ -- @return #string The name of the STATIC
+ -- @return #table The STATIC
+ function SET_STATIC:AddInDatabase( Event )
+ self:F3( { Event } )
+
+ if Event.IniObjectCategory == Object.Category.STATIC then
+ if not self.Database[Event.IniDCSStaticName] then
+ self.Database[Event.IniDCSStaticName] = STATIC:Register( Event.IniDCSStaticName )
+ self:T3( self.Database[Event.IniDCSStaticName] )
+ end
+ end
+
+ return Event.IniDCSStaticName, self.Database[Event.IniDCSStaticName]
+ end
+
+ --- Handles the Database to check on any event that Object exists in the Database.
+ -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa!
+ -- @param #SET_STATIC self
+ -- @param Core.Event#EVENTDATA Event
+ -- @return #string The name of the STATIC
+ -- @return #table The STATIC
+ function SET_STATIC:FindInDatabase( Event )
+ self:F2( { Event.IniDCSStaticName, self.Set[Event.IniDCSStaticName], Event } )
+
+
+ return Event.IniDCSStaticName, self.Set[Event.IniDCSStaticName]
+ end
+
+
+ do -- Is Zone methods
+
+ --- Check if minimal one element of the SET_STATIC is in the Zone.
+ -- @param #SET_STATIC self
+ -- @param Core.Zone#ZONE Zone The Zone to be tested for.
+ -- @return #boolean
+ function SET_STATIC:IsPatriallyInZone( Zone )
+
+ local IsPartiallyInZone = false
+
+ local function EvaluateZone( ZoneStatic )
+
+ local ZoneStaticName = ZoneStatic:GetName()
+ if self:FindStatic( ZoneStaticName ) then
+ IsPartiallyInZone = true
+ return false
+ end
+
+ return true
+ end
+
+ return IsPartiallyInZone
+ end
+
+
+ --- Check if no element of the SET_STATIC is in the Zone.
+ -- @param #SET_STATIC self
+ -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for.
+ -- @return #boolean
+ function SET_STATIC:IsNotInZone( Zone )
+
+ local IsNotInZone = true
+
+ local function EvaluateZone( ZoneStatic )
+
+ local ZoneStaticName = ZoneStatic:GetName()
+ if self:FindStatic( ZoneStaticName ) then
+ IsNotInZone = false
+ return false
+ end
+
+ return true
+ end
+
+ Zone:Search( EvaluateZone )
+
+ return IsNotInZone
+ end
+
+
+ --- Check if minimal one element of the SET_STATIC is in the Zone.
+ -- @param #SET_STATIC self
+ -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter.
+ -- @return #SET_STATIC self
+ function SET_STATIC:ForEachStaticInZone( IteratorFunction, ... )
+ self:F2( arg )
+
+ self:ForEach( IteratorFunction, arg, self.Set )
+
+ return self
+ end
+
+
+ end
+
+
+ --- Iterate the SET_STATIC and call an interator function for each **alive** STATIC, providing the STATIC and optional parameters.
+ -- @param #SET_STATIC self
+ -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter.
+ -- @return #SET_STATIC self
+ function SET_STATIC:ForEachStatic( IteratorFunction, ... )
+ self:F2( arg )
+
+ self:ForEach( IteratorFunction, arg, self.Set )
+
+ return self
+ end
+
+
+ --- Iterate the SET_STATIC and call an iterator function for each **alive** STATIC presence completely in a @{Zone}, providing the STATIC and optional parameters to the called function.
+ -- @param #SET_STATIC self
+ -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for.
+ -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter.
+ -- @return #SET_STATIC self
+ function SET_STATIC:ForEachStaticCompletelyInZone( ZoneObject, IteratorFunction, ... )
+ self:F2( arg )
+
+ self:ForEach( IteratorFunction, arg, self.Set,
+ --- @param Core.Zone#ZONE_BASE ZoneObject
+ -- @param Wrapper.Static#STATIC StaticObject
+ function( ZoneObject, StaticObject )
+ if StaticObject:IsInZone( ZoneObject ) then
+ return true
+ else
+ return false
+ end
+ end, { ZoneObject } )
+
+ return self
+ end
+
+ --- Iterate the SET_STATIC and call an iterator function for each **alive** STATIC presence not in a @{Zone}, providing the STATIC and optional parameters to the called function.
+ -- @param #SET_STATIC self
+ -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for.
+ -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter.
+ -- @return #SET_STATIC self
+ function SET_STATIC:ForEachStaticNotInZone( ZoneObject, IteratorFunction, ... )
+ self:F2( arg )
+
+ self:ForEach( IteratorFunction, arg, self.Set,
+ --- @param Core.Zone#ZONE_BASE ZoneObject
+ -- @param Wrapper.Static#STATIC StaticObject
+ function( ZoneObject, StaticObject )
+ if StaticObject:IsNotInZone( ZoneObject ) then
+ return true
+ else
+ return false
+ end
+ end, { ZoneObject } )
+
+ return self
+ end
+
+ --- Returns map of unit types.
+ -- @param #SET_STATIC self
+ -- @return #map<#string,#number> A map of the unit types found. The key is the StaticTypeName and the value is the amount of unit types found.
+ function SET_STATIC:GetStaticTypes()
+ self:F2()
+
+ local MT = {} -- Message Text
+ local StaticTypes = {}
+
+ for StaticID, StaticData in pairs( self:GetSet() ) do
+ local TextStatic = StaticData -- Wrapper.Static#STATIC
+ if TextStatic:IsAlive() then
+ local StaticType = TextStatic:GetTypeName()
+
+ if not StaticTypes[StaticType] then
+ StaticTypes[StaticType] = 1
+ else
+ StaticTypes[StaticType] = StaticTypes[StaticType] + 1
+ end
+ end
+ end
+
+ for StaticTypeID, StaticType in pairs( StaticTypes ) do
+ MT[#MT+1] = StaticType .. " of " .. StaticTypeID
+ end
+
+ return StaticTypes
+ end
+
+
+ --- Returns a comma separated string of the unit types with a count in the @{Set}.
+ -- @param #SET_STATIC self
+ -- @return #string The unit types string
+ function SET_STATIC:GetStaticTypesText()
+ self:F2()
+
+ local MT = {} -- Message Text
+ local StaticTypes = self:GetStaticTypes()
+
+ for StaticTypeID, StaticType in pairs( StaticTypes ) do
+ MT[#MT+1] = StaticType .. " of " .. StaticTypeID
+ end
+
+ return table.concat( MT, ", " )
+ end
+
+ --- Get the center coordinate of the SET_STATIC.
+ -- @param #SET_STATIC self
+ -- @return Core.Point#COORDINATE The center coordinate of all the units in the set, including heading in degrees and speed in mps in case of moving units.
+ function SET_STATIC:GetCoordinate()
+
+ local Coordinate = self:GetFirst():GetCoordinate()
+
+ local x1 = Coordinate.x
+ local x2 = Coordinate.x
+ local y1 = Coordinate.y
+ local y2 = Coordinate.y
+ local z1 = Coordinate.z
+ local z2 = Coordinate.z
+ local MaxVelocity = 0
+ local AvgHeading = nil
+ local MovingCount = 0
+
+ for StaticName, StaticData in pairs( self:GetSet() ) do
+
+ local Static = StaticData -- Wrapper.Static#STATIC
+ local Coordinate = Static:GetCoordinate()
+
+ x1 = ( Coordinate.x < x1 ) and Coordinate.x or x1
+ x2 = ( Coordinate.x > x2 ) and Coordinate.x or x2
+ y1 = ( Coordinate.y < y1 ) and Coordinate.y or y1
+ y2 = ( Coordinate.y > y2 ) and Coordinate.y or y2
+ z1 = ( Coordinate.y < z1 ) and Coordinate.z or z1
+ z2 = ( Coordinate.y > z2 ) and Coordinate.z or z2
+
+ local Velocity = Coordinate:GetVelocity()
+ if Velocity ~= 0 then
+ MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity
+ local Heading = Coordinate:GetHeading()
+ AvgHeading = AvgHeading and ( AvgHeading + Heading ) or Heading
+ MovingCount = MovingCount + 1
+ end
+ end
+
+ AvgHeading = AvgHeading and ( AvgHeading / MovingCount )
+
+ Coordinate.x = ( x2 - x1 ) / 2 + x1
+ Coordinate.y = ( y2 - y1 ) / 2 + y1
+ Coordinate.z = ( z2 - z1 ) / 2 + z1
+ Coordinate:SetHeading( AvgHeading )
+ Coordinate:SetVelocity( MaxVelocity )
+
+ self:F( { Coordinate = Coordinate } )
+ return Coordinate
+
+ end
+
+ --- Get the maximum velocity of the SET_STATIC.
+ -- @param #SET_STATIC self
+ -- @return #number The speed in mps in case of moving units.
+ function SET_STATIC:GetVelocity()
+
+ return 0
+
+ end
+
+ --- Get the average heading of the SET_STATIC.
+ -- @param #SET_STATIC self
+ -- @return #number Heading Heading in degrees and speed in mps in case of moving units.
+ function SET_STATIC:GetHeading()
+
+ local HeadingSet = nil
+ local MovingCount = 0
+
+ for StaticName, StaticData in pairs( self:GetSet() ) do
+
+ local Static = StaticData -- Wrapper.Static#STATIC
+ local Coordinate = Static:GetCoordinate()
+
+ local Velocity = Coordinate:GetVelocity()
+ if Velocity ~= 0 then
+ local Heading = Coordinate:GetHeading()
+ if HeadingSet == nil then
+ HeadingSet = Heading
+ else
+ local HeadingDiff = ( HeadingSet - Heading + 180 + 360 ) % 360 - 180
+ HeadingDiff = math.abs( HeadingDiff )
+ if HeadingDiff > 5 then
+ HeadingSet = nil
+ break
+ end
+ end
+ end
+ end
+
+ return HeadingSet
+
+ end
+
+
+ ---
+ -- @param #SET_STATIC self
+ -- @param Wrapper.Static#STATIC MStatic
+ -- @return #SET_STATIC self
+ function SET_STATIC:IsIncludeObject( MStatic )
+ self:F2( MStatic )
+ local MStaticInclude = true
+
+ if self.Filter.Coalitions then
+ local MStaticCoalition = false
+ for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do
+ self:T3( { "Coalition:", MStatic:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } )
+ if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MStatic:GetCoalition() then
+ MStaticCoalition = true
+ end
+ end
+ MStaticInclude = MStaticInclude and MStaticCoalition
+ end
+
+ if self.Filter.Categories then
+ local MStaticCategory = false
+ for CategoryID, CategoryName in pairs( self.Filter.Categories ) do
+ self:T3( { "Category:", MStatic:GetDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } )
+ if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MStatic:GetDesc().category then
+ MStaticCategory = true
+ end
+ end
+ MStaticInclude = MStaticInclude and MStaticCategory
+ end
+
+ if self.Filter.Types then
+ local MStaticType = false
+ for TypeID, TypeName in pairs( self.Filter.Types ) do
+ self:T3( { "Type:", MStatic:GetTypeName(), TypeName } )
+ if TypeName == MStatic:GetTypeName() then
+ MStaticType = true
+ end
+ end
+ MStaticInclude = MStaticInclude and MStaticType
+ end
+
+ if self.Filter.Countries then
+ local MStaticCountry = false
+ for CountryID, CountryName in pairs( self.Filter.Countries ) do
+ self:T3( { "Country:", MStatic:GetCountry(), CountryName } )
+ if country.id[CountryName] == MStatic:GetCountry() then
+ MStaticCountry = true
+ end
+ end
+ MStaticInclude = MStaticInclude and MStaticCountry
+ end
+
+ if self.Filter.StaticPrefixes then
+ local MStaticPrefix = false
+ for StaticPrefixId, StaticPrefix in pairs( self.Filter.StaticPrefixes ) do
+ self:T3( { "Prefix:", string.find( MStatic:GetName(), StaticPrefix, 1 ), StaticPrefix } )
+ if string.find( MStatic:GetName(), StaticPrefix, 1 ) then
+ MStaticPrefix = true
+ end
+ end
+ MStaticInclude = MStaticInclude and MStaticPrefix
+ end
+
+ self:T2( MStaticInclude )
+ return MStaticInclude
+ end
+
+
+ --- Retrieve the type names of the @{Static}s in the SET, delimited by an optional delimiter.
+ -- @param #SET_STATIC self
+ -- @param #string Delimiter (optional) The delimiter, which is default a comma.
+ -- @return #string The types of the @{Static}s delimited.
+ function SET_STATIC:GetTypeNames( Delimiter )
+
+ Delimiter = Delimiter or ", "
+ local TypeReport = REPORT:New()
+ local Types = {}
+
+ for StaticName, StaticData in pairs( self:GetSet() ) do
+
+ local Static = StaticData -- Wrapper.Static#STATIC
+ local StaticTypeName = Static:GetTypeName()
+
+ if not Types[StaticTypeName] then
+ Types[StaticTypeName] = StaticTypeName
+ TypeReport:Add( StaticTypeName )
+ end
+ end
+
+ return TypeReport:Text( Delimiter )
end
- return TypeReport:Text( Delimiter )
end
diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua
index 19f3cbc12..f4fc8e70a 100644
--- a/Moose Development/Moose/Core/Zone.lua
+++ b/Moose Development/Moose/Core/Zone.lua
@@ -50,6 +50,8 @@
-- ## Each zone has a name:
--
-- * @{#ZONE_BASE.GetName}(): Returns the name of the zone.
+-- * @{#ZONE_BASE.SetName}(): Sets the name of the zone.
+--
--
-- ## Each zone implements two polymorphic functions defined in @{Zone#ZONE_BASE}:
--
@@ -121,6 +123,17 @@ function ZONE_BASE:GetName()
return self.ZoneName
end
+
+--- Sets the name of the zone.
+-- @param #ZONE_BASE self
+-- @param #string ZoneName The name of the zone.
+-- @return #ZONE_BASE
+function ZONE_BASE:SetName( ZoneName )
+ self:F2()
+
+ self.ZoneName = ZoneName
+end
+
--- Returns if a Vec2 is within the zone.
-- @param #ZONE_BASE self
-- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 to test.
@@ -562,6 +575,160 @@ function ZONE_RADIUS:GetVec3( Height )
end
+--- Scan the zone
+-- @param #ZONE_RADIUS self
+-- @param Coalition
+function ZONE_RADIUS:Scan()
+
+ self.Coalitions = {}
+
+ local ZoneCoord = self:GetCoordinate()
+ local ZoneRadius = self:GetRadius()
+
+ self:E({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()})
+
+ local SphereSearch = {
+ id = world.VolumeType.SPHERE,
+ params = {
+ point = ZoneCoord:GetVec3(),
+ radius = ZoneRadius,
+ }
+ }
+
+ local function EvaluateZone( ZoneDCSUnit )
+ if ZoneDCSUnit:isExist() then
+ local CategoryDCSUnit = ZoneDCSUnit:getCategory()
+ if ( CategoryDCSUnit == Object.Category.UNIT and ZoneDCSUnit:isActive() ) or
+ CategoryDCSUnit == Object.Category.STATIC then
+ local CoalitionDCSUnit = ZoneDCSUnit:getCoalition()
+ self.Coalitions[CoalitionDCSUnit] = true
+ self:E( { Name = ZoneDCSUnit:getName(), Coalition = CoalitionDCSUnit } )
+ end
+ end
+ return true
+ end
+
+ world.searchObjects( { Object.Category.UNIT, Object.Category.STATIC }, SphereSearch, EvaluateZone )
+
+end
+
+
+function ZONE_RADIUS:CountCoalitions()
+
+ local Count = 0
+
+ for CoalitionID, Coalition in pairs( self.Coalitions ) do
+ Count = Count + 1
+ end
+ return Count
+end
+
+--- Is All in Zone of Coalition?
+-- @param #ZONE_RADIUS self
+-- @param Coalition
+-- @return #boolean
+function ZONE_RADIUS:IsAllInZoneOfCoalition( Coalition )
+
+ return self:CountCoalitions() == 1 and self.Coalitions[Coalition] == true
+end
+
+
+--- Is All in Zone of Other Coalition?
+-- @param #ZONE_RADIUS self
+-- @param Coalition
+-- @return #boolean
+function ZONE_RADIUS:IsAllInZoneOfOtherCoalition( Coalition )
+
+ self:E( { Coalitions = self.Coalitions, Count = self:CountCoalitions() } )
+ return self:CountCoalitions() == 1 and self.Coalitions[Coalition] == nil
+end
+
+
+--- Is Some in Zone of Coalition?
+-- @param #ZONE_RADIUS self
+-- @param Coalition
+-- @return #boolean
+function ZONE_RADIUS:IsSomeInZoneOfCoalition( Coalition )
+
+ return self:CountCoalitions() > 1 and self.Coalitions[Coalition] == true
+end
+
+
+--- Is None in Zone of Coalition?
+-- @param #ZONE_RADIUS self
+-- @param Coalition
+-- @return #boolean
+function ZONE_RADIUS:IsNoneInZoneOfCoalition( Coalition )
+
+ return self.Coalitions[Coalition] == nil
+end
+
+
+--- Is None in Zone?
+-- @param #ZONE_RADIUS self
+-- @return #boolean
+function ZONE_RADIUS:IsNoneInZone()
+
+ return self:CountCoalitions() == 0
+end
+
+
+--- Get the Zone Coalitions.
+-- Returns nil if there are none ot two coalitions in the zone!
+-- @param #ZONE_RADIUS self
+-- @return Coalitions
+function ZONE_RADIUS:GetCoalition()
+
+ local Count = 0
+ local ReturnCoalition = nil
+
+ for CoalitionID, Coalition in pairs( self.Coalitions ) do
+ Count = Count + 1
+ ReturnCoalition = CoalitionID
+ end
+
+ if Count ~= 1 then
+ ReturnCoalition = nil
+ end
+
+ return ReturnCoalition
+end
+
+
+
+--- Searches the zone
+-- @param #ZONE_RADIUS self
+-- @param EvaluateFunction
+function ZONE_RADIUS:SearchZone( EvaluateFunction )
+
+ local SearchZoneResult = true
+
+ local ZoneCoord = self:GetCoordinate()
+ local ZoneRadius = self:GetRadius()
+
+ self:E({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()})
+
+ local SphereSearch = {
+ id = world.VolumeType.SPHERE,
+ params = {
+ point = ZoneCoord:GetVec3(),
+ radius = ZoneRadius / 2,
+ }
+ }
+
+ local function EvaluateZone( ZoneDCSUnit )
+
+ env.info( ZoneDCSUnit:getName() )
+
+ local ZoneUnit = UNIT:Find( ZoneDCSUnit )
+
+ return EvaluateFunction( ZoneUnit )
+ end
+
+ world.searchObjects( Object.Category.UNIT, SphereSearch, EvaluateZone )
+
+end
+
--- Returns if a location is within the zone.
-- @param #ZONE_RADIUS self
-- @param Dcs.DCSTypes#Vec2 Vec2 The location to test.
diff --git a/Moose Development/Moose/Functional/Protect.lua b/Moose Development/Moose/Functional/Protect.lua
new file mode 100644
index 000000000..7b163ea2b
--- /dev/null
+++ b/Moose Development/Moose/Functional/Protect.lua
@@ -0,0 +1,305 @@
+--- **Functional** -- The PROTECT class handles the protection of objects, which can be zones, units, scenery.
+--
+-- ===
+--
+-- ### Author: **Sven Van de Velde (FlightControl)**
+-- ### Contributions: **MillerTime**
+--
+-- ====
+--
+-- @module Protect
+
+--- @type PROTECT.__ Methods which are not intended for mission designers, but which are used interally by the moose designer :-)
+-- @extends Core.Fsm#FSM
+
+--- @type PROTECT
+-- @extends #PROTECT.__
+
+--- # PROTECT, extends @{Base#BASE}
+--
+-- @field #PROTECT
+PROTECT = {
+ ClassName = "PROTECT",
+}
+
+--- Get the ProtectZone
+-- @param #PROTECT self
+-- @return Core.Zone#ZONE_BASE
+function PROTECT:GetProtectZone()
+ return self.ProtectZone
+end
+
+
+--- Get the name of the ProtectZone
+-- @param #PROTECT self
+-- @return #string
+function PROTECT:GetProtectZoneName()
+ return self.ProtectZone:GetName()
+end
+
+
+--- Set the owning coalition of the zone.
+-- @param #PROTECT self
+-- @param DCSCoalition.DCSCoalition#coalition Coalition
+function PROTECT:SetCoalition( Coalition )
+ self.Coalition = Coalition
+end
+
+
+--- Get the owning coalition of the zone.
+-- @param #PROTECT self
+-- @return DCSCoalition.DCSCoalition#coalition Coalition.
+function PROTECT:GetCoalition()
+ return self.Coalition
+end
+
+
+--- Get the owning coalition name of the zone.
+-- @param #PROTECT self
+-- @return #string Coalition name.
+function PROTECT:GetCoalitionName()
+
+ if self.Coalition == coalition.side.BLUE then
+ return "Blue"
+ end
+
+ if self.Coalition == coalition.side.RED then
+ return "Red"
+ end
+
+ if self.Coalition == coalition.side.NEUTRAL then
+ return "Neutral"
+ end
+
+ return ""
+end
+
+
+function PROTECT:IsGuarded()
+
+ local IsGuarded = self.ProtectZone:IsAllInZoneOfCoalition( self.Coalition )
+ self:E( { IsGuarded = IsGuarded } )
+ return IsGuarded
+end
+
+function PROTECT:IsCaptured()
+
+ local IsCaptured = self.ProtectZone:IsAllInZoneOfOtherCoalition( self.Coalition )
+ self:E( { IsCaptured = IsCaptured } )
+ return IsCaptured
+end
+
+
+function PROTECT:IsAttacked()
+
+ local IsAttacked = self.ProtectZone:IsSomeInZoneOfCoalition( self.Coalition )
+ self:E( { IsAttacked = IsAttacked } )
+ return IsAttacked
+end
+
+
+function PROTECT:IsEmpty()
+
+ local IsEmpty = self.ProtectZone:IsNoneInZone()
+ self:E( { IsEmpty = IsEmpty } )
+ return IsEmpty
+end
+
+
+--- Check if the units are still alive.
+-- @param #PROTECT self
+function PROTECT:AreProtectUnitsAlive()
+
+ local IsAlive = false
+
+ local UnitSet = self.ProtectUnitSet
+ UnitSet:Flush()
+ local UnitList = UnitSet:GetSet()
+
+ for UnitID, ProtectUnit in pairs( UnitList ) do
+ local IsUnitAlive = ProtectUnit:IsAlive()
+ if IsUnitAlive == true then
+ IsAlive = true
+ break
+ end
+ end
+
+ return IsAlive
+end
+
+--- Check if the statics are still alive.
+-- @param #PROTECT self
+function PROTECT:AreProtectStaticsAlive()
+
+ local IsAlive = false
+
+ local StaticSet = self.ProtectStaticSet
+ StaticSet:Flush()
+ local StaticList = StaticSet:GetSet()
+
+ for UnitID, ProtectStatic in pairs( StaticList ) do
+ local IsStaticAlive = ProtectStatic:IsAlive()
+ if IsStaticAlive == true then
+ IsAlive = true
+ break
+ end
+ end
+
+ return IsAlive
+end
+
+
+--- Check if there is a capture unit in the zone.
+-- @param #PROTECT self
+function PROTECT:IsCaptureUnitInZone()
+
+ local CaptureUnitSet = self.CaptureUnitSet
+ CaptureUnitSet:Flush()
+
+ local IsInZone = self.CaptureUnitSet:IsPartiallyInZone( self.ProtectZone )
+
+ self:E({IsInZone = IsInZone})
+
+ return IsInZone
+end
+
+--- Smoke.
+-- @param #PROTECT self
+-- @param #SMOKECOLOR.Color SmokeColor
+function PROTECT:Smoke( SmokeColor )
+
+ self.SmokeColor = SmokeColor
+end
+
+
+--- Flare.
+-- @param #PROTECT self
+-- @param #SMOKECOLOR.Color FlareColor
+function PROTECT:Flare( FlareColor )
+ self.ProtectZone:FlareZone( FlareColor, math.random( 1, 360 ) )
+end
+
+
+--- Mark.
+-- @param #PROTECT self
+function PROTECT:Mark()
+
+ local Coord = self.ProtectZone:GetCoordinate()
+ local ZoneName = self:GetProtectZoneName()
+ local State = self:GetState()
+
+ if self.MarkRed and self.MarkBlue then
+ self:E( { MarkRed = self.MarkRed, MarkBlue = self.MarkBlue } )
+ Coord:RemoveMark( self.MarkRed )
+ Coord:RemoveMark( self.MarkBlue )
+ end
+
+ if self.Coalition == coalition.side.BLUE then
+ self.MarkBlue = Coord:MarkToCoalitionBlue( "Guard Zone: " .. ZoneName .. "\nStatus: " .. State )
+ self.MarkRed = Coord:MarkToCoalitionRed( "Capture Zone: " .. ZoneName .. "\nStatus: " .. State )
+ else
+ self.MarkRed = Coord:MarkToCoalitionRed( "Guard Zone: " .. ZoneName .. "\nStatus: " .. State )
+ self.MarkBlue = Coord:MarkToCoalitionBlue( "Capture Zone: " .. ZoneName .. "\nStatus: " .. State )
+ end
+end
+
+
+--- Bound.
+-- @param #PROTECT self
+function PROTECT:onafterStart()
+
+ self:ScheduleRepeat( 5, 15, 0.1, nil, self.StatusCoalition, self )
+ self:ScheduleRepeat( 5, 15, 0.1, nil, self.StatusZone, self )
+ self:ScheduleRepeat( 10, 15, 0, nil, self.StatusSmoke, self )
+end
+
+--- Bound.
+-- @param #PROTECT self
+function PROTECT:onenterGuarded()
+
+
+ if self.Coalition == coalition.side.BLUE then
+ --elf.ProtectZone:BoundZone( 12, country.id.USA )
+ else
+ --self.ProtectZone:BoundZone( 12, country.id.RUSSIA )
+ end
+
+ self:Mark()
+
+end
+
+function PROTECT:onenterCaptured()
+
+ local NewCoalition = self.ProtectZone:GetCoalition()
+ self:E( { NewCoalition = NewCoalition } )
+ self:SetCoalition( NewCoalition )
+
+ self:Mark()
+end
+
+
+function PROTECT:onenterEmpty()
+
+ self:Mark()
+end
+
+
+function PROTECT:onenterAttacked()
+
+ self:Mark()
+end
+
+
+--- Check status Coalition ownership.
+-- @param #PROTECT self
+function PROTECT:StatusCoalition()
+
+ self:E( { State = self:GetState() } )
+
+ self.ProtectZone:Scan()
+
+ if self:IsGuarded() then
+ self:Guard()
+ else
+ if self:IsCaptured() then
+ self:Capture()
+ end
+ end
+end
+
+--- Check status Zone.
+-- @param #PROTECT self
+function PROTECT:StatusZone()
+
+ self:E( { State = self:GetState() } )
+
+ self.ProtectZone:Scan()
+
+ if self:IsAttacked() then
+ self:Attack()
+ else
+ if self:IsEmpty() then
+ self:Empty()
+ end
+ end
+end
+
+--- Check status Smoke.
+-- @param #PROTECT self
+function PROTECT:StatusSmoke()
+
+ local CurrentTime = timer.getTime()
+
+ if self.SmokeTime == nil or self.SmokeTime + 300 <= CurrentTime then
+ if self.SmokeColor then
+ self.ProtectZone:GetCoordinate():Smoke( self.SmokeColor )
+ --self.SmokeColor = nil
+ self.SmokeTime = CurrentTime
+ end
+ end
+end
+
+
+
+
+
diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua
index 40c434f33..8ec331a5f 100644
--- a/Moose Development/Moose/Functional/Scoring.lua
+++ b/Moose Development/Moose/Functional/Scoring.lua
@@ -710,6 +710,39 @@ function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score )
end
end
+--- Registers Scores the players completing a Mission Task.
+-- @param #SCORING self
+-- @param Tasking.Mission#MISSION Mission
+-- @param #string PlayerName
+-- @param #string Text
+-- @param #number Score
+function SCORING:_AddMissionGoalScore( Mission, PlayerName, Text, Score )
+
+ local MissionName = Mission:GetName()
+
+ self:E( { Mission:GetName(), PlayerName, Text, Score } )
+
+ -- PlayerName can be nil, if the Unit with the player crashed or due to another reason.
+ if PlayerName then
+ local PlayerData = self.Players[PlayerName]
+
+ if not PlayerData.Mission[MissionName] then
+ PlayerData.Mission[MissionName] = {}
+ PlayerData.Mission[MissionName].ScoreTask = 0
+ PlayerData.Mission[MissionName].ScoreMission = 0
+ end
+
+ self:T( PlayerName )
+ self:T( PlayerData.Mission[MissionName] )
+
+ PlayerData.Score = self.Players[PlayerName].Score + Score
+ PlayerData.Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score
+
+ MESSAGE:NewType( string.format( "%s%s: %s! Player %s receives %d score!", self.DisplayMessagePrefix, MissionName, Text, PlayerName, Score ), MESSAGE.Type.Information ):ToAll()
+
+ self:ScoreCSV( PlayerName, "", "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score )
+ end
+end
--- Registers Mission Scores for possible multiple players that contributed in the Mission.
-- @param #SCORING self
diff --git a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua
new file mode 100644
index 000000000..823cb284f
--- /dev/null
+++ b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua
@@ -0,0 +1,67 @@
+--- **Functional (wIP)** -- Base class that models processes to capture a Zone for a Coalition, guarded by another Coalition.
+--
+-- ====
+--
+-- ZONE_CAPTURE_COALITION models processes that have an objective with a defined achievement involving a Zone. Derived classes implement the ways how the achievements can be realized.
+--
+-- ====
+--
+-- ### Author: **Sven Van de Velde (FlightControl)**
+--
+-- ====
+--
+-- @module ZoneCaptureCoalition
+
+do -- ZoneGoal
+
+ --- @type ZONE_CAPTURE_COALITION
+ -- @extends Functional.ZoneGoalCoalition#ZONE_GOAL_COALITION
+
+
+ --- # ZONE_CAPTURE_COALITION class, extends @{ZoneGoalCoalition#ZONE_GOAL_COALITION}
+ --
+ -- ZONE_CAPTURE_COALITION models processes that have an objective with a defined achievement involving a Zone. Derived classes implement the ways how the achievements can be realized.
+ --
+ -- ## 1. ZONE_CAPTURE_COALITION constructor
+ --
+ -- * @{#ZONE_CAPTURE_COALITION.New}(): Creates a new ZONE_CAPTURE_COALITION object.
+ --
+ -- ## 2. ZONE_CAPTURE_COALITION is a finite state machine (FSM).
+ --
+ -- ### 2.1 ZONE_CAPTURE_COALITION States
+ --
+ -- ### 2.2 ZONE_CAPTURE_COALITION Events
+ --
+ -- @field #ZONE_CAPTURE_COALITION
+ ZONE_CAPTURE_COALITION = {
+ ClassName = "ZONE_CAPTURE_COALITION",
+ }
+
+ --- @field #table ZONE_CAPTURE_COALITION.States
+ ZONE_CAPTURE_COALITION.States = {}
+
+ --- ZONE_CAPTURE_COALITION Constructor.
+ -- @param #ZONE_CAPTURE_COALITION self
+ -- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved.
+ -- @param DCSCoalition.DCSCoalition#coalition Coalition The initial coalition owning the zone.
+ -- @return #ZONE_CAPTURE_COALITION
+ function ZONE_CAPTURE_COALITION:New( Zone, Coalition )
+
+ local self = BASE:Inherit( self, ZONE_GOAL_COALITION:New( Zone, Coalition ) ) -- #ZONE_CAPTURE_COALITION
+
+ self:F( { Zone = Zone, Coalition = Coalition } )
+
+ return self
+ end
+
+
+ --- @param #ZONE_CAPTURE_COALITION self
+ function ZONE_CAPTURE_COALITION:onenterCaptured()
+
+ self:GetParent( self, ZONE_CAPTURE_COALITION ).onenterCaptured( self )
+
+ self.Goal:Achieved()
+ end
+
+end
+
diff --git a/Moose Development/Moose/Functional/ZoneGoal.lua b/Moose Development/Moose/Functional/ZoneGoal.lua
new file mode 100644
index 000000000..992f26404
--- /dev/null
+++ b/Moose Development/Moose/Functional/ZoneGoal.lua
@@ -0,0 +1,177 @@
+--- **Functional (WIP)** -- Base class that models processes to achieve goals involving a Zone.
+--
+-- ====
+--
+-- ZONE_GOAL models processes that have a Goal with a defined achievement involving a Zone.
+-- Derived classes implement the ways how the achievements can be realized.
+--
+-- ====
+--
+-- ### Author: **Sven Van de Velde (FlightControl)**
+--
+-- ====
+--
+-- @module ZoneGoal
+
+do -- Zone
+
+ --- @type ZONE_GOAL
+ -- @extends Core.Fsm#FSM
+
+
+ --- # ZONE_GOAL class, extends @{Fsm#FSM}
+ --
+ -- ZONE_GOAL models processes that have a Goal with a defined achievement involving a Zone.
+ -- Derived classes implement the ways how the achievements can be realized.
+ --
+ -- ## 1. ZONE_GOAL constructor
+ --
+ -- * @{#ZONE_GOAL.New}(): Creates a new ZONE_GOAL object.
+ --
+ -- ## 2. ZONE_GOAL is a finite state machine (FSM).
+ --
+ -- ### 2.1 ZONE_GOAL States
+ --
+ -- * None: Initial State
+ --
+ -- ### 2.2 ZONE_GOAL Events
+ --
+ -- * DestroyedUnit: A @{Unit} is destroyed in the Zone. The event will only get triggered if the method @{#ZONE_GOAL.MonitorDestroyedUnits}() is used.
+ --
+ -- @field #ZONE_GOAL
+ ZONE_GOAL = {
+ ClassName = "ZONE_GOAL",
+ }
+
+ --- ZONE_GOAL Constructor.
+ -- @param #ZONE_GOAL self
+ -- @param Core.Zone#ZONE_BASE Zone A @{Zone} object with the goal to be achieved.
+ -- @return #ZONE_GOAL
+ function ZONE_GOAL:New( Zone )
+
+ local self = BASE:Inherit( self, FSM:New() ) -- #ZONE_GOAL
+ self:F( { Zone = Zone } )
+
+ self.Zone = Zone -- Core.Zone#ZONE_BASE
+ self.Goal = GOAL:New()
+
+ self.SmokeTime = nil
+
+ self:AddTransition( "*", "DestroyedUnit", "*" )
+
+ --- DestroyedUnit Handler OnAfter for ZONE_GOAL
+ -- @function [parent=#ZONE_GOAL] OnAfterDestroyedUnit
+ -- @param #ZONE_GOAL self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+ -- @param Wrapper.Unit#UNIT DestroyedUnit The destroyed unit.
+ -- @param #string PlayerName The name of the player.
+
+ return self
+ end
+
+ --- Get the Zone
+ -- @param #ZONE_GOAL self
+ -- @return Core.Zone#ZONE_BASE
+ function ZONE_GOAL:GetZone()
+ return self.Zone
+ end
+
+
+ --- Get the name of the ProtectZone
+ -- @param #ZONE_GOAL self
+ -- @return #string
+ function ZONE_GOAL:GetZoneName()
+ return self.Zone:GetName()
+ end
+
+
+ --- Smoke the center of theh zone.
+ -- @param #ZONE_GOAL self
+ -- @param #SMOKECOLOR.Color SmokeColor
+ function ZONE_GOAL:Smoke( SmokeColor )
+
+ self:F( { SmokeColor = SmokeColor} )
+
+ self.SmokeColor = SmokeColor
+ end
+
+
+ --- Flare the center of the zone.
+ -- @param #ZONE_GOAL self
+ -- @param #SMOKECOLOR.Color FlareColor
+ function ZONE_GOAL:Flare( FlareColor )
+ self.Zone:FlareZone( FlareColor, math.random( 1, 360 ) )
+ end
+
+
+ --- When started, check the Smoke and the Zone status.
+ -- @param #ZONE_GOAL self
+ function ZONE_GOAL:onafterGuard()
+
+ --self:GetParent( self ):onafterStart()
+
+ self:E("Guard")
+
+ --self:ScheduleRepeat( 15, 15, 0.1, nil, self.StatusZone, self )
+ if not self.SmokeScheduler then
+ self.SmokeScheduler = self:ScheduleRepeat( 1, 1, 0.1, nil, self.StatusSmoke, self )
+ end
+ end
+
+
+ --- Check status Smoke.
+ -- @param #ZONE_GOAL self
+ function ZONE_GOAL:StatusSmoke()
+
+ self:F({self.SmokeTime, self.SmokeColor})
+
+ local CurrentTime = timer.getTime()
+
+ if self.SmokeTime == nil or self.SmokeTime + 300 <= CurrentTime then
+ if self.SmokeColor then
+ self.Zone:GetCoordinate():Smoke( self.SmokeColor )
+ --self.SmokeColor = nil
+ self.SmokeTime = CurrentTime
+ end
+ end
+ end
+
+
+ --- @param #ZONE_GOAL self
+ -- @param Core.Event#EVENTDATA EventData
+ function ZONE_GOAL:__Destroyed( EventData )
+ self:E( { "EventDead", EventData } )
+
+ self:E( { EventData.IniUnit } )
+
+ local Vec3 = EventData.IniDCSUnit:getPosition().p
+ self:E( { Vec3 = Vec3 } )
+ local ZoneGoal = self:GetZone()
+ self:E({ZoneGoal})
+
+ if EventData.IniDCSUnit then
+ if ZoneGoal:IsVec3InZone(Vec3) then
+ local PlayerHits = _DATABASE.HITS[EventData.IniUnitName]
+ if PlayerHits then
+ for PlayerName, PlayerHit in pairs( PlayerHits.Players or {} ) do
+ self.Goal:AddPlayerContribution( PlayerName )
+ self:DestroyedUnit( EventData.IniUnitName, PlayerName )
+ end
+ end
+ end
+ end
+ end
+
+
+ --- Activate the event UnitDestroyed to be fired when a unit is destroyed in the zone.
+ -- @param #ZONE_GOAL self
+ function ZONE_GOAL:MonitorDestroyedUnits()
+
+ self:HandleEvent( EVENTS.Dead, self.__Destroyed )
+ self:HandleEvent( EVENTS.Crash, self.__Destroyed )
+
+ end
+
+end
diff --git a/Moose Development/Moose/Functional/ZoneGoalCoalition.lua b/Moose Development/Moose/Functional/ZoneGoalCoalition.lua
new file mode 100644
index 000000000..c5d157ee5
--- /dev/null
+++ b/Moose Development/Moose/Functional/ZoneGoalCoalition.lua
@@ -0,0 +1,452 @@
+--- **Functional (WIP)** -- Base class that models processes to achieve goals involving a Zone for a Coalition.
+--
+-- ====
+--
+-- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
+-- Derived classes implement the ways how the achievements can be realized.
+--
+-- ====
+--
+-- ### Author: **Sven Van de Velde (FlightControl)**
+--
+-- ====
+--
+-- @module ZoneGoalCoalition
+
+do -- ZoneGoal
+
+ --- @type ZONE_GOAL_COALITION
+ -- @extends Functional.ZoneGoal#ZONE_GOAL
+
+
+ --- # ZONE_GOAL_COALITION class, extends @{ZoneGoal#ZONE_GOAL}
+ --
+ -- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
+ -- Derived classes implement the ways how the achievements can be realized.
+ --
+ -- ## 1. ZONE_GOAL_COALITION constructor
+ --
+ -- * @{#ZONE_GOAL_COALITION.New}(): Creates a new ZONE_GOAL_COALITION object.
+ --
+ -- ## 2. ZONE_GOAL_COALITION is a finite state machine (FSM).
+ --
+ -- ### 2.1 ZONE_GOAL_COALITION States
+ --
+ -- * **Captured**: The Zone has been captured by an other coalition.
+ -- * **Attacked**: The Zone is currently intruded by an other coalition. There are units of the owning coalition and an other coalition in the Zone.
+ -- * **Guarded**: The Zone is guarded by the owning coalition. There is no other unit of an other coalition in the Zone.
+ -- * **Empty**: The Zone is empty. There is not valid unit in the Zone.
+ --
+ -- ### 2.2 ZONE_GOAL_COALITION Events
+ --
+ -- * **Capture**: The Zone has been captured by an other coalition.
+ -- * **Attack**: The Zone is currently intruded by an other coalition. There are units of the owning coalition and an other coalition in the Zone.
+ -- * **Guard**: The Zone is guarded by the owning coalition. There is no other unit of an other coalition in the Zone.
+ -- * **Empty**: The Zone is empty. There is not valid unit in the Zone.
+ --
+ -- ### 2.3 ZONE_GOAL_COALITION State Machine
+ --
+ -- @field #ZONE_GOAL_COALITION
+ ZONE_GOAL_COALITION = {
+ ClassName = "ZONE_GOAL_COALITION",
+ }
+
+ --- @field #table ZONE_GOAL_COALITION.States
+ ZONE_GOAL_COALITION.States = {}
+
+ --- ZONE_GOAL_COALITION Constructor.
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved.
+ -- @param DCSCoalition.DCSCoalition#coalition Coalition The initial coalition owning the zone.
+ -- @return #ZONE_GOAL_COALITION
+ function ZONE_GOAL_COALITION:New( Zone, Coalition )
+
+ local self = BASE:Inherit( self, ZONE_GOAL:New( Zone ) ) -- #ZONE_GOAL_COALITION
+ self:F( { Zone = Zone, Coalition = Coalition } )
+
+ self:SetCoalition( Coalition )
+
+ do
+
+ --- Captured State Handler OnLeave for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] OnLeaveCaptured
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+ -- @return #boolean
+
+ --- Captured State Handler OnEnter for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] OnEnterCaptured
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+
+ end
+
+
+ do
+
+ --- Attacked State Handler OnLeave for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] OnLeaveAttacked
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+ -- @return #boolean
+
+ --- Attacked State Handler OnEnter for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] OnEnterAttacked
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+
+ end
+
+ do
+
+ --- Guarded State Handler OnLeave for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] OnLeaveGuarded
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+ -- @return #boolean
+
+ --- Guarded State Handler OnEnter for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] OnEnterGuarded
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+
+ end
+
+
+ do
+
+ --- Empty State Handler OnLeave for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] OnLeaveEmpty
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+ -- @return #boolean
+
+ --- Empty State Handler OnEnter for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] OnEnterEmpty
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+
+ end
+
+ self:AddTransition( "*", "Guard", "Guarded" )
+
+ --- Guard Handler OnBefore for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] OnBeforeGuard
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+ -- @return #boolean
+
+ --- Guard Handler OnAfter for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] OnAfterGuard
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+
+ --- Guard Trigger for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] Guard
+ -- @param #ZONE_GOAL_COALITION self
+
+ --- Guard Asynchronous Trigger for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] __Guard
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #number Delay
+
+ self:AddTransition( "*", "Empty", "Empty" )
+
+ --- Empty Handler OnBefore for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] OnBeforeEmpty
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+ -- @return #boolean
+
+ --- Empty Handler OnAfter for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] OnAfterEmpty
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+
+ --- Empty Trigger for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] Empty
+ -- @param #ZONE_GOAL_COALITION self
+
+ --- Empty Asynchronous Trigger for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] __Empty
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #number Delay
+
+
+ self:AddTransition( { "Guarded", "Empty" }, "Attack", "Attacked" )
+
+ --- Attack Handler OnBefore for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] OnBeforeAttack
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+ -- @return #boolean
+
+ --- Attack Handler OnAfter for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] OnAfterAttack
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+
+ --- Attack Trigger for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] Attack
+ -- @param #ZONE_GOAL_COALITION self
+
+ --- Attack Asynchronous Trigger for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] __Attack
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #number Delay
+
+ self:AddTransition( { "Guarded", "Attacked", "Empty" }, "Capture", "Captured" )
+
+ --- Capture Handler OnBefore for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] OnBeforeCapture
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+ -- @return #boolean
+
+ --- Capture Handler OnAfter for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] OnAfterCapture
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #string From
+ -- @param #string Event
+ -- @param #string To
+
+ --- Capture Trigger for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] Capture
+ -- @param #ZONE_GOAL_COALITION self
+
+ --- Capture Asynchronous Trigger for ZONE_GOAL_COALITION
+ -- @function [parent=#ZONE_GOAL_COALITION] __Capture
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param #number Delay
+
+ return self
+ end
+
+
+ --- Set the owning coalition of the zone.
+ -- @param #ZONE_GOAL_COALITION self
+ -- @param DCSCoalition.DCSCoalition#coalition Coalition
+ function ZONE_GOAL_COALITION:SetCoalition( Coalition )
+ self.Coalition = Coalition
+ end
+
+
+ --- Get the owning coalition of the zone.
+ -- @param #ZONE_GOAL_COALITION self
+ -- @return DCSCoalition.DCSCoalition#coalition Coalition.
+ function ZONE_GOAL_COALITION:GetCoalition()
+ return self.Coalition
+ end
+
+
+ --- Get the owning coalition name of the zone.
+ -- @param #ZONE_GOAL_COALITION self
+ -- @return #string Coalition name.
+ function ZONE_GOAL_COALITION:GetCoalitionName()
+
+ if self.Coalition == coalition.side.BLUE then
+ return "Blue"
+ end
+
+ if self.Coalition == coalition.side.RED then
+ return "Red"
+ end
+
+ if self.Coalition == coalition.side.NEUTRAL then
+ return "Neutral"
+ end
+
+ return ""
+ end
+
+
+ function ZONE_GOAL_COALITION:IsGuarded()
+
+ local IsGuarded = self.Zone:IsAllInZoneOfCoalition( self.Coalition )
+ self:E( { IsGuarded = IsGuarded } )
+ return IsGuarded
+ end
+
+
+ function ZONE_GOAL_COALITION:IsEmpty()
+
+ local IsEmpty = self.Zone:IsNoneInZone()
+ self:E( { IsEmpty = IsEmpty } )
+ return IsEmpty
+ end
+
+
+ function ZONE_GOAL_COALITION:IsCaptured()
+
+ local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition )
+ self:E( { IsCaptured = IsCaptured } )
+ return IsCaptured
+ end
+
+
+ function ZONE_GOAL_COALITION:IsAttacked()
+
+ local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
+ self:E( { IsAttacked = IsAttacked } )
+ return IsAttacked
+ end
+
+
+
+ --- Mark.
+ -- @param #ZONE_GOAL_COALITION self
+ function ZONE_GOAL_COALITION:Mark()
+
+ local Coord = self.Zone:GetCoordinate()
+ local ZoneName = self:GetZoneName()
+ local State = self:GetState()
+
+ if self.MarkRed and self.MarkBlue then
+ self:E( { MarkRed = self.MarkRed, MarkBlue = self.MarkBlue } )
+ Coord:RemoveMark( self.MarkRed )
+ Coord:RemoveMark( self.MarkBlue )
+ end
+
+ if self.Coalition == coalition.side.BLUE then
+ self.MarkBlue = Coord:MarkToCoalitionBlue( "Guard Zone: " .. ZoneName .. "\nStatus: " .. State )
+ self.MarkRed = Coord:MarkToCoalitionRed( "Capture Zone: " .. ZoneName .. "\nStatus: " .. State )
+ else
+ self.MarkRed = Coord:MarkToCoalitionRed( "Guard Zone: " .. ZoneName .. "\nStatus: " .. State )
+ self.MarkBlue = Coord:MarkToCoalitionBlue( "Capture Zone: " .. ZoneName .. "\nStatus: " .. State )
+ end
+ end
+
+ --- Bound.
+ -- @param #ZONE_GOAL_COALITION self
+ function ZONE_GOAL_COALITION:onenterGuarded()
+
+ --self:GetParent( self ):onenterGuarded()
+
+ if self.Coalition == coalition.side.BLUE then
+ --elf.ProtectZone:BoundZone( 12, country.id.USA )
+ else
+ --self.ProtectZone:BoundZone( 12, country.id.RUSSIA )
+ end
+
+ self:Mark()
+
+ end
+
+ function ZONE_GOAL_COALITION:onenterCaptured()
+
+ --self:GetParent( self ):onenterCaptured()
+
+ local NewCoalition = self.Zone:GetCoalition()
+ self:E( { NewCoalition = NewCoalition } )
+ self:SetCoalition( NewCoalition )
+
+ self:Mark()
+ end
+
+
+ function ZONE_GOAL_COALITION:onenterEmpty()
+
+ --self:GetParent( self ):onenterEmpty()
+
+ self:Mark()
+ end
+
+
+ function ZONE_GOAL_COALITION:onenterAttacked()
+
+ --self:GetParent( self ):onenterAttacked()
+
+ self:Mark()
+ end
+
+
+ --- When started, check the Coalition status.
+ -- @param #ZONE_GOAL_COALITION self
+ function ZONE_GOAL_COALITION:onafterGuard()
+
+ --self:E({BASE:GetParent( self )})
+ --BASE:GetParent( self ).onafterGuard( self )
+
+ if not self.SmokeScheduler then
+ self.SmokeScheduler = self:ScheduleRepeat( 1, 1, 0.1, nil, self.StatusSmoke, self )
+ end
+ if not self.ScheduleStatusZone then
+ self.ScheduleStatusZone = self:ScheduleRepeat( 15, 15, 0.1, nil, self.StatusZone, self )
+ end
+ end
+
+
+ function ZONE_GOAL_COALITION:IsCaptured()
+
+ local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition )
+ self:E( { IsCaptured = IsCaptured } )
+ return IsCaptured
+ end
+
+
+ function ZONE_GOAL_COALITION:IsAttacked()
+
+ local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
+ self:E( { IsAttacked = IsAttacked } )
+ return IsAttacked
+ end
+
+ --- Check status Coalition ownership.
+ -- @param #ZONE_GOAL_COALITION self
+ function ZONE_GOAL_COALITION:StatusZone()
+
+ local State = self:GetState()
+ self:E( { State = self:GetState() } )
+
+ self.Zone:Scan()
+
+ if State ~= "Guarded" and self:IsGuarded() then
+ self:Guard()
+ end
+
+ if State ~= "Empty" and self:IsEmpty() then
+ self:Empty()
+ end
+
+ if State ~= "Attacked" and self:IsAttacked() then
+ self:Attack()
+ end
+
+ if State ~= "Captured" and self:IsCaptured() then
+ self:Capture()
+ end
+
+ end
+
+end
+
diff --git a/Moose Development/Moose/Tasking/Task.lua b/Moose Development/Moose/Tasking/Task.lua
index e6a791794..9bd94eaff 100644
--- a/Moose Development/Moose/Tasking/Task.lua
+++ b/Moose Development/Moose/Tasking/Task.lua
@@ -175,28 +175,34 @@ function TASK:New( Mission, SetGroupAssign, TaskName, TaskType, TaskBriefing )
--- Goal Handler OnBefore for TASK
-- @function [parent=#TASK] OnBeforeGoal
-- @param #TASK self
- -- @param Wrapper.Controllable#CONTROLLABLE Controllable
-- @param #string From
-- @param #string Event
-- @param #string To
+ -- @param Wrapper.Unit#UNIT PlayerUnit The @{Unit} of the player.
+ -- @param #string PlayerName The name of the player.
-- @return #boolean
--- Goal Handler OnAfter for TASK
-- @function [parent=#TASK] OnAfterGoal
-- @param #TASK self
- -- @param Wrapper.Controllable#CONTROLLABLE Controllable
-- @param #string From
-- @param #string Event
-- @param #string To
+ -- @param Wrapper.Unit#UNIT PlayerUnit The @{Unit} of the player.
+ -- @param #string PlayerName The name of the player.
--- Goal Trigger for TASK
-- @function [parent=#TASK] Goal
-- @param #TASK self
+ -- @param Wrapper.Unit#UNIT PlayerUnit The @{Unit} of the player.
+ -- @param #string PlayerName The name of the player.
--- Goal Asynchronous Trigger for TASK
-- @function [parent=#TASK] __Goal
-- @param #TASK self
-- @param #number Delay
+ -- @param Wrapper.Unit#UNIT PlayerUnit The @{Unit} of the player.
+ -- @param #string PlayerName The name of the player.
@@ -872,42 +878,20 @@ function TASK:MenuMarkToGroup( TaskGroup )
-- List the name of the Task.
local Name = self:GetName()
- Report:Add( Name .. ": " .. self:GetTaskBriefing() )
+ Report:Add( "Task " .. Name .. ": " .. self:GetTaskBriefing() .. "\n" )
for TaskInfoID, TaskInfo in pairs( self.TaskInfo, function( t, a, b ) return t[a].TaskInfoOrder < t[b].TaskInfoOrder end ) do
-
- local TaskInfoIDText = "" --string.format( "%s: ", TaskInfoID )
- if type( TaskInfo.TaskInfoText ) == "string" then
- if TaskInfoID == "Targets" then
- else
- Report:Add( TaskInfoIDText .. TaskInfo.TaskInfoText )
- end
- elseif type( TaskInfo ) == "table" then
- if TaskInfoID == "Coordinates" then
- --local ToCoordinate = TaskInfo.TaskInfoText -- Core.Point#COORDINATE
- --Report:Add( TaskInfoIDText .. ToCoordinate:ToString() )
- else
- end
+ local ReportText = self:GetMarkInfo( TaskInfoID, TaskInfo )
+ if ReportText then
+ Report:Add( ReportText )
end
-
end
- local Coordinate = self:GetInfo( "Coordinates" ) -- Core.Point#COORDINATE
-
- local Velocity = self.TargetSetUnit:GetVelocity()
- local Heading = self.TargetSetUnit:GetHeading()
-
- Coordinate:SetHeading( Heading )
- Coordinate:SetVelocity( Velocity )
-
- Report:Add( "Targets are" .. Coordinate:GetMovingText() .. "." )
-
+ local TargetCoordinate = self:GetInfo( "Coordinate" ) -- Core.Point#COORDINATE
local MarkText = Report:Text( ", " )
-
- self:F( { Coordinate = Coordinate, MarkText = MarkText } )
-
- Coordinate:MarkToGroup( MarkText, TaskGroup )
+ self:F( { Coordinate = TargetCoordinate, MarkText = MarkText } )
+ TargetCoordinate:MarkToGroup( MarkText, TaskGroup )
--Coordinate:MarkToAll( Briefing )
end
@@ -1274,7 +1258,7 @@ function TASK:onenterAssigned( From, Event, To, PlayerUnit, PlayerName )
self:GetMission():__Start( 1 )
-- When the task is assigned, the task goal needs to be checked of the derived classes.
- self:__Goal( -10 ) -- Polymorphic
+ self:__Goal( -10, PlayerUnit, PlayerName ) -- Polymorphic
self:SetMenu()
end
@@ -1443,14 +1427,14 @@ function TASK:ReportSummary( ReportGroup )
local Report = REPORT:New()
-- List the name of the Task.
- Report:Add( self:GetName() )
+ Report:Add( "Task " .. self:GetName() )
-- Determine the status of the Task.
Report:Add( "State: <" .. self:GetState() .. ">" )
- if self.TaskInfo["Coordinates"] then
+ if self.TaskInfo["Coordinate"] then
local TaskInfoIDText = string.format( "%s: ", "Coordinate" )
- local TaskCoord = self.TaskInfo["Coordinates"].TaskInfoText -- Core.Point#COORDINATE
+ local TaskCoord = self.TaskInfo["Coordinate"].TaskInfoText -- Core.Point#COORDINATE
Report:Add( TaskInfoIDText .. TaskCoord:ToString( ReportGroup, nil, self ) )
end
@@ -1480,7 +1464,7 @@ function TASK:ReportOverview( ReportGroup )
if Line ~= 0 then
Report:AddIndent( LineReport:Text( ", " ) )
else
- Report:Add( TaskName .. ", " .. LineReport:Text( ", " ) )
+ Report:Add( "Task " .. TaskName .. ", " .. LineReport:Text( ", " ) )
end
LineReport = REPORT:New()
Line = math.floor( TaskInfo.TaskInfoOrder / 10 )
@@ -1491,7 +1475,7 @@ function TASK:ReportOverview( ReportGroup )
if type( TaskInfo.TaskInfoText ) == "string" then
LineReport:Add( TaskInfoIDText .. TaskInfo.TaskInfoText )
elseif type(TaskInfo) == "table" then
- if TaskInfoID == "Coordinates" then
+ if TaskInfoID == "Coordinate" then
local ToCoordinate = TaskInfo.TaskInfoText -- Core.Point#COORDINATE
--Report:Add( TaskInfoIDText )
LineReport:Add( TaskInfoIDText .. ToCoordinate:ToString( ReportGroup, nil, self ) )
@@ -1565,7 +1549,7 @@ function TASK:ReportDetails( ReportGroup )
-- Determine the status of the Task.
local Status = "<" .. self:GetState() .. ">"
- Report:Add( "Task: " .. Name .. " - " .. Status .. " - Detailed Report" )
+ Report:Add( "Task " .. Name .. " - " .. Status .. " - Detailed Report" )
-- Loop each Unit active in the Task, and find Player Names.
local PlayerNames = self:GetPlayerNames()
@@ -1583,31 +1567,13 @@ function TASK:ReportDetails( ReportGroup )
for TaskInfoID, TaskInfo in pairs( self.TaskInfo, function( t, a, b ) return t[a].TaskInfoOrder < t[b].TaskInfoOrder end ) do
- local TaskInfoIDText = string.format( " - %s: ", TaskInfoID )
-
- if type( TaskInfo.TaskInfoText ) == "string" then
- Report:Add( TaskInfoIDText .. TaskInfo.TaskInfoText )
- elseif type(TaskInfo) == "table" then
- if TaskInfoID == "Coordinates" then
- local FromCoordinate = ReportGroup:GetUnit(1):GetCoordinate()
- local ToCoordinate = TaskInfo.TaskInfoText -- Core.Point#COORDINATE
- Report:Add( TaskInfoIDText .. ToCoordinate:ToString( ReportGroup:GetUnit(1), nil, self ) )
- else
- end
+ local ReportText = self:GetReportDetail( ReportGroup, TaskInfoID, TaskInfo )
+ if ReportText then
+ Report:Add( ReportText )
end
end
- local Coordinate = self:GetInfo( "Coordinates" ) -- Core.Point#COORDINATE
-
- local Velocity = self.TargetSetUnit:GetVelocity()
- local Heading = self.TargetSetUnit:GetHeading()
-
- Coordinate:SetHeading( Heading )
- Coordinate:SetVelocity( Velocity )
-
- Report:Add( "Targets are" .. Coordinate:GetMovingText() .. "." )
-
return Report:Text()
end
diff --git a/Moose Development/Moose/Tasking/TaskZoneCapture.lua b/Moose Development/Moose/Tasking/TaskZoneCapture.lua
new file mode 100644
index 000000000..b23f35988
--- /dev/null
+++ b/Moose Development/Moose/Tasking/TaskZoneCapture.lua
@@ -0,0 +1,284 @@
+--- **Tasking** - The TASK_Protect models tasks for players to protect or capture specific zones.
+--
+-- ====
+--
+-- ### Author: **Sven Van de Velde (FlightControl)**
+--
+-- ### Contributions: MillerTime
+--
+-- ====
+--
+-- @module TaskZoneCapture
+
+do -- TASK_ZONE_GOAL
+
+ --- The TASK_ZONE_GOAL class
+ -- @type TASK_ZONE_GOAL
+ -- @field Core.ZoneGoal#ZONE_GOAL ZoneGoal
+ -- @extends Tasking.Task#TASK
+
+ --- # TASK_ZONE_GOAL class, extends @{Task#TASK}
+ --
+ -- The TASK_ZONE_GOAL class defines the task to protect or capture a protection zone.
+ -- The TASK_ZONE_GOAL is implemented using a @{Fsm#FSM_TASK}, and has the following statuses:
+ --
+ -- * **None**: Start of the process
+ -- * **Planned**: The A2G task is planned.
+ -- * **Assigned**: The A2G task is assigned to a @{Group#GROUP}.
+ -- * **Success**: The A2G task is successfully completed.
+ -- * **Failed**: The A2G task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ.
+ --
+ -- ## Set the scoring of achievements in an A2G attack.
+ --
+ -- Scoring or penalties can be given in the following circumstances:
+ --
+ -- * @{#TASK_ZONE_GOAL.SetScoreOnDestroy}(): Set a score when a target in scope of the A2G attack, has been destroyed.
+ -- * @{#TASK_ZONE_GOAL.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2G attack, have been destroyed.
+ -- * @{#TASK_ZONE_GOAL.SetPenaltyOnFailed}(): Set a penalty when the A2G attack has failed.
+ --
+ -- @field #TASK_ZONE_GOAL
+ TASK_ZONE_GOAL = {
+ ClassName = "TASK_ZONE_GOAL",
+ }
+
+ --- Instantiates a new TASK_ZONE_GOAL.
+ -- @param #TASK_ZONE_GOAL self
+ -- @param Tasking.Mission#MISSION Mission
+ -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned.
+ -- @param #string TaskName The name of the Task.
+ -- @param Core.ZoneGoal#ZONE_GOAL ZoneGoal
+ -- @return #TASK_ZONE_GOAL self
+ function TASK_ZONE_GOAL:New( Mission, SetGroup, TaskName, ZoneGoal, TaskType, TaskBriefing )
+ local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType, TaskBriefing ) ) -- #TASK_ZONE_GOAL
+ self:F()
+
+ self.ZoneGoal = ZoneGoal
+ self.TaskType = TaskType
+
+ local Fsm = self:GetUnitProcess()
+
+
+ Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "StartMonitoring", Rejected = "Reject" } )
+
+ Fsm:AddTransition( "Assigned", "StartMonitoring", "Monitoring" )
+ Fsm:AddTransition( "Monitoring", "Monitor", "Monitoring", {} )
+ Fsm:AddTransition( "Monitoring", "RouteTo", "Monitoring" )
+ Fsm:AddProcess( "Monitoring", "RouteToZone", ACT_ROUTE_ZONE:New(), {} )
+
+ --Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
+ --Fsm:AddTransition( "Accounted", "Success", "Success" )
+ Fsm:AddTransition( "Rejected", "Reject", "Aborted" )
+ Fsm:AddTransition( "Failed", "Fail", "Failed" )
+
+ self:SetTargetZone( self.ZoneGoal:GetZone() )
+
+ --- Test
+ -- @param #FSM_PROCESS self
+ -- @param Wrapper.Unit#UNIT TaskUnit
+ -- @param Tasking.Task#TASK_ZONE_GOAL Task
+ function Fsm:onafterStartMonitoring( TaskUnit, Task )
+ self:E( { self } )
+ self:__Monitor( 0.1 )
+ self:__RouteTo( 0.1 )
+ end
+
+ --- Monitor Loop
+ -- @param #FSM_PROCESS self
+ -- @param Wrapper.Unit#UNIT TaskUnit
+ -- @param Tasking.Task#TASK_ZONE_GOAL Task
+ function Fsm:onafterMonitor( TaskUnit, Task )
+ self:E( { self } )
+ self:__Monitor( 15 )
+ end
+
+ --- Test
+ -- @param #FSM_PROCESS self
+ -- @param Wrapper.Unit#UNIT TaskUnit
+ -- @param Tasking.Task_A2G#TASK_ZONE_GOAL Task
+ function Fsm:onafterRouteTo( TaskUnit, Task )
+ self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
+ -- Determine the first Unit from the self.TargetSetUnit
+
+ if Task:GetTargetZone( TaskUnit ) then
+ self:__RouteTo( 0.1 )
+ end
+ end
+
+ return self
+
+ end
+
+ --- @param #TASK_ZONE_GOAL self
+ -- @param Core.ZoneGoal#ZONE_GOAL ZoneGoal The ZoneGoal Engine.
+ function TASK_ZONE_GOAL:SetProtect( ZoneGoal )
+
+ self.ZoneGoal = ZoneGoal -- Core.ZoneGoal#ZONE_GOAL
+ end
+
+
+
+ --- @param #TASK_ZONE_GOAL self
+ function TASK_ZONE_GOAL:GetPlannedMenuText()
+ return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.ZoneGoal:GetZoneName() .. " )"
+ end
+
+
+ --- @param #TASK_ZONE_GOAL self
+ -- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map.
+ -- @param Wrapper.Unit#UNIT TaskUnit
+ function TASK_ZONE_GOAL:SetTargetZone( TargetZone, TaskUnit )
+
+ local ProcessUnit = self:GetUnitProcess( TaskUnit )
+
+ local ActRouteZone = ProcessUnit:GetProcess( "Monitoring", "RouteToZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
+ ActRouteZone:SetZone( TargetZone )
+ end
+
+
+ --- @param #TASK_ZONE_GOAL self
+ -- @param Wrapper.Unit#UNIT TaskUnit
+ -- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map.
+ function TASK_ZONE_GOAL:GetTargetZone( TaskUnit )
+
+ local ProcessUnit = self:GetUnitProcess( TaskUnit )
+
+ local ActRouteZone = ProcessUnit:GetProcess( "Monitoring", "RouteToZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
+ return ActRouteZone:GetZone()
+ end
+
+ function TASK_ZONE_GOAL:SetGoalTotal( GoalTotal )
+
+ self.GoalTotal = GoalTotal
+ end
+
+ function TASK_ZONE_GOAL:GetGoalTotal()
+
+ return self.GoalTotal
+ end
+
+ function TASK_ZONE_GOAL:GetMarkInfo( TaskInfoID, TaskInfo )
+
+ if type( TaskInfo.TaskInfoText ) == "string" then
+ return string.format( "%s: %s", TaskInfoID, TaskInfo.TaskInfoText )
+ elseif type( TaskInfo ) == "table" then
+ if TaskInfoID == "Coordinate" then
+ end
+ end
+
+ return nil
+ end
+
+ function TASK_ZONE_GOAL:GetReportDetail( ReportGroup, TaskInfoID, TaskInfo )
+
+ if type( TaskInfo.TaskInfoText ) == "string" then
+ return string.format( " - %s: %s", TaskInfoID, TaskInfo.TaskInfoText )
+ elseif type(TaskInfo) == "table" then
+ if TaskInfoID == "Coordinate" then
+ local FromCoordinate = ReportGroup:GetUnit(1):GetCoordinate()
+ local ToCoordinate = TaskInfo.TaskInfoText -- Core.Point#COORDINATE
+ return string.format( " - %s: %s", TaskInfoID, ToCoordinate:ToString( ReportGroup:GetUnit( 1 ), nil, self ) )
+ else
+ end
+ end
+ end
+
+
+end
+
+
+do -- TASK_ZONE_CAPTURE
+
+ --- The TASK_ZONE_CAPTURE class
+ -- @type TASK_ZONE_CAPTURE
+ -- @field Core.ZoneGoalCoalition#ZONE_GOAL_COALITION ZoneGoal
+ -- @extends #TASK_ZONE_GOAL
+
+ --- # TASK_ZONE_CAPTURE class, extends @{TaskZoneGoal#TASK_ZONE_GOAL}
+ --
+ -- The TASK_ZONE_CAPTURE class defines an Suppression or Extermination of Air Defenses task for a human player to be executed.
+ -- These tasks are important to be executed as they will help to achieve air superiority at the vicinity.
+ --
+ -- The TASK_ZONE_CAPTURE is used by the @{Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create SEAD tasks
+ -- based on detected enemy ground targets.
+ --
+ -- @field #TASK_ZONE_CAPTURE
+ TASK_ZONE_CAPTURE = {
+ ClassName = "TASK_ZONE_CAPTURE",
+ }
+
+ --- Instantiates a new TASK_ZONE_CAPTURE.
+ -- @param #TASK_ZONE_CAPTURE self
+ -- @param Tasking.Mission#MISSION Mission
+ -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned.
+ -- @param #string TaskName The name of the Task.
+ -- @param Core.ZoneGoalCoalition#ZONE_GOAL_COALITION ZoneGoalCoalition
+ -- @param #string TaskBriefing The briefing of the task.
+ -- @return #TASK_ZONE_CAPTURE self
+ function TASK_ZONE_CAPTURE:New( Mission, SetGroup, TaskName, ZoneGoalCoalition, TaskBriefing)
+ local self = BASE:Inherit( self, TASK_ZONE_GOAL:New( Mission, SetGroup, TaskName, ZoneGoalCoalition, "CAPTURE", TaskBriefing ) ) -- #TASK_ZONE_CAPTURE
+ self:F()
+
+ Mission:AddTask( self )
+
+ self.TaskCoalition = ZoneGoalCoalition:GetCoalition()
+ self.TaskCoalitionName = ZoneGoalCoalition:GetCoalitionName()
+ self.TaskZoneName = ZoneGoalCoalition:GetZoneName()
+
+ ZoneGoalCoalition:MonitorDestroyedUnits()
+
+ self:SetBriefing(
+ TaskBriefing or
+ "Capture Zone " .. self.TaskZoneName
+ )
+
+ self:UpdateTaskInfo()
+
+ return self
+ end
+
+ --- Instantiates a new TASK_ZONE_CAPTURE.
+ -- @param #TASK_ZONE_CAPTURE self
+ function TASK_ZONE_CAPTURE:UpdateTaskInfo()
+
+
+ local ZoneCoordinate = self.ZoneGoal:GetZone():GetCoordinate()
+ self:SetInfo( "Coordinate", ZoneCoordinate, 0 )
+ self:SetInfo( "Zone Name", self.ZoneGoal:GetZoneName(), 10 )
+ self:SetInfo( "Zone Coalition", self.ZoneGoal:GetCoalitionName(), 11 )
+ end
+
+ function TASK_ZONE_CAPTURE:ReportOrder( ReportGroup )
+ local Coordinate = self:GetInfo( "Coordinate" )
+ --local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
+ local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
+
+ return Distance
+ end
+
+
+ --- @param #TASK_ZONE_CAPTURE self
+ -- @param Wrapper.Unit#UNIT TaskUnit
+ function TASK_ZONE_CAPTURE:OnAfterGoal( From, Event, To, PlayerUnit, PlayerName )
+
+ self:E( { PlayerUnit = PlayerUnit } )
+
+ if self.ZoneGoal then
+ if self.ZoneGoal.Goal:IsAchieved() then
+ self:Success()
+ local TotalContributions = self.ZoneGoal.Goal:GetTotalContributions()
+ local PlayerContributions = self.ZoneGoal.Goal:GetPlayerContributions()
+ self:E( { TotalContributions = TotalContributions, PlayerContributions = PlayerContributions } )
+ for PlayerName, PlayerContribution in pairs( PlayerContributions ) do
+ local Scoring = self:GetScoring()
+ if Scoring then
+ Scoring:_AddMissionGoalScore( self.Mission, PlayerName, "Zone " .. self.ZoneGoal:GetZoneName() .." captured", PlayerContribution * 200 / TotalContributions )
+ end
+ end
+ end
+ end
+
+ self:__Goal( -10, PlayerUnit, PlayerName )
+ end
+
+end
+
diff --git a/Moose Development/Moose/Tasking/Task_A2A.lua b/Moose Development/Moose/Tasking/Task_A2A.lua
index a40424276..44687a2fb 100644
--- a/Moose Development/Moose/Tasking/Task_A2A.lua
+++ b/Moose Development/Moose/Tasking/Task_A2A.lua
@@ -277,6 +277,35 @@ do -- TASK_A2A
return self.GoalTotal
end
+ function TASK_A2A:GetMarkInfo( TaskInfoID, TaskInfo )
+
+ if type( TaskInfo.TaskInfoText ) == "string" then
+ if TaskInfoID == "Targets" then
+ else
+ return string.format( "%s: %s", TaskInfoID, TaskInfo.TaskInfoText )
+ end
+ elseif type( TaskInfo ) == "table" then
+ if TaskInfoID == "Coordinate" then
+ end
+ end
+
+ return nil
+ end
+
+ function TASK_A2A:GetReportDetail( ReportGroup, TaskInfoID, TaskInfo )
+
+ if type( TaskInfo.TaskInfoText ) == "string" then
+ return string.format( "%s: %s", TaskInfoID, TaskInfo.TaskInfoText )
+ elseif type(TaskInfo) == "table" then
+ if TaskInfoID == "Coordinate" then
+ local FromCoordinate = ReportGroup:GetUnit(1):GetCoordinate()
+ local ToCoordinate = TaskInfo.TaskInfoText -- Core.Point#COORDINATE
+ return string.format( " - %s: %s", TaskInfoID, ToCoordinate:ToString( ReportGroup:GetUnit(1), nil, self ) )
+ else
+ end
+ end
+ end
+
end
@@ -336,7 +365,7 @@ do -- TASK_A2A_INTERCEPT
function TASK_A2A_INTERCEPT:UpdateTaskInfo()
local TargetCoordinate = self.Detection and self.Detection:GetDetectedItemCoordinate( self.DetectedItemIndex ) or self.TargetSetUnit:GetFirst():GetCoordinate()
- self:SetInfo( "Coordinates", TargetCoordinate, 0 )
+ self:SetInfo( "Coordinate", TargetCoordinate, 0 )
self:SetInfo( "Threat", "[" .. string.rep( "â– ", self.Detection and self.Detection:GetDetectedItemThreatLevel( self.DetectedItemIndex ) or self.TargetSetUnit:CalculateThreatLevelA2G() ) .. "]", 11 )
@@ -492,7 +521,7 @@ do -- TASK_A2A_SWEEP
function TASK_A2A_SWEEP:UpdateTaskInfo()
local TargetCoordinate = self.Detection and self.Detection:GetDetectedItemCoordinate( self.DetectedItemIndex ) or self.TargetSetUnit:GetFirst():GetCoordinate()
- self:SetInfo( "Coordinates", TargetCoordinate, 0 )
+ self:SetInfo( "Coordinate", TargetCoordinate, 0 )
self:SetInfo( "Assumed Threat", "[" .. string.rep( "â– ", self.Detection and self.Detection:GetDetectedItemThreatLevel( self.DetectedItemIndex ) or self.TargetSetUnit:CalculateThreatLevelA2G() ) .. "]", 11 )
@@ -641,7 +670,7 @@ do -- TASK_A2A_ENGAGE
function TASK_A2A_ENGAGE:UpdateTaskInfo()
local TargetCoordinate = self.Detection and self.Detection:GetDetectedItemCoordinate( self.DetectedItemIndex ) or self.TargetSetUnit:GetFirst():GetCoordinate()
- self:SetInfo( "Coordinates", TargetCoordinate, 0 )
+ self:SetInfo( "Coordinate", TargetCoordinate, 0 )
self:SetInfo( "Threat", "[" .. string.rep( "â– ", self.Detection and self.Detection:GetDetectedItemThreatLevel( self.DetectedItemIndex ) or self.TargetSetUnit:CalculateThreatLevelA2G() ) .. "]", 11 )
diff --git a/Moose Development/Moose/Tasking/Task_A2G.lua b/Moose Development/Moose/Tasking/Task_A2G.lua
index cc4a21797..e07579827 100644
--- a/Moose Development/Moose/Tasking/Task_A2G.lua
+++ b/Moose Development/Moose/Tasking/Task_A2G.lua
@@ -285,6 +285,36 @@ do -- TASK_A2G
return self.GoalTotal
end
+
+ function TASK_A2G:GetMarkInfo( TaskInfoID, TaskInfo )
+
+ if type( TaskInfo.TaskInfoText ) == "string" then
+ if TaskInfoID == "Targets" then
+ else
+ return string.format( "%s: %s", TaskInfoID, TaskInfo.TaskInfoText )
+ end
+ elseif type( TaskInfo ) == "table" then
+ if TaskInfoID == "Coordinate" then
+ end
+ end
+
+ return nil
+ end
+
+
+ function TASK_A2G:GetReportDetail( ReportGroup, TaskInfoID, TaskInfo )
+
+ if type( TaskInfo.TaskInfoText ) == "string" then
+ return string.format( "%s: %s", TaskInfoID, TaskInfo.TaskInfoText )
+ elseif type(TaskInfo) == "table" then
+ if TaskInfoID == "Coordinate" then
+ local FromCoordinate = ReportGroup:GetUnit(1):GetCoordinate()
+ local ToCoordinate = TaskInfo.TaskInfoText -- Core.Point#COORDINATE
+ return string.format( " - %s: %s", TaskInfoID, ToCoordinate:ToString( ReportGroup:GetUnit(1), nil, self ) )
+ else
+ end
+ end
+ end
end
@@ -335,7 +365,7 @@ do -- TASK_A2G_SEAD
local TargetCoordinate = self.Detection and self.Detection:GetDetectedItemCoordinate( self.DetectedItemIndex ) or self.TargetSetUnit:GetFirst():GetCoordinate()
- self:SetInfo( "Coordinates", TargetCoordinate, 0 )
+ self:SetInfo( "Coordinate", TargetCoordinate, 0 )
local ThreatLevel, ThreatText
if self.Detection then
@@ -366,7 +396,7 @@ do -- TASK_A2G_SEAD
end
function TASK_A2G_SEAD:ReportOrder( ReportGroup )
- local Coordinate = self:GetInfo( "Coordinates" )
+ local Coordinate = self:GetInfo( "Coordinate" )
--local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
@@ -484,7 +514,7 @@ do -- TASK_A2G_BAI
self:E({self.Detection, self.DetectedItemIndex})
local TargetCoordinate = self.Detection and self.Detection:GetDetectedItemCoordinate( self.DetectedItemIndex ) or self.TargetSetUnit:GetFirst():GetCoordinate()
- self:SetInfo( "Coordinates", TargetCoordinate, 0 )
+ self:SetInfo( "Coordinate", TargetCoordinate, 0 )
local ThreatLevel, ThreatText
if self.Detection then
@@ -512,11 +542,21 @@ do -- TASK_A2G_BAI
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, DetectedItemsTypes ), 10 )
end
+ local TargetCoordinate = self:GetInfo( "Coordinate" ) -- Core.Point#COORDINATE
+
+ local Velocity = self.TargetSetUnit:GetVelocity()
+ local Heading = self.TargetSetUnit:GetHeading()
+
+ TargetCoordinate:SetHeading( Heading )
+ TargetCoordinate:SetVelocity( Velocity )
+
+ self:SetInfo( "Position", "Targets are" .. TargetCoordinate:GetMovingText() .. ".", 12 )
+
end
function TASK_A2G_BAI:ReportOrder( ReportGroup )
- local Coordinate = self:GetInfo( "Coordinates" )
+ local Coordinate = self:GetInfo( "Coordinate" )
--local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
@@ -633,7 +673,7 @@ do -- TASK_A2G_CAS
function TASK_A2G_CAS:UpdateTaskInfo()
local TargetCoordinate = ( self.Detection and self.Detection:GetDetectedItemCoordinate( self.DetectedItemIndex ) ) or self.TargetSetUnit:GetFirst():GetCoordinate()
- self:SetInfo( "Coordinates", TargetCoordinate, 0 )
+ self:SetInfo( "Coordinate", TargetCoordinate, 0 )
local ThreatLevel, ThreatText
if self.Detection then
@@ -666,7 +706,7 @@ do -- TASK_A2G_CAS
--- @param #TASK_A2G_CAS self
function TASK_A2G_CAS:ReportOrder( ReportGroup )
- local Coordinate = self:GetInfo( "Coordinates" )
+ local Coordinate = self:GetInfo( "Coordinate" )
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
diff --git a/Moose Development/Moose/Wrapper/Identifiable.lua b/Moose Development/Moose/Wrapper/Identifiable.lua
index cd7243f4b..cd86668c0 100644
--- a/Moose Development/Moose/Wrapper/Identifiable.lua
+++ b/Moose Development/Moose/Wrapper/Identifiable.lua
@@ -159,6 +159,36 @@ function IDENTIFIABLE:GetCoalition()
return nil
end
+--- Returns the name of the coalition of the Identifiable.
+-- @param #IDENTIFIABLE self
+-- @return #string The name of the coalition.
+-- @return #nil The DCS Identifiable is not existing or alive.
+function IDENTIFIABLE:GetCoalitionName()
+ self:F2( self.IdentifiableName )
+
+ local DCSIdentifiable = self:GetDCSObject()
+
+ if DCSIdentifiable then
+ local IdentifiableCoalition = DCSIdentifiable:getCoalition()
+ self:T3( IdentifiableCoalition )
+
+ if IdentifiableCoalition == coalition.side.BLUE then
+ return "Blue"
+ end
+
+ if IdentifiableCoalition == coalition.side.RED then
+ return "Red"
+ end
+
+ if IdentifiableCoalition == coalition.side.NEUTRAL then
+ return "Neutral"
+ end
+ end
+
+ self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" )
+ return nil
+end
+
--- Returns country of the Identifiable.
-- @param #IDENTIFIABLE self
-- @return Dcs.DCScountry#country.id The country identifier.
diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua
index 592508733..02c6b2ec3 100644
--- a/Moose Development/Moose/Wrapper/Positionable.lua
+++ b/Moose Development/Moose/Wrapper/Positionable.lua
@@ -415,9 +415,9 @@ function POSITIONABLE:GetMessageText( Message, Name ) --R2.1 added
local DCSObject = self:GetDCSObject()
if DCSObject then
- Name = Name and ( " (" .. Name .. ")" ) or ""
- local Callsign = string.format( "[%s]", self:GetCallsign() ~= "" and self:GetCallsign() or self:GetName() )
- local MessageText = Callsign .. Name .. ": " .. Message
+ Name = Name and ( " => " .. Name ) or ""
+ local Callsign = string.format( "%s", self:GetCallsign() ~= "" and self:GetCallsign() or self:GetName() )
+ local MessageText = string.format("[%s%s]: %s", Callsign, Name, Message )
return MessageText
end
diff --git a/Moose Mission Setup/Moose.files b/Moose Mission Setup/Moose.files
index b50c54d88..9c19c0000 100644
--- a/Moose Mission Setup/Moose.files
+++ b/Moose Mission Setup/Moose.files
@@ -16,6 +16,7 @@ Core/Message.lua
Core/Fsm.lua
Core/Radio.lua
Core/SpawnStatic.lua
+Core/Goal.lua
Core/Cargo.lua
Core/Spot.lua
@@ -41,6 +42,9 @@ Functional/AirbasePolice.lua
Functional/Detection.lua
Functional/Designate.lua
Functional/RAT.lua
+Functional/ZoneGoal.lua
+Functional/ZoneGoalCoalition.lua
+Functional/ZoneCaptureCoalition.lua
AI/AI_Balancer.lua
AI/AI_A2A.lua
@@ -68,5 +72,6 @@ Tasking/Task_A2G.lua
Tasking/Task_A2A_Dispatcher.lua
Tasking/Task_A2A.lua
Tasking/Task_Cargo.lua
+Tasking/TaskZoneCapture.lua
Moose.lua
diff --git a/Moose Mission Setup/Moose_.lua b/Moose Mission Setup/Moose_.lua
index 276a1df1d..c0cbc0ff8 100644
--- a/Moose Mission Setup/Moose_.lua
+++ b/Moose Mission Setup/Moose_.lua
@@ -1,85 +1,27293 @@
-env.info('*** MOOSE DYNAMIC INCLUDE START *** ')
-env.info('Moose Generation Timestamp: 20171003_1348')
-local base=_G
-__Moose={}
-__Moose.Include=function(IncludeFile)
-if not __Moose.Includes[IncludeFile]then
-__Moose.Includes[IncludeFile]=IncludeFile
-local f=assert(base.loadfile(__Moose.ProgramPath..IncludeFile))
-if f==nil then
-error("Moose: Could not load Moose file "..IncludeFile)
+env.info('*** MOOSE STATIC INCLUDE START *** ')
+env.info('Moose Generation Timestamp: 20171006_1443')
+env.setErrorMessageBoxEnabled(false)
+routines={}
+routines.majorVersion=3
+routines.minorVersion=3
+routines.build=22
+routines.utils={}
+routines.utils.deepCopy=function(object)
+local lookup_table={}
+local function _copy(object)
+if type(object)~="table"then
+return object
+elseif lookup_table[object]then
+return lookup_table[object]
+end
+local new_table={}
+lookup_table[object]=new_table
+for index,value in pairs(object)do
+new_table[_copy(index)]=_copy(value)
+end
+return setmetatable(new_table,getmetatable(object))
+end
+local objectreturn=_copy(object)
+return objectreturn
+end
+routines.utils.oneLineSerialize=function(tbl)
+lookup_table={}
+local function _Serialize(tbl)
+if type(tbl)=='table'then
+if lookup_table[tbl]then
+return lookup_table[object]
+end
+local tbl_str={}
+lookup_table[tbl]=tbl_str
+tbl_str[#tbl_str+1]='{'
+for ind,val in pairs(tbl)do
+local ind_str={}
+if type(ind)=="number"then
+ind_str[#ind_str+1]='['
+ind_str[#ind_str+1]=tostring(ind)
+ind_str[#ind_str+1]=']='
else
-env.info("Moose: "..IncludeFile.." dynamically loaded from "..__Moose.ProgramPath)
-return f()
+ind_str[#ind_str+1]='['
+ind_str[#ind_str+1]=routines.utils.basicSerialize(ind)
+ind_str[#ind_str+1]=']='
+end
+local val_str={}
+if((type(val)=='number')or(type(val)=='boolean'))then
+val_str[#val_str+1]=tostring(val)
+val_str[#val_str+1]=','
+tbl_str[#tbl_str+1]=table.concat(ind_str)
+tbl_str[#tbl_str+1]=table.concat(val_str)
+elseif type(val)=='string'then
+val_str[#val_str+1]=routines.utils.basicSerialize(val)
+val_str[#val_str+1]=','
+tbl_str[#tbl_str+1]=table.concat(ind_str)
+tbl_str[#tbl_str+1]=table.concat(val_str)
+elseif type(val)=='nil'then
+val_str[#val_str+1]='nil,'
+tbl_str[#tbl_str+1]=table.concat(ind_str)
+tbl_str[#tbl_str+1]=table.concat(val_str)
+elseif type(val)=='table'then
+if ind=="__index"then
+else
+val_str[#val_str+1]=_Serialize(val)
+val_str[#val_str+1]=','
+tbl_str[#tbl_str+1]=table.concat(ind_str)
+tbl_str[#tbl_str+1]=table.concat(val_str)
+end
+elseif type(val)=='function'then
+else
+end
+end
+tbl_str[#tbl_str+1]='}'
+return table.concat(tbl_str)
+else
+return tostring(tbl)
+end
+end
+local objectreturn=_Serialize(tbl)
+return objectreturn
+end
+routines.utils.basicSerialize=function(s)
+if s==nil then
+return"\"\""
+else
+if((type(s)=='number')or(type(s)=='boolean')or(type(s)=='function')or(type(s)=='table')or(type(s)=='userdata'))then
+return tostring(s)
+elseif type(s)=='string'then
+s=string.format('%q',s)
+return s
end
end
end
-__Moose.ProgramPath="Scripts/Moose/"
-__Moose.Includes={}
-__Moose.Include('Utilities/Routines.lua')
-__Moose.Include('Utilities/Utils.lua')
-__Moose.Include('Core/Base.lua')
-__Moose.Include('Core/Report.lua')
-__Moose.Include('Core/Scheduler.lua')
-__Moose.Include('Core/ScheduleDispatcher.lua')
-__Moose.Include('Core/Event.lua')
-__Moose.Include('Core/Settings.lua')
-__Moose.Include('Core/Menu.lua')
-__Moose.Include('Core/Zone.lua')
-__Moose.Include('Core/Database.lua')
-__Moose.Include('Core/Set.lua')
-__Moose.Include('Core/Point.lua')
-__Moose.Include('Core/Message.lua')
-__Moose.Include('Core/Fsm.lua')
-__Moose.Include('Core/Radio.lua')
-__Moose.Include('Core/SpawnStatic.lua')
-__Moose.Include('Core/Cargo.lua')
-__Moose.Include('Core/Spot.lua')
-__Moose.Include('Wrapper/Object.lua')
-__Moose.Include('Wrapper/Identifiable.lua')
-__Moose.Include('Wrapper/Positionable.lua')
-__Moose.Include('Wrapper/Controllable.lua')
-__Moose.Include('Wrapper/Group.lua')
-__Moose.Include('Wrapper/Unit.lua')
-__Moose.Include('Wrapper/Client.lua')
-__Moose.Include('Wrapper/Static.lua')
-__Moose.Include('Wrapper/Airbase.lua')
-__Moose.Include('Wrapper/Scenery.lua')
-__Moose.Include('Functional/Scoring.lua')
-__Moose.Include('Functional/CleanUp.lua')
-__Moose.Include('Functional/Spawn.lua')
-__Moose.Include('Functional/Movement.lua')
-__Moose.Include('Functional/Sead.lua')
-__Moose.Include('Functional/Escort.lua')
-__Moose.Include('Functional/MissileTrainer.lua')
-__Moose.Include('Functional/AirbasePolice.lua')
-__Moose.Include('Functional/Detection.lua')
-__Moose.Include('Functional/Designate.lua')
-__Moose.Include('Functional/RAT.lua')
-__Moose.Include('AI/AI_Balancer.lua')
-__Moose.Include('AI/AI_A2A.lua')
-__Moose.Include('AI/AI_A2A_Patrol.lua')
-__Moose.Include('AI/AI_A2A_Cap.lua')
-__Moose.Include('AI/AI_A2A_Gci.lua')
-__Moose.Include('AI/AI_A2A_Dispatcher.lua')
-__Moose.Include('AI/AI_Patrol.lua')
-__Moose.Include('AI/AI_Cap.lua')
-__Moose.Include('AI/AI_Cas.lua')
-__Moose.Include('AI/AI_Bai.lua')
-__Moose.Include('AI/AI_Formation.lua')
-__Moose.Include('Actions/Act_Assign.lua')
-__Moose.Include('Actions/Act_Route.lua')
-__Moose.Include('Actions/Act_Account.lua')
-__Moose.Include('Actions/Act_Assist.lua')
-__Moose.Include('Tasking/CommandCenter.lua')
-__Moose.Include('Tasking/Mission.lua')
-__Moose.Include('Tasking/Task.lua')
-__Moose.Include('Tasking/DetectionManager.lua')
-__Moose.Include('Tasking/Task_A2G_Dispatcher.lua')
-__Moose.Include('Tasking/Task_A2G.lua')
-__Moose.Include('Tasking/Task_A2A_Dispatcher.lua')
-__Moose.Include('Tasking/Task_A2A.lua')
-__Moose.Include('Tasking/Task_Cargo.lua')
-__Moose.Include('Moose.lua')
-BASE:TraceOnOff(true)
+routines.utils.toDegree=function(angle)
+return angle*180/math.pi
+end
+routines.utils.toRadian=function(angle)
+return angle*math.pi/180
+end
+routines.utils.metersToNM=function(meters)
+return meters/1852
+end
+routines.utils.metersToFeet=function(meters)
+return meters/0.3048
+end
+routines.utils.NMToMeters=function(NM)
+return NM*1852
+end
+routines.utils.feetToMeters=function(feet)
+return feet*0.3048
+end
+routines.utils.mpsToKnots=function(mps)
+return mps*3600/1852
+end
+routines.utils.mpsToKmph=function(mps)
+return mps*3.6
+end
+routines.utils.knotsToMps=function(knots)
+return knots*1852/3600
+end
+routines.utils.kmphToMps=function(kmph)
+return kmph/3.6
+end
+function routines.utils.makeVec2(Vec3)
+if Vec3.z then
+return{x=Vec3.x,y=Vec3.z}
+else
+return{x=Vec3.x,y=Vec3.y}
+end
+end
+function routines.utils.makeVec3(Vec2,y)
+if not Vec2.z then
+if not y then
+y=0
+end
+return{x=Vec2.x,y=y,z=Vec2.y}
+else
+return{x=Vec2.x,y=Vec2.y,z=Vec2.z}
+end
+end
+function routines.utils.makeVec3GL(Vec2,offset)
+local adj=offset or 0
+if not Vec2.z then
+return{x=Vec2.x,y=(land.getHeight(Vec2)+adj),z=Vec2.y}
+else
+return{x=Vec2.x,y=(land.getHeight({x=Vec2.x,y=Vec2.z})+adj),z=Vec2.z}
+end
+end
+routines.utils.zoneToVec3=function(zone)
+local new={}
+if type(zone)=='table'and zone.point then
+new.x=zone.point.x
+new.y=zone.point.y
+new.z=zone.point.z
+return new
+elseif type(zone)=='string'then
+zone=trigger.misc.getZone(zone)
+if zone then
+new.x=zone.point.x
+new.y=zone.point.y
+new.z=zone.point.z
+return new
+end
+end
+end
+function routines.utils.getDir(vec,point)
+local dir=math.atan2(vec.z,vec.x)
+dir=dir+routines.getNorthCorrection(point)
+if dir<0 then
+dir=dir+2*math.pi
+end
+return dir
+end
+function routines.utils.get2DDist(point1,point2)
+point1=routines.utils.makeVec3(point1)
+point2=routines.utils.makeVec3(point2)
+return routines.vec.mag({x=point1.x-point2.x,y=0,z=point1.z-point2.z})
+end
+function routines.utils.get3DDist(point1,point2)
+return routines.vec.mag({x=point1.x-point2.x,y=point1.y-point2.y,z=point1.z-point2.z})
+end
+routines.vec={}
+routines.vec.add=function(vec1,vec2)
+return{x=vec1.x+vec2.x,y=vec1.y+vec2.y,z=vec1.z+vec2.z}
+end
+routines.vec.sub=function(vec1,vec2)
+return{x=vec1.x-vec2.x,y=vec1.y-vec2.y,z=vec1.z-vec2.z}
+end
+routines.vec.scalarMult=function(vec,mult)
+return{x=vec.x*mult,y=vec.y*mult,z=vec.z*mult}
+end
+routines.vec.scalar_mult=routines.vec.scalarMult
+routines.vec.dp=function(vec1,vec2)
+return vec1.x*vec2.x+vec1.y*vec2.y+vec1.z*vec2.z
+end
+routines.vec.cp=function(vec1,vec2)
+return{x=vec1.y*vec2.z-vec1.z*vec2.y,y=vec1.z*vec2.x-vec1.x*vec2.z,z=vec1.x*vec2.y-vec1.y*vec2.x}
+end
+routines.vec.mag=function(vec)
+return(vec.x^2+vec.y^2+vec.z^2)^0.5
+end
+routines.vec.getUnitVec=function(vec)
+local mag=routines.vec.mag(vec)
+return{x=vec.x/mag,y=vec.y/mag,z=vec.z/mag}
+end
+routines.vec.rotateVec2=function(vec2,theta)
+return{x=vec2.x*math.cos(theta)-vec2.y*math.sin(theta),y=vec2.x*math.sin(theta)+vec2.y*math.cos(theta)}
+end
+routines.tostringMGRS=function(MGRS,acc)
+if acc==0 then
+return MGRS.UTMZone..' '..MGRS.MGRSDigraph
+else
+return MGRS.UTMZone..' '..MGRS.MGRSDigraph..' '..string.format('%0'..acc..'d',routines.utils.round(MGRS.Easting/(10^(5-acc)),0))
+..' '..string.format('%0'..acc..'d',routines.utils.round(MGRS.Northing/(10^(5-acc)),0))
+end
+end
+routines.tostringLL=function(lat,lon,acc,DMS)
+local latHemi,lonHemi
+if lat>0 then
+latHemi='N'
+else
+latHemi='S'
+end
+if lon>0 then
+lonHemi='E'
+else
+lonHemi='W'
+end
+lat=math.abs(lat)
+lon=math.abs(lon)
+local latDeg=math.floor(lat)
+local latMin=(lat-latDeg)*60
+local lonDeg=math.floor(lon)
+local lonMin=(lon-lonDeg)*60
+if DMS then
+local oldLatMin=latMin
+latMin=math.floor(latMin)
+local latSec=routines.utils.round((oldLatMin-latMin)*60,acc)
+local oldLonMin=lonMin
+lonMin=math.floor(lonMin)
+local lonSec=routines.utils.round((oldLonMin-lonMin)*60,acc)
+if latSec==60 then
+latSec=0
+latMin=latMin+1
+end
+if lonSec==60 then
+lonSec=0
+lonMin=lonMin+1
+end
+local secFrmtStr
+if acc<=0 then
+secFrmtStr='%02d'
+else
+local width=3+acc
+secFrmtStr='%0'..width..'.'..acc..'f'
+end
+return string.format('%02d',latDeg)..' '..string.format('%02d',latMin)..'\' '..string.format(secFrmtStr,latSec)..'"'..latHemi..' '
+..string.format('%02d',lonDeg)..' '..string.format('%02d',lonMin)..'\' '..string.format(secFrmtStr,lonSec)..'"'..lonHemi
+else
+latMin=routines.utils.round(latMin,acc)
+lonMin=routines.utils.round(lonMin,acc)
+if latMin==60 then
+latMin=0
+latDeg=latDeg+1
+end
+if lonMin==60 then
+lonMin=0
+lonDeg=lonDeg+1
+end
+local minFrmtStr
+if acc<=0 then
+minFrmtStr='%02d'
+else
+local width=3+acc
+minFrmtStr='%0'..width..'.'..acc..'f'
+end
+return string.format('%02d',latDeg)..' '..string.format(minFrmtStr,latMin)..'\''..latHemi..' '
+..string.format('%02d',lonDeg)..' '..string.format(minFrmtStr,lonMin)..'\''..lonHemi
+end
+end
+routines.tostringBR=function(az,dist,alt,metric)
+az=routines.utils.round(routines.utils.toDegree(az),0)
+if metric then
+dist=routines.utils.round(dist/1000,2)
+else
+dist=routines.utils.round(routines.utils.metersToNM(dist),2)
+end
+local s=string.format('%03d',az)..' for '..dist
+if alt then
+if metric then
+s=s..' at '..routines.utils.round(alt,0)
+else
+s=s..' at '..routines.utils.round(routines.utils.metersToFeet(alt),0)
+end
+end
+return s
+end
+routines.getNorthCorrection=function(point)
+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
+do
+local idNum=0
+routines.addEventHandler=function(f)
+local handler={}
+idNum=idNum+1
+handler.id=idNum
+handler.f=f
+handler.onEvent=function(self,event)
+self.f(event)
+end
+world.addEventHandler(handler)
+end
+routines.removeEventHandler=function(id)
+for key,handler in pairs(world.eventHandlers)do
+if handler.id and handler.id==id then
+world.eventHandlers[key]=nil
+return true
+end
+end
+return false
+end
+end
+function routines.getRandPointInCircle(point,radius,innerRadius)
+local theta=2*math.pi*math.random()
+local rad=math.random()+math.random()
+if rad>1 then
+rad=2-rad
+end
+local radMult
+if innerRadius and innerRadius<=radius then
+radMult=(radius-innerRadius)*rad+innerRadius
+else
+radMult=radius*rad
+end
+if not point.z then
+point.z=point.y
+end
+local rndCoord
+if radius>0 then
+rndCoord={x=math.cos(theta)*radMult+point.x,y=math.sin(theta)*radMult+point.z}
+else
+rndCoord={x=point.x,y=point.z}
+end
+return rndCoord
+end
+routines.goRoute=function(group,path)
+local misTask={
+id='Mission',
+params={
+route={
+points=routines.utils.deepCopy(path),
+},
+},
+}
+if type(group)=='string'then
+group=Group.getByName(group)
+end
+local groupCon=group:getController()
+if groupCon then
+groupCon:setTask(misTask)
+return true
+end
+Controller.setTask(groupCon,misTask)
+return false
+end
+routines.ground={}
+routines.fixedWing={}
+routines.heli={}
+routines.ground.buildWP=function(point,overRideForm,overRideSpeed)
+local wp={}
+wp.x=point.x
+if point.z then
+wp.y=point.z
+else
+wp.y=point.y
+end
+local form,speed
+if point.speed and not overRideSpeed then
+wp.speed=point.speed
+elseif type(overRideSpeed)=='number'then
+wp.speed=overRideSpeed
+else
+wp.speed=routines.utils.kmphToMps(20)
+end
+if point.form and not overRideForm then
+form=point.form
+else
+form=overRideForm
+end
+if not form then
+wp.action='Cone'
+else
+form=string.lower(form)
+if form=='off_road'or form=='off road'then
+wp.action='Off Road'
+elseif form=='on_road'or form=='on road'then
+wp.action='On Road'
+elseif form=='rank'or form=='line_abrest'or form=='line abrest'or form=='lineabrest'then
+wp.action='Rank'
+elseif form=='cone'then
+wp.action='Cone'
+elseif form=='diamond'then
+wp.action='Diamond'
+elseif form=='vee'then
+wp.action='Vee'
+elseif form=='echelon_left'or form=='echelon left'or form=='echelonl'then
+wp.action='EchelonL'
+elseif form=='echelon_right'or form=='echelon right'or form=='echelonr'then
+wp.action='EchelonR'
+else
+wp.action='Cone'
+end
+end
+wp.type='Turning Point'
+return wp
+end
+routines.fixedWing.buildWP=function(point,WPtype,speed,alt,altType)
+local wp={}
+wp.x=point.x
+if point.z then
+wp.y=point.z
+else
+wp.y=point.y
+end
+if alt and type(alt)=='number'then
+wp.alt=alt
+else
+wp.alt=2000
+end
+if altType then
+altType=string.lower(altType)
+if altType=='radio'or'agl'then
+wp.alt_type='RADIO'
+elseif altType=='baro'or'asl'then
+wp.alt_type='BARO'
+end
+else
+wp.alt_type='RADIO'
+end
+if point.speed then
+speed=point.speed
+end
+if point.type then
+WPtype=point.type
+end
+if not speed then
+wp.speed=routines.utils.kmphToMps(500)
+else
+wp.speed=speed
+end
+if not WPtype then
+wp.action='Turning Point'
+else
+WPtype=string.lower(WPtype)
+if WPtype=='flyover'or WPtype=='fly over'or WPtype=='fly_over'then
+wp.action='Fly Over Point'
+elseif WPtype=='turningpoint'or WPtype=='turning point'or WPtype=='turning_point'then
+wp.action='Turning Point'
+else
+wp.action='Turning Point'
+end
+end
+wp.type='Turning Point'
+return wp
+end
+routines.heli.buildWP=function(point,WPtype,speed,alt,altType)
+local wp={}
+wp.x=point.x
+if point.z then
+wp.y=point.z
+else
+wp.y=point.y
+end
+if alt and type(alt)=='number'then
+wp.alt=alt
+else
+wp.alt=500
+end
+if altType then
+altType=string.lower(altType)
+if altType=='radio'or'agl'then
+wp.alt_type='RADIO'
+elseif altType=='baro'or'asl'then
+wp.alt_type='BARO'
+end
+else
+wp.alt_type='RADIO'
+end
+if point.speed then
+speed=point.speed
+end
+if point.type then
+WPtype=point.type
+end
+if not speed then
+wp.speed=routines.utils.kmphToMps(200)
+else
+wp.speed=speed
+end
+if not WPtype then
+wp.action='Turning Point'
+else
+WPtype=string.lower(WPtype)
+if WPtype=='flyover'or WPtype=='fly over'or WPtype=='fly_over'then
+wp.action='Fly Over Point'
+elseif WPtype=='turningpoint'or WPtype=='turning point'or WPtype=='turning_point'then
+wp.action='Turning Point'
+else
+wp.action='Turning Point'
+end
+end
+wp.type='Turning Point'
+return wp
+end
+routines.groupToRandomPoint=function(vars)
+local group=vars.group
+local point=vars.point
+local radius=vars.radius or 0
+local innerRadius=vars.innerRadius
+local form=vars.form or'Cone'
+local heading=vars.heading or math.random()*2*math.pi
+local headingDegrees=vars.headingDegrees
+local speed=vars.speed or routines.utils.kmphToMps(20)
+local useRoads
+if not vars.disableRoads then
+useRoads=true
+else
+useRoads=false
+end
+local path={}
+if headingDegrees then
+heading=headingDegrees*math.pi/180
+end
+if heading>=2*math.pi then
+heading=heading-2*math.pi
+end
+local rndCoord=routines.getRandPointInCircle(point,radius,innerRadius)
+local offset={}
+local posStart=routines.getLeadPos(group)
+offset.x=routines.utils.round(math.sin(heading-(math.pi/2))*50+rndCoord.x,3)
+offset.z=routines.utils.round(math.cos(heading+(math.pi/2))*50+rndCoord.y,3)
+path[#path+1]=routines.ground.buildWP(posStart,form,speed)
+if useRoads==true and((point.x-posStart.x)^2+(point.z-posStart.z)^2)^0.5>radius*1.3 then
+path[#path+1]=routines.ground.buildWP({['x']=posStart.x+11,['z']=posStart.z+11},'off_road',speed)
+path[#path+1]=routines.ground.buildWP(posStart,'on_road',speed)
+path[#path+1]=routines.ground.buildWP(offset,'on_road',speed)
+else
+path[#path+1]=routines.ground.buildWP({['x']=posStart.x+25,['z']=posStart.z+25},form,speed)
+end
+path[#path+1]=routines.ground.buildWP(offset,form,speed)
+path[#path+1]=routines.ground.buildWP(rndCoord,form,speed)
+routines.goRoute(group,path)
+return
+end
+routines.groupRandomDistSelf=function(gpData,dist,form,heading,speed)
+local pos=routines.getLeadPos(gpData)
+local fakeZone={}
+fakeZone.radius=dist or math.random(300,1000)
+fakeZone.point={x=pos.x,y,pos.y,z=pos.z}
+routines.groupToRandomZone(gpData,fakeZone,form,heading,speed)
+return
+end
+routines.groupToRandomZone=function(gpData,zone,form,heading,speed)
+if type(gpData)=='string'then
+gpData=Group.getByName(gpData)
+end
+if type(zone)=='string'then
+zone=trigger.misc.getZone(zone)
+elseif type(zone)=='table'and not zone.radius then
+zone=trigger.misc.getZone(zone[math.random(1,#zone)])
+end
+if speed then
+speed=routines.utils.kmphToMps(speed)
+end
+local vars={}
+vars.group=gpData
+vars.radius=zone.radius
+vars.form=form
+vars.headingDegrees=heading
+vars.speed=speed
+vars.point=routines.utils.zoneToVec3(zone)
+routines.groupToRandomPoint(vars)
+return
+end
+routines.isTerrainValid=function(coord,terrainTypes)
+if coord.z then
+coord.y=coord.z
+end
+local typeConverted={}
+if type(terrainTypes)=='string'then
+for constId,constData in pairs(land.SurfaceType)do
+if string.lower(constId)==string.lower(terrainTypes)or string.lower(constData)==string.lower(terrainTypes)then
+table.insert(typeConverted,constId)
+end
+end
+elseif type(terrainTypes)=='table'then
+for typeId,typeData in pairs(terrainTypes)do
+for constId,constData in pairs(land.SurfaceType)do
+if string.lower(constId)==string.lower(typeData)or string.lower(constData)==string.lower(typeId)then
+table.insert(typeConverted,constId)
+end
+end
+end
+end
+for validIndex,validData in pairs(typeConverted)do
+if land.getSurfaceType(coord)==land.SurfaceType[validData]then
+return true
+end
+end
+return false
+end
+routines.groupToPoint=function(gpData,point,form,heading,speed,useRoads)
+if type(point)=='string'then
+point=trigger.misc.getZone(point)
+end
+if speed then
+speed=routines.utils.kmphToMps(speed)
+end
+local vars={}
+vars.group=gpData
+vars.form=form
+vars.headingDegrees=heading
+vars.speed=speed
+vars.disableRoads=useRoads
+vars.point=routines.utils.zoneToVec3(point)
+routines.groupToRandomPoint(vars)
+return
+end
+routines.getLeadPos=function(group)
+if type(group)=='string'then
+group=Group.getByName(group)
+end
+local units=group:getUnits()
+local leader=units[1]
+if not leader then
+local lowestInd=math.huge
+for ind,unit in pairs(units)do
+if ind0 then
+local maxPos=-math.huge
+local maxPosInd
+for i=1,#unitPosTbl do
+local rotatedVec2=routines.vec.rotateVec2(routines.utils.makeVec2(unitPosTbl[i]),heading)
+if(not maxPos)or maxPos=1.0 then
+CurrentZoneID=routines.IsUnitInZones(CargoUnit,LandingZones)
+if CurrentZoneID then
+break
+end
+end
+end
+end
+return CurrentZoneID
+end
+function routines.IsUnitInZones(TransportUnit,LandingZones)
+local TransportZoneResult=nil
+local TransportZonePos=nil
+local TransportZone=nil
+if TransportUnit then
+local TransportUnitPos=TransportUnit:getPosition().p
+if type(LandingZones)=="table"then
+for LandingZoneID,LandingZoneName in pairs(LandingZones)do
+TransportZone=trigger.misc.getZone(LandingZoneName)
+if TransportZone then
+TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z}
+if(((TransportUnitPos.x-TransportZonePos.x)^2+(TransportUnitPos.z-TransportZonePos.z)^2)^0.5<=TransportZonePos.radius)then
+TransportZoneResult=LandingZoneID
+break
+end
+end
+end
+else
+TransportZone=trigger.misc.getZone(LandingZones)
+TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z}
+if(((TransportUnitPos.x-TransportZonePos.x)^2+(TransportUnitPos.z-TransportZonePos.z)^2)^0.5<=TransportZonePos.radius)then
+TransportZoneResult=1
+end
+end
+if TransportZoneResult then
+else
+end
+return TransportZoneResult
+else
+return nil
+end
+end
+function routines.IsUnitNearZonesRadius(TransportUnit,LandingZones,ZoneRadius)
+local TransportZoneResult=nil
+local TransportZonePos=nil
+local TransportZone=nil
+if TransportUnit then
+local TransportUnitPos=TransportUnit:getPosition().p
+if type(LandingZones)=="table"then
+for LandingZoneID,LandingZoneName in pairs(LandingZones)do
+TransportZone=trigger.misc.getZone(LandingZoneName)
+if TransportZone then
+TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z}
+if(((TransportUnitPos.x-TransportZonePos.x)^2+(TransportUnitPos.z-TransportZonePos.z)^2)^0.5<=ZoneRadius)then
+TransportZoneResult=LandingZoneID
+break
+end
+end
+end
+else
+TransportZone=trigger.misc.getZone(LandingZones)
+TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z}
+if(((TransportUnitPos.x-TransportZonePos.x)^2+(TransportUnitPos.z-TransportZonePos.z)^2)^0.5<=ZoneRadius)then
+TransportZoneResult=1
+end
+end
+if TransportZoneResult then
+else
+end
+return TransportZoneResult
+else
+return nil
+end
+end
+function routines.IsStaticInZones(TransportStatic,LandingZones)
+local TransportZoneResult=nil
+local TransportZonePos=nil
+local TransportZone=nil
+local TransportStaticPos=TransportStatic:getPosition().p
+if type(LandingZones)=="table"then
+for LandingZoneID,LandingZoneName in pairs(LandingZones)do
+TransportZone=trigger.misc.getZone(LandingZoneName)
+if TransportZone then
+TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z}
+if(((TransportStaticPos.x-TransportZonePos.x)^2+(TransportStaticPos.z-TransportZonePos.z)^2)^0.5<=TransportZonePos.radius)then
+TransportZoneResult=LandingZoneID
+break
+end
+end
+end
+else
+TransportZone=trigger.misc.getZone(LandingZones)
+TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z}
+if(((TransportStaticPos.x-TransportZonePos.x)^2+(TransportStaticPos.z-TransportZonePos.z)^2)^0.5<=TransportZonePos.radius)then
+TransportZoneResult=1
+end
+end
+return TransportZoneResult
+end
+function routines.IsUnitInRadius(CargoUnit,ReferencePosition,Radius)
+local Valid=true
+local CargoPos=CargoUnit:getPosition().p
+local ReferenceP=ReferencePosition.p
+if(((CargoPos.x-ReferenceP.x)^2+(CargoPos.z-ReferenceP.z)^2)^0.5<=Radius)then
+else
+Valid=false
+end
+return Valid
+end
+function routines.IsPartOfGroupInRadius(CargoGroup,ReferencePosition,Radius)
+local Valid=true
+Valid=routines.ValidateGroup(CargoGroup,"CargoGroup",Valid)
+local CargoUnits=CargoGroup:getUnits()
+for CargoUnitId,CargoUnit in pairs(CargoUnits)do
+local CargoUnitPos=CargoUnit:getPosition().p
+local ReferenceP=ReferencePosition.p
+if(((CargoUnitPos.x-ReferenceP.x)^2+(CargoUnitPos.z-ReferenceP.z)^2)^0.5<=Radius)then
+else
+Valid=false
+break
+end
+end
+return Valid
+end
+function routines.ValidateString(Variable,VariableName,Valid)
+if type(Variable)=="string"then
+if Variable==""then
+error("routines.ValidateString: error: "..VariableName.." must be filled out!")
+Valid=false
+end
+else
+error("routines.ValidateString: error: "..VariableName.." is not a string.")
+Valid=false
+end
+return Valid
+end
+function routines.ValidateNumber(Variable,VariableName,Valid)
+if type(Variable)=="number"then
+else
+error("routines.ValidateNumber: error: "..VariableName.." is not a number.")
+Valid=false
+end
+return Valid
+end
+function routines.ValidateGroup(Variable,VariableName,Valid)
+if Variable==nil then
+error("routines.ValidateGroup: error: "..VariableName.." is a nil value!")
+Valid=false
+end
+return Valid
+end
+function routines.ValidateZone(LandingZones,VariableName,Valid)
+if LandingZones==nil then
+error("routines.ValidateGroup: error: "..VariableName.." is a nil value!")
+Valid=false
+end
+if type(LandingZones)=="table"then
+for LandingZoneID,LandingZoneName in pairs(LandingZones)do
+if trigger.misc.getZone(LandingZoneName)==nil then
+error("routines.ValidateGroup: error: Zone "..LandingZoneName.." does not exist!")
+Valid=false
+break
+end
+end
+else
+if trigger.misc.getZone(LandingZones)==nil then
+error("routines.ValidateGroup: error: Zone "..LandingZones.." does not exist!")
+Valid=false
+end
+end
+return Valid
+end
+function routines.ValidateEnumeration(Variable,VariableName,Enum,Valid)
+local ValidVariable=false
+for EnumId,EnumData in pairs(Enum)do
+if Variable==EnumData then
+ValidVariable=true
+break
+end
+end
+if ValidVariable then
+else
+error('TransportValidateEnum: " .. VariableName .. " is not a valid type.'..Variable)
+Valid=false
+end
+return Valid
+end
+function routines.getGroupRoute(groupIdent,task)
+local gpId=groupIdent
+if type(groupIdent)=='string'and not tonumber(groupIdent)then
+gpId=_DATABASE.Templates.Groups[groupIdent].groupId
+end
+for coa_name,coa_data in pairs(env.mission.coalition)do
+if(coa_name=='red'or coa_name=='blue')and type(coa_data)=='table'then
+if coa_data.country then
+for cntry_id,cntry_data in pairs(coa_data.country)do
+for obj_type_name,obj_type_data in pairs(cntry_data)do
+if obj_type_name=="helicopter"or obj_type_name=="ship"or obj_type_name=="plane"or obj_type_name=="vehicle"then
+if((type(obj_type_data)=='table')and obj_type_data.group and(type(obj_type_data.group)=='table')and(#obj_type_data.group>0))then
+for group_num,group_data in pairs(obj_type_data.group)do
+if group_data and group_data.groupId==gpId then
+if group_data.route and group_data.route.points and#group_data.route.points>0 then
+local points={}
+for point_num,point in pairs(group_data.route.points)do
+local routeData={}
+if not point.point then
+routeData.x=point.x
+routeData.y=point.y
+else
+routeData.point=point.point
+end
+routeData.form=point.action
+routeData.speed=point.speed
+routeData.alt=point.alt
+routeData.alt_type=point.alt_type
+routeData.airdromeId=point.airdromeId
+routeData.helipadId=point.helipadId
+routeData.type=point.type
+routeData.action=point.action
+if task then
+routeData.task=point.task
+end
+points[point_num]=routeData
+end
+return points
+end
+return
+end
+end
+end
+end
+end
+end
+end
+end
+end
+end
+routines.ground.patrolRoute=function(vars)
+local tempRoute={}
+local useRoute={}
+local gpData=vars.gpData
+if type(gpData)=='string'then
+gpData=Group.getByName(gpData)
+end
+local useGroupRoute
+if not vars.useGroupRoute then
+useGroupRoute=vars.gpData
+else
+useGroupRoute=vars.useGroupRoute
+end
+local routeProvided=false
+if not vars.route then
+if useGroupRoute then
+tempRoute=routines.getGroupRoute(useGroupRoute)
+end
+else
+useRoute=vars.route
+local posStart=routines.getLeadPos(gpData)
+useRoute[1]=routines.ground.buildWP(posStart,useRoute[1].action,useRoute[1].speed)
+routeProvided=true
+end
+local overRideSpeed=vars.speed or'default'
+local pType=vars.pType
+local offRoadForm=vars.offRoadForm or'default'
+local onRoadForm=vars.onRoadForm or'default'
+if routeProvided==false and#tempRoute>0 then
+local posStart=routines.getLeadPos(gpData)
+useRoute[#useRoute+1]=routines.ground.buildWP(posStart,offRoadForm,overRideSpeed)
+for i=1,#tempRoute do
+local tempForm=tempRoute[i].action
+local tempSpeed=tempRoute[i].speed
+if offRoadForm=='default'then
+tempForm=tempRoute[i].action
+end
+if onRoadForm=='default'then
+onRoadForm='On Road'
+end
+if(string.lower(tempRoute[i].action)=='on road'or string.lower(tempRoute[i].action)=='onroad'or string.lower(tempRoute[i].action)=='on_road')then
+tempForm=onRoadForm
+else
+tempForm=offRoadForm
+end
+if type(overRideSpeed)=='number'then
+tempSpeed=overRideSpeed
+end
+useRoute[#useRoute+1]=routines.ground.buildWP(tempRoute[i],tempForm,tempSpeed)
+end
+if pType and string.lower(pType)=='doubleback'then
+local curRoute=routines.utils.deepCopy(useRoute)
+for i=#curRoute,2,-1 do
+useRoute[#useRoute+1]=routines.ground.buildWP(curRoute[i],curRoute[i].action,curRoute[i].speed)
+end
+end
+useRoute[1].action=useRoute[#useRoute].action
+end
+local cTask3={}
+local newPatrol={}
+newPatrol.route=useRoute
+newPatrol.gpData=gpData:getName()
+cTask3[#cTask3+1]='routines.ground.patrolRoute('
+cTask3[#cTask3+1]=routines.utils.oneLineSerialize(newPatrol)
+cTask3[#cTask3+1]=')'
+cTask3=table.concat(cTask3)
+local tempTask={
+id='WrappedAction',
+params={
+action={
+id='Script',
+params={
+command=cTask3,
+},
+},
+},
+}
+useRoute[#useRoute].task=tempTask
+routines.goRoute(gpData,useRoute)
+return
+end
+routines.ground.patrol=function(gpData,pType,form,speed)
+local vars={}
+if type(gpData)=='table'and gpData:getName()then
+gpData=gpData:getName()
+end
+vars.useGroupRoute=gpData
+vars.gpData=gpData
+vars.pType=pType
+vars.offRoadForm=form
+vars.speed=speed
+routines.ground.patrolRoute(vars)
+return
+end
+function routines.GetUnitHeight(CheckUnit)
+local UnitPoint=CheckUnit:getPoint()
+local UnitPosition={x=UnitPoint.x,y=UnitPoint.z}
+local UnitHeight=UnitPoint.y
+local LandHeight=land.getHeight(UnitPosition)
+return UnitHeight-LandHeight
+end
+Su34Status={status={}}
+boardMsgRed={statusMsg=""}
+boardMsgAll={timeMsg=""}
+SpawnSettings={}
+Su34MenuPath={}
+Su34Menus=0
+function Su34AttackCarlVinson(groupName)
+local groupSu34=Group.getByName(groupName)
+local controllerSu34=groupSu34.getController(groupSu34)
+local groupCarlVinson=Group.getByName("US Carl Vinson #001")
+controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE)
+controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
+if groupCarlVinson~=nil then
+controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupCarlVinson:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=true}})
+end
+Su34Status.status[groupName]=1
+MessageToRed(string.format('%s: ',groupName)..'Attacking carrier Carl Vinson. ',10,'RedStatus'..groupName)
+end
+function Su34AttackWest(groupName)
+local groupSu34=Group.getByName(groupName)
+local controllerSu34=groupSu34.getController(groupSu34)
+local groupShipWest1=Group.getByName("US Ship West #001")
+local groupShipWest2=Group.getByName("US Ship West #002")
+controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE)
+controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
+if groupShipWest1~=nil then
+controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupShipWest1:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=true}})
+end
+if groupShipWest2~=nil then
+controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupShipWest2:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=true}})
+end
+Su34Status.status[groupName]=2
+MessageToRed(string.format('%s: ',groupName)..'Attacking invading ships in the west. ',10,'RedStatus'..groupName)
+end
+function Su34AttackNorth(groupName)
+local groupSu34=Group.getByName(groupName)
+local controllerSu34=groupSu34.getController(groupSu34)
+local groupShipNorth1=Group.getByName("US Ship North #001")
+local groupShipNorth2=Group.getByName("US Ship North #002")
+local groupShipNorth3=Group.getByName("US Ship North #003")
+controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE)
+controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
+if groupShipNorth1~=nil then
+controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupShipNorth1:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=false}})
+end
+if groupShipNorth2~=nil then
+controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupShipNorth2:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=false}})
+end
+if groupShipNorth3~=nil then
+controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupShipNorth3:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=false}})
+end
+Su34Status.status[groupName]=3
+MessageToRed(string.format('%s: ',groupName)..'Attacking invading ships in the north. ',10,'RedStatus'..groupName)
+end
+function Su34Orbit(groupName)
+local groupSu34=Group.getByName(groupName)
+local controllerSu34=groupSu34:getController()
+controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_HOLD)
+controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
+controllerSu34:pushTask({id='ControlledTask',params={task={id='Orbit',params={pattern=AI.Task.OrbitPattern.RACE_TRACK}},stopCondition={duration=600}}})
+Su34Status.status[groupName]=4
+MessageToRed(string.format('%s: ',groupName)..'In orbit and awaiting further instructions. ',10,'RedStatus'..groupName)
+end
+function Su34TakeOff(groupName)
+local groupSu34=Group.getByName(groupName)
+local controllerSu34=groupSu34:getController()
+controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_HOLD)
+controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE)
+Su34Status.status[groupName]=8
+MessageToRed(string.format('%s: ',groupName)..'Take-Off. ',10,'RedStatus'..groupName)
+end
+function Su34Hold(groupName)
+local groupSu34=Group.getByName(groupName)
+local controllerSu34=groupSu34:getController()
+controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_HOLD)
+controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE)
+Su34Status.status[groupName]=5
+MessageToRed(string.format('%s: ',groupName)..'Holding Weapons. ',10,'RedStatus'..groupName)
+end
+function Su34RTB(groupName)
+Su34Status.status[groupName]=6
+MessageToRed(string.format('%s: ',groupName)..'Return to Krasnodar. ',10,'RedStatus'..groupName)
+end
+function Su34Destroyed(groupName)
+Su34Status.status[groupName]=7
+MessageToRed(string.format('%s: ',groupName)..'Destroyed. ',30,'RedStatus'..groupName)
+end
+function GroupAlive(groupName)
+local groupTest=Group.getByName(groupName)
+local groupExists=false
+if groupTest then
+groupExists=groupTest:isExist()
+end
+return groupExists
+end
+function Su34IsDead()
+end
+function Su34OverviewStatus()
+local msg=""
+local currentStatus=0
+local Exists=false
+for groupName,currentStatus in pairs(Su34Status.status)do
+env.info(('Su34 Overview Status: GroupName = '..groupName))
+Alive=GroupAlive(groupName)
+if Alive then
+if currentStatus==1 then
+msg=msg..string.format("%s: ",groupName)
+msg=msg.."Attacking carrier Carl Vinson. "
+elseif currentStatus==2 then
+msg=msg..string.format("%s: ",groupName)
+msg=msg.."Attacking supporting ships in the west. "
+elseif currentStatus==3 then
+msg=msg..string.format("%s: ",groupName)
+msg=msg.."Attacking invading ships in the north. "
+elseif currentStatus==4 then
+msg=msg..string.format("%s: ",groupName)
+msg=msg.."In orbit and awaiting further instructions. "
+elseif currentStatus==5 then
+msg=msg..string.format("%s: ",groupName)
+msg=msg.."Holding Weapons. "
+elseif currentStatus==6 then
+msg=msg..string.format("%s: ",groupName)
+msg=msg.."Return to Krasnodar. "
+elseif currentStatus==7 then
+msg=msg..string.format("%s: ",groupName)
+msg=msg.."Destroyed. "
+elseif currentStatus==8 then
+msg=msg..string.format("%s: ",groupName)
+msg=msg.."Take-Off. "
+end
+else
+if currentStatus==7 then
+msg=msg..string.format("%s: ",groupName)
+msg=msg.."Destroyed. "
+else
+Su34Destroyed(groupName)
+end
+end
+end
+boardMsgRed.statusMsg=msg
+end
+function UpdateBoardMsg()
+Su34OverviewStatus()
+MessageToRed(boardMsgRed.statusMsg,15,'RedStatus')
+end
+function MusicReset(flg)
+trigger.action.setUserFlag(95,flg)
+end
+function PlaneActivate(groupNameFormat,flg)
+local groupName=groupNameFormat..string.format("#%03d",trigger.misc.getUserFlag(flg))
+trigger.action.activateGroup(Group.getByName(groupName))
+end
+function Su34Menu(groupName)
+local groupSu34=Group.getByName(groupName)
+if Su34Status.status[groupName]==1 or
+Su34Status.status[groupName]==2 or
+Su34Status.status[groupName]==3 or
+Su34Status.status[groupName]==4 or
+Su34Status.status[groupName]==5 then
+if Su34MenuPath[groupName]==nil then
+if planeMenuPath==nil then
+planeMenuPath=missionCommands.addSubMenuForCoalition(
+coalition.side.RED,
+"SU-34 anti-ship flights",
+nil
+)
+end
+Su34MenuPath[groupName]=missionCommands.addSubMenuForCoalition(
+coalition.side.RED,
+"Flight "..groupName,
+planeMenuPath
+)
+missionCommands.addCommandForCoalition(
+coalition.side.RED,
+"Attack carrier Carl Vinson",
+Su34MenuPath[groupName],
+Su34AttackCarlVinson,
+groupName
+)
+missionCommands.addCommandForCoalition(
+coalition.side.RED,
+"Attack ships in the west",
+Su34MenuPath[groupName],
+Su34AttackWest,
+groupName
+)
+missionCommands.addCommandForCoalition(
+coalition.side.RED,
+"Attack ships in the north",
+Su34MenuPath[groupName],
+Su34AttackNorth,
+groupName
+)
+missionCommands.addCommandForCoalition(
+coalition.side.RED,
+"Hold position and await instructions",
+Su34MenuPath[groupName],
+Su34Orbit,
+groupName
+)
+missionCommands.addCommandForCoalition(
+coalition.side.RED,
+"Report status",
+Su34MenuPath[groupName],
+Su34OverviewStatus
+)
+end
+else
+if Su34MenuPath[groupName]then
+missionCommands.removeItemForCoalition(coalition.side.RED,Su34MenuPath[groupName])
+end
+end
+end
+function ChooseInfantry(TeleportPrefixTable,TeleportMax)
+TeleportPrefixTableCount=#TeleportPrefixTable
+TeleportPrefixTableIndex=math.random(1,TeleportPrefixTableCount)
+local TeleportFound=false
+local TeleportLoop=true
+local Index=TeleportPrefixTableIndex
+local TeleportPrefix=''
+while TeleportLoop do
+TeleportPrefix=TeleportPrefixTable[Index]
+if SpawnSettings[TeleportPrefix]then
+if SpawnSettings[TeleportPrefix]['SpawnCount']-10 then
+local PlayerFound=false
+local MusicStart=0
+local MusicTime=0
+for SndQueueIdx,SndQueue in pairs(_MusicTable.Queue)do
+if SndQueue.PlayerName==PlayerName then
+PlayerFound=true
+MusicStart=SndQueue.Start
+MusicTime=_MusicTable.Files[SndQueue.Ref].Time
+break
+end
+end
+if PlayerFound then
+if MusicStart+MusicTime<=timer.getTime()then
+MusicOut=true
+end
+else
+MusicOut=true
+end
+end
+if MusicOut then
+else
+end
+return MusicOut
+end
+function MusicScheduler()
+if _MusicTable['Queue']~=nil and _MusicTable.FileCnt>0 then
+for SndQueueIdx,SndQueue in pairs(_MusicTable.Queue)do
+if SndQueue.Continue then
+if MusicCanStart(SndQueue.PlayerName)then
+MusicToPlayer('',SndQueue.PlayerName,true)
+end
+end
+end
+end
+end
+env.info(('Init: Scripts Loaded v1.1'))
+SMOKECOLOR=trigger.smokeColor
+FLARECOLOR=trigger.flareColor
+UTILS={
+_MarkID=1
+}
+UTILS.IsInstanceOf=function(object,className)
+if not type(className)=='string'then
+if type(className)=='table'and className.IsInstanceOf~=nil then
+className=className.ClassName
+else
+local err_str='className parameter should be a string; parameter received: '..type(className)
+self:E(err_str)
+return false
+end
+end
+if type(object)=='table'and object.IsInstanceOf~=nil then
+return object:IsInstanceOf(className)
+else
+local basicDataTypes={'string','number','function','boolean','nil','table'}
+for _,basicDataType in ipairs(basicDataTypes)do
+if className==basicDataType then
+return type(object)==basicDataType
+end
+end
+end
+return false
+end
+UTILS.DeepCopy=function(object)
+local lookup_table={}
+local function _copy(object)
+if type(object)~="table"then
+return object
+elseif lookup_table[object]then
+return lookup_table[object]
+end
+local new_table={}
+lookup_table[object]=new_table
+for index,value in pairs(object)do
+new_table[_copy(index)]=_copy(value)
+end
+return setmetatable(new_table,getmetatable(object))
+end
+local objectreturn=_copy(object)
+return objectreturn
+end
+UTILS.OneLineSerialize=function(tbl)
+lookup_table={}
+local function _Serialize(tbl)
+if type(tbl)=='table'then
+if lookup_table[tbl]then
+return lookup_table[object]
+end
+local tbl_str={}
+lookup_table[tbl]=tbl_str
+tbl_str[#tbl_str+1]='{'
+for ind,val in pairs(tbl)do
+local ind_str={}
+if type(ind)=="number"then
+ind_str[#ind_str+1]='['
+ind_str[#ind_str+1]=tostring(ind)
+ind_str[#ind_str+1]=']='
+else
+ind_str[#ind_str+1]='['
+ind_str[#ind_str+1]=routines.utils.basicSerialize(ind)
+ind_str[#ind_str+1]=']='
+end
+local val_str={}
+if((type(val)=='number')or(type(val)=='boolean'))then
+val_str[#val_str+1]=tostring(val)
+val_str[#val_str+1]=','
+tbl_str[#tbl_str+1]=table.concat(ind_str)
+tbl_str[#tbl_str+1]=table.concat(val_str)
+elseif type(val)=='string'then
+val_str[#val_str+1]=routines.utils.basicSerialize(val)
+val_str[#val_str+1]=','
+tbl_str[#tbl_str+1]=table.concat(ind_str)
+tbl_str[#tbl_str+1]=table.concat(val_str)
+elseif type(val)=='nil'then
+val_str[#val_str+1]='nil,'
+tbl_str[#tbl_str+1]=table.concat(ind_str)
+tbl_str[#tbl_str+1]=table.concat(val_str)
+elseif type(val)=='table'then
+if ind=="__index"then
+else
+val_str[#val_str+1]=_Serialize(val)
+val_str[#val_str+1]=','
+tbl_str[#tbl_str+1]=table.concat(ind_str)
+tbl_str[#tbl_str+1]=table.concat(val_str)
+end
+elseif type(val)=='function'then
+tbl_str[#tbl_str+1]="f() "..tostring(ind)
+tbl_str[#tbl_str+1]=','
+else
+env.info('unable to serialize value type '..routines.utils.basicSerialize(type(val))..' at index '..tostring(ind))
+env.info(debug.traceback())
+end
+end
+tbl_str[#tbl_str+1]='}'
+return table.concat(tbl_str)
+else
+return tostring(tbl)
+end
+end
+local objectreturn=_Serialize(tbl)
+return objectreturn
+end
+UTILS.BasicSerialize=function(s)
+if s==nil then
+return"\"\""
+else
+if((type(s)=='number')or(type(s)=='boolean')or(type(s)=='function')or(type(s)=='table')or(type(s)=='userdata'))then
+return tostring(s)
+elseif type(s)=='string'then
+s=string.format('%q',s)
+return s
+end
+end
+end
+UTILS.ToDegree=function(angle)
+return angle*180/math.pi
+end
+UTILS.ToRadian=function(angle)
+return angle*math.pi/180
+end
+UTILS.MetersToNM=function(meters)
+return meters/1852
+end
+UTILS.MetersToFeet=function(meters)
+return meters/0.3048
+end
+UTILS.NMToMeters=function(NM)
+return NM*1852
+end
+UTILS.FeetToMeters=function(feet)
+return feet*0.3048
+end
+UTILS.MpsToKnots=function(mps)
+return mps*3600/1852
+end
+UTILS.MpsToKmph=function(mps)
+return mps*3.6
+end
+UTILS.KnotsToMps=function(knots)
+return knots*1852/3600
+end
+UTILS.KnotsToKmph=function(knots)
+return knots*1.852
+end
+UTILS.KmphToMps=function(kmph)
+return kmph/3.6
+end
+UTILS.tostringLL=function(lat,lon,acc,DMS)
+local latHemi,lonHemi
+if lat>0 then
+latHemi='N'
+else
+latHemi='S'
+end
+if lon>0 then
+lonHemi='E'
+else
+lonHemi='W'
+end
+lat=math.abs(lat)
+lon=math.abs(lon)
+local latDeg=math.floor(lat)
+local latMin=(lat-latDeg)*60
+local lonDeg=math.floor(lon)
+local lonMin=(lon-lonDeg)*60
+if DMS then
+local oldLatMin=latMin
+latMin=math.floor(latMin)
+local latSec=UTILS.Round((oldLatMin-latMin)*60,acc)
+local oldLonMin=lonMin
+lonMin=math.floor(lonMin)
+local lonSec=UTILS.Round((oldLonMin-lonMin)*60,acc)
+if latSec==60 then
+latSec=0
+latMin=latMin+1
+end
+if lonSec==60 then
+lonSec=0
+lonMin=lonMin+1
+end
+local secFrmtStr
+secFrmtStr='%02d'
+return string.format('%02d',latDeg)..' '..string.format('%02d',latMin)..'\' '..string.format(secFrmtStr,latSec)..'"'..latHemi..' '
+..string.format('%02d',lonDeg)..' '..string.format('%02d',lonMin)..'\' '..string.format(secFrmtStr,lonSec)..'"'..lonHemi
+else
+latMin=UTILS.Round(latMin,acc)
+lonMin=UTILS.Round(lonMin,acc)
+if latMin==60 then
+latMin=0
+latDeg=latDeg+1
+end
+if lonMin==60 then
+lonMin=0
+lonDeg=lonDeg+1
+end
+local minFrmtStr
+if acc<=0 then
+minFrmtStr='%02d'
+else
+local width=3+acc
+minFrmtStr='%0'..width..'.'..acc..'f'
+end
+return string.format('%02d',latDeg)..' '..string.format(minFrmtStr,latMin)..'\''..latHemi..' '
+..string.format('%02d',lonDeg)..' '..string.format(minFrmtStr,lonMin)..'\''..lonHemi
+end
+end
+UTILS.tostringMGRS=function(MGRS,acc)
+if acc==0 then
+return MGRS.UTMZone..' '..MGRS.MGRSDigraph
+else
+return MGRS.UTMZone..' '..MGRS.MGRSDigraph..' '..string.format('%0'..acc..'d',UTILS.Round(MGRS.Easting/(10^(5-acc)),0))
+..' '..string.format('%0'..acc..'d',UTILS.Round(MGRS.Northing/(10^(5-acc)),0))
+end
+end
+function UTILS.Round(num,idp)
+local mult=10^(idp or 0)
+return math.floor(num*mult+0.5)/mult
+end
+function UTILS.DoString(s)
+local f,err=loadstring(s)
+if f then
+return true,f()
+else
+return false,err
+end
+end
+function UTILS.spairs(t,order)
+local keys={}
+for k in pairs(t)do keys[#keys+1]=k end
+if order then
+table.sort(keys,function(a,b)return order(t,a,b)end)
+else
+table.sort(keys)
+end
+local i=0
+return function()
+i=i+1
+if keys[i]then
+return keys[i],t[keys[i]]
+end
+end
+end
+function UTILS.GetMarkID()
+UTILS._MarkID=UTILS._MarkID+1
+return UTILS._MarkID
+end
+function UTILS.IsInRadius(InVec2,Vec2,Radius)
+local InRadius=((InVec2.x-Vec2.x)^2+(InVec2.y-Vec2.y)^2)^0.5<=Radius
+return InRadius
+end
+function UTILS.IsInSphere(InVec3,Vec3,Radius)
+local InSphere=((InVec3.x-Vec3.x)^2+(InVec3.y-Vec3.y)^2+(InVec3.z-Vec3.z)^2)^0.5<=Radius
+return InSphere
+end
+local _TraceOnOff=true
+local _TraceLevel=1
+local _TraceAll=false
+local _TraceClass={}
+local _TraceClassMethod={}
+local _ClassID=0
+BASE={
+ClassName="BASE",
+ClassID=0,
+Events={},
+States={},
+}
+BASE.__={}
+BASE._={
+Schedules={}
+}
+FORMATION={
+Cone="Cone",
+Vee="Vee"
+}
+function BASE:New()
+local self=routines.utils.deepCopy(self)
+_ClassID=_ClassID+1
+self.ClassID=_ClassID
+return self
+end
+function BASE:Inherit(Child,Parent)
+local Child=routines.utils.deepCopy(Child)
+if Child~=nil then
+if rawget(Child,"__")then
+setmetatable(Child,{__index=Child.__})
+setmetatable(Child.__,{__index=Parent})
+else
+setmetatable(Child,{__index=Parent})
+end
+end
+return Child
+end
+local function getParent(Child)
+local Parent=nil
+if Child.ClassName=='BASE'then
+Parent=nil
+else
+if rawget(Child,"__")then
+Parent=getmetatable(Child.__).__index
+else
+Parent=getmetatable(Child).__index
+end
+end
+return Parent
+end
+function BASE:GetParent(Child,FromClass)
+local Parent
+if Child.ClassName=='BASE'then
+Parent=nil
+else
+self:E({FromClass=FromClass})
+self:E({Child=Child.ClassName})
+if FromClass then
+while(Child.ClassName~="BASE"and Child.ClassName~=FromClass.ClassName)do
+Child=getParent(Child)
+self:E({Child.ClassName})
+end
+end
+if Child.ClassName=='BASE'then
+Parent=nil
+else
+Parent=getParent(Child)
+end
+end
+self:E({Parent.ClassName})
+return Parent
+end
+function BASE:IsInstanceOf(ClassName)
+if type(ClassName)~='string'then
+if type(ClassName)=='table'and ClassName.ClassName~=nil then
+ClassName=ClassName.ClassName
+else
+local err_str='className parameter should be a string; parameter received: '..type(ClassName)
+self:E(err_str)
+return false
+end
+end
+ClassName=string.upper(ClassName)
+if string.upper(self.ClassName)==ClassName then
+return true
+end
+local Parent=getParent(self)
+while Parent do
+if string.upper(Parent.ClassName)==ClassName then
+return true
+end
+Parent=getParent(Parent)
+end
+return false
+end
+function BASE:GetClassNameAndID()
+return string.format('%s#%09d',self.ClassName,self.ClassID)
+end
+function BASE:GetClassName()
+return self.ClassName
+end
+function BASE:GetClassID()
+return self.ClassID
+end
+do
+function BASE:EventDispatcher()
+return _EVENTDISPATCHER
+end
+function BASE:GetEventPriority()
+return self._.EventPriority or 5
+end
+function BASE:SetEventPriority(EventPriority)
+self._.EventPriority=EventPriority
+end
+function BASE:EventRemoveAll()
+self:EventDispatcher():RemoveAll(self)
+return self
+end
+function BASE:HandleEvent(Event,EventFunction)
+self:EventDispatcher():OnEventGeneric(EventFunction,self,Event)
+return self
+end
+function BASE:UnHandleEvent(Event)
+self:EventDispatcher():RemoveEvent(self,Event)
+return self
+end
+end
+function BASE:CreateEventBirth(EventTime,Initiator,IniUnitName,place,subplace)
+self:F({EventTime,Initiator,IniUnitName,place,subplace})
+local Event={
+id=world.event.S_EVENT_BIRTH,
+time=EventTime,
+initiator=Initiator,
+IniUnitName=IniUnitName,
+place=place,
+subplace=subplace
+}
+world.onEvent(Event)
+end
+function BASE:CreateEventCrash(EventTime,Initiator)
+self:F({EventTime,Initiator})
+local Event={
+id=world.event.S_EVENT_CRASH,
+time=EventTime,
+initiator=Initiator,
+}
+world.onEvent(Event)
+end
+function BASE:CreateEventTakeoff(EventTime,Initiator)
+self:F({EventTime,Initiator})
+local Event={
+id=world.event.S_EVENT_TAKEOFF,
+time=EventTime,
+initiator=Initiator,
+}
+world.onEvent(Event)
+end
+function BASE:onEvent(event)
+if self then
+for EventID,EventObject in pairs(self.Events)do
+if EventObject.EventEnabled then
+if event.id==EventObject.Event then
+if self==EventObject.Self then
+if event.initiator and event.initiator:isExist()then
+event.IniUnitName=event.initiator:getName()
+end
+if event.target and event.target:isExist()then
+event.TgtUnitName=event.target:getName()
+end
+end
+end
+end
+end
+end
+end
+do
+function BASE:ScheduleOnce(Start,SchedulerFunction,...)
+self:F2({Start})
+self:T3({...})
+local ObjectName="-"
+ObjectName=self.ClassName..self.ClassID
+self:F3({"ScheduleOnce: ",ObjectName,Start})
+self.SchedulerObject=self
+local ScheduleID=_SCHEDULEDISPATCHER:AddSchedule(
+self,
+SchedulerFunction,
+{...},
+Start,
+nil,
+nil,
+nil
+)
+self._.Schedules[#self.Schedules+1]=ScheduleID
+return self._.Schedules
+end
+function BASE:ScheduleRepeat(Start,Repeat,RandomizeFactor,Stop,SchedulerFunction,...)
+self:F2({Start})
+self:T3({...})
+local ObjectName="-"
+ObjectName=self.ClassName..self.ClassID
+self:F3({"ScheduleRepeat: ",ObjectName,Start,Repeat,RandomizeFactor,Stop})
+self.SchedulerObject=self
+local ScheduleID=_SCHEDULEDISPATCHER:AddSchedule(
+self,
+SchedulerFunction,
+{...},
+Start,
+Repeat,
+RandomizeFactor,
+Stop
+)
+self._.Schedules[SchedulerFunction]=ScheduleID
+return self._.Schedules
+end
+function BASE:ScheduleStop(SchedulerFunction)
+self:F3({"ScheduleStop:"})
+_SCHEDULEDISPATCHER:Stop(self,self._.Schedules[SchedulerFunction])
+end
+end
+function BASE:SetState(Object,Key,Value)
+local ClassNameAndID=Object:GetClassNameAndID()
+self.States[ClassNameAndID]=self.States[ClassNameAndID]or{}
+self.States[ClassNameAndID][Key]=Value
+return self.States[ClassNameAndID][Key]
+end
+function BASE:GetState(Object,Key)
+local ClassNameAndID=Object:GetClassNameAndID()
+if self.States[ClassNameAndID]then
+local Value=self.States[ClassNameAndID][Key]or false
+return Value
+end
+return nil
+end
+function BASE:ClearState(Object,StateName)
+local ClassNameAndID=Object:GetClassNameAndID()
+if self.States[ClassNameAndID]then
+self.States[ClassNameAndID][StateName]=nil
+end
+end
+function BASE:TraceOnOff(TraceOnOff)
+_TraceOnOff=TraceOnOff
+end
+function BASE:IsTrace()
+if debug and(_TraceAll==true)or(_TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName])then
+return true
+else
+return false
+end
+end
+function BASE:TraceLevel(Level)
+_TraceLevel=Level
+self:E("Tracing level "..Level)
+end
+function BASE:TraceAll(TraceAll)
+_TraceAll=TraceAll
+if _TraceAll then
+self:E("Tracing all methods in MOOSE ")
+else
+self:E("Switched off tracing all methods in MOOSE")
+end
+end
+function BASE:TraceClass(Class)
+_TraceClass[Class]=true
+_TraceClassMethod[Class]={}
+self:E("Tracing class "..Class)
+end
+function BASE:TraceClassMethod(Class,Method)
+if not _TraceClassMethod[Class]then
+_TraceClassMethod[Class]={}
+_TraceClassMethod[Class].Method={}
+end
+_TraceClassMethod[Class].Method[Method]=true
+self:E("Tracing method "..Method.." of class "..Class)
+end
+function BASE:_F(Arguments,DebugInfoCurrentParam,DebugInfoFromParam)
+if debug and(_TraceAll==true)or(_TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName])then
+local DebugInfoCurrent=DebugInfoCurrentParam and DebugInfoCurrentParam or debug.getinfo(2,"nl")
+local DebugInfoFrom=DebugInfoFromParam and DebugInfoFromParam or debug.getinfo(3,"l")
+local Function="function"
+if DebugInfoCurrent.name then
+Function=DebugInfoCurrent.name
+end
+if _TraceAll==true or _TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName].Method[Function]then
+local LineCurrent=0
+if DebugInfoCurrent.currentline then
+LineCurrent=DebugInfoCurrent.currentline
+end
+local LineFrom=0
+if DebugInfoFrom then
+LineFrom=DebugInfoFrom.currentline
+end
+env.info(string.format("%6d(%6d)/%1s:%20s%05d.%s(%s)",LineCurrent,LineFrom,"F",self.ClassName,self.ClassID,Function,routines.utils.oneLineSerialize(Arguments)))
+end
+end
+end
+function BASE:F(Arguments)
+if debug and _TraceOnOff then
+local DebugInfoCurrent=debug.getinfo(2,"nl")
+local DebugInfoFrom=debug.getinfo(3,"l")
+if _TraceLevel>=1 then
+self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom)
+end
+end
+end
+function BASE:F2(Arguments)
+if debug and _TraceOnOff then
+local DebugInfoCurrent=debug.getinfo(2,"nl")
+local DebugInfoFrom=debug.getinfo(3,"l")
+if _TraceLevel>=2 then
+self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom)
+end
+end
+end
+function BASE:F3(Arguments)
+if debug and _TraceOnOff then
+local DebugInfoCurrent=debug.getinfo(2,"nl")
+local DebugInfoFrom=debug.getinfo(3,"l")
+if _TraceLevel>=3 then
+self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom)
+end
+end
+end
+function BASE:_T(Arguments,DebugInfoCurrentParam,DebugInfoFromParam)
+if debug and(_TraceAll==true)or(_TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName])then
+local DebugInfoCurrent=DebugInfoCurrentParam and DebugInfoCurrentParam or debug.getinfo(2,"nl")
+local DebugInfoFrom=DebugInfoFromParam and DebugInfoFromParam or debug.getinfo(3,"l")
+local Function="function"
+if DebugInfoCurrent.name then
+Function=DebugInfoCurrent.name
+end
+if _TraceAll==true or _TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName].Method[Function]then
+local LineCurrent=0
+if DebugInfoCurrent.currentline then
+LineCurrent=DebugInfoCurrent.currentline
+end
+local LineFrom=0
+if DebugInfoFrom then
+LineFrom=DebugInfoFrom.currentline
+end
+env.info(string.format("%6d(%6d)/%1s:%20s%05d.%s",LineCurrent,LineFrom,"T",self.ClassName,self.ClassID,routines.utils.oneLineSerialize(Arguments)))
+end
+end
+end
+function BASE:T(Arguments)
+if debug and _TraceOnOff then
+local DebugInfoCurrent=debug.getinfo(2,"nl")
+local DebugInfoFrom=debug.getinfo(3,"l")
+if _TraceLevel>=1 then
+self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom)
+end
+end
+end
+function BASE:T2(Arguments)
+if debug and _TraceOnOff then
+local DebugInfoCurrent=debug.getinfo(2,"nl")
+local DebugInfoFrom=debug.getinfo(3,"l")
+if _TraceLevel>=2 then
+self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom)
+end
+end
+end
+function BASE:T3(Arguments)
+if debug and _TraceOnOff then
+local DebugInfoCurrent=debug.getinfo(2,"nl")
+local DebugInfoFrom=debug.getinfo(3,"l")
+if _TraceLevel>=3 then
+self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom)
+end
+end
+end
+function BASE:E(Arguments)
+if debug then
+local DebugInfoCurrent=debug.getinfo(2,"nl")
+local DebugInfoFrom=debug.getinfo(3,"l")
+local Function="function"
+if DebugInfoCurrent.name then
+Function=DebugInfoCurrent.name
+end
+local LineCurrent=DebugInfoCurrent.currentline
+local LineFrom=-1
+if DebugInfoFrom then
+LineFrom=DebugInfoFrom.currentline
+end
+env.info(string.format("%6d(%6d)/%1s:%20s%05d.%s(%s)",LineCurrent,LineFrom,"E",self.ClassName,self.ClassID,Function,routines.utils.oneLineSerialize(Arguments)))
+end
+end
+REPORT={
+ClassName="REPORT",
+Title="",
+}
+function REPORT:New(Title)
+local self=BASE:Inherit(self,BASE:New())
+self.Report={}
+self:SetTitle(Title or"")
+self:SetIndent(3)
+return self
+end
+function REPORT:HasText()
+return#self.Report>0
+end
+function REPORT:SetIndent(Indent)
+self.Indent=Indent
+return self
+end
+function REPORT:Add(Text)
+self.Report[#self.Report+1]=Text
+return self
+end
+function REPORT:AddIndent(Text)
+self.Report[#self.Report+1]=string.rep(" ",self.Indent)..Text:gsub("\n","\n"..string.rep(" ",self.Indent))
+return self
+end
+function REPORT:Text(Delimiter)
+Delimiter=Delimiter or"\n"
+local ReportText=(self.Title~=""and self.Title..Delimiter or self.Title)..table.concat(self.Report,Delimiter)or""
+return ReportText
+end
+function REPORT:SetTitle(Title)
+self.Title=Title
+return self
+end
+function REPORT:GetCount()
+return#self.Report
+end
+SCHEDULER={
+ClassName="SCHEDULER",
+Schedules={},
+}
+function SCHEDULER:New(SchedulerObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop)
+local self=BASE:Inherit(self,BASE:New())
+self:F2({Start,Repeat,RandomizeFactor,Stop})
+local ScheduleID=nil
+self.MasterObject=SchedulerObject
+if SchedulerFunction then
+ScheduleID=self:Schedule(SchedulerObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop)
+end
+return self,ScheduleID
+end
+function SCHEDULER:Schedule(SchedulerObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop)
+self:F2({Start,Repeat,RandomizeFactor,Stop})
+self:T3({SchedulerArguments})
+local ObjectName="-"
+if SchedulerObject and SchedulerObject.ClassName and SchedulerObject.ClassID then
+ObjectName=SchedulerObject.ClassName..SchedulerObject.ClassID
+end
+self:F3({"Schedule :",ObjectName,tostring(SchedulerObject),Start,Repeat,RandomizeFactor,Stop})
+self.SchedulerObject=SchedulerObject
+local ScheduleID=_SCHEDULEDISPATCHER:AddSchedule(
+self,
+SchedulerFunction,
+SchedulerArguments,
+Start,
+Repeat,
+RandomizeFactor,
+Stop
+)
+self.Schedules[#self.Schedules+1]=ScheduleID
+return ScheduleID
+end
+function SCHEDULER:Start(ScheduleID)
+self:F3({ScheduleID})
+_SCHEDULEDISPATCHER:Start(self,ScheduleID)
+end
+function SCHEDULER:Stop(ScheduleID)
+self:F3({ScheduleID})
+_SCHEDULEDISPATCHER:Stop(self,ScheduleID)
+end
+function SCHEDULER:Remove(ScheduleID)
+self:F3({ScheduleID})
+_SCHEDULEDISPATCHER:Remove(self,ScheduleID)
+end
+function SCHEDULER:Clear()
+self:F3()
+_SCHEDULEDISPATCHER:Clear(self)
+end
+SCHEDULEDISPATCHER={
+ClassName="SCHEDULEDISPATCHER",
+CallID=0,
+}
+function SCHEDULEDISPATCHER:New()
+local self=BASE:Inherit(self,BASE:New())
+self:F3()
+return self
+end
+function SCHEDULEDISPATCHER:AddSchedule(Scheduler,ScheduleFunction,ScheduleArguments,Start,Repeat,Randomize,Stop)
+self:F2({Scheduler,ScheduleFunction,ScheduleArguments,Start,Repeat,Randomize,Stop})
+self.CallID=self.CallID+1
+local CallID=self.CallID.."#"..(Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID()or"")or""
+self.PersistentSchedulers=self.PersistentSchedulers or{}
+self.ObjectSchedulers=self.ObjectSchedulers or setmetatable({},{__mode="v"})
+if Scheduler.MasterObject then
+self.ObjectSchedulers[CallID]=Scheduler
+self:F3({CallID=CallID,ObjectScheduler=tostring(self.ObjectSchedulers[CallID]),MasterObject=tostring(Scheduler.MasterObject)})
+else
+self.PersistentSchedulers[CallID]=Scheduler
+self:F3({CallID=CallID,PersistentScheduler=self.PersistentSchedulers[CallID]})
+end
+self.Schedule=self.Schedule or setmetatable({},{__mode="k"})
+self.Schedule[Scheduler]=self.Schedule[Scheduler]or{}
+self.Schedule[Scheduler][CallID]={}
+self.Schedule[Scheduler][CallID].Function=ScheduleFunction
+self.Schedule[Scheduler][CallID].Arguments=ScheduleArguments
+self.Schedule[Scheduler][CallID].StartTime=timer.getTime()+(Start or 0)
+self.Schedule[Scheduler][CallID].Start=Start+.1
+self.Schedule[Scheduler][CallID].Repeat=Repeat or 0
+self.Schedule[Scheduler][CallID].Randomize=Randomize or 0
+self.Schedule[Scheduler][CallID].Stop=Stop
+self:T3(self.Schedule[Scheduler][CallID])
+self.Schedule[Scheduler][CallID].CallHandler=function(CallID)
+self:F2(CallID)
+local ErrorHandler=function(errmsg)
+env.info("Error in timer function: "..errmsg)
+if debug~=nil then
+env.info(debug.traceback())
+end
+return errmsg
+end
+local Scheduler=self.ObjectSchedulers[CallID]
+if not Scheduler then
+Scheduler=self.PersistentSchedulers[CallID]
+end
+if Scheduler then
+local MasterObject=tostring(Scheduler.MasterObject)
+local Schedule=self.Schedule[Scheduler][CallID]
+local ScheduleObject=Scheduler.SchedulerObject
+local ScheduleFunction=Schedule.Function
+local ScheduleArguments=Schedule.Arguments
+local Start=Schedule.Start
+local Repeat=Schedule.Repeat or 0
+local Randomize=Schedule.Randomize or 0
+local Stop=Schedule.Stop or 0
+local ScheduleID=Schedule.ScheduleID
+local Status,Result
+if ScheduleObject then
+local function Timer()
+return ScheduleFunction(ScheduleObject,unpack(ScheduleArguments))
+end
+Status,Result=xpcall(Timer,ErrorHandler)
+else
+local function Timer()
+return ScheduleFunction(unpack(ScheduleArguments))
+end
+Status,Result=xpcall(Timer,ErrorHandler)
+end
+local CurrentTime=timer.getTime()
+local StartTime=Schedule.StartTime
+self:F3({Master=MasterObject,CurrentTime=CurrentTime,StartTime=StartTime,Start=Start,Repeat=Repeat,Randomize=Randomize,Stop=Stop})
+if Status and((Result==nil)or(Result and Result~=false))then
+if Repeat~=0 and((Stop==0)or(Stop~=0 and CurrentTime<=StartTime+Stop))then
+local ScheduleTime=
+CurrentTime+
+Repeat+
+math.random(
+-(Randomize*Repeat/2),
+(Randomize*Repeat/2)
+)+
+0.01
+return ScheduleTime
+else
+self:Stop(Scheduler,CallID)
+end
+else
+self:Stop(Scheduler,CallID)
+end
+else
+self:E("Scheduled obsolete call for CallID: "..CallID)
+end
+return nil
+end
+self:Start(Scheduler,CallID)
+return CallID
+end
+function SCHEDULEDISPATCHER:RemoveSchedule(Scheduler,CallID)
+self:F2({Remove=CallID,Scheduler=Scheduler})
+if CallID then
+self:Stop(Scheduler,CallID)
+self.Schedule[Scheduler][CallID]=nil
+end
+end
+function SCHEDULEDISPATCHER:Start(Scheduler,CallID)
+self:F2({Start=CallID,Scheduler=Scheduler})
+if CallID then
+local Schedule=self.Schedule[Scheduler]
+if not Schedule[CallID].ScheduleID then
+Schedule[CallID].StartTime=timer.getTime()
+Schedule[CallID].ScheduleID=timer.scheduleFunction(
+Schedule[CallID].CallHandler,
+CallID,
+timer.getTime()+Schedule[CallID].Start
+)
+end
+else
+for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do
+self:Start(Scheduler,CallID)
+end
+end
+end
+function SCHEDULEDISPATCHER:Stop(Scheduler,CallID)
+self:F2({Stop=CallID,Scheduler=Scheduler})
+if CallID then
+local Schedule=self.Schedule[Scheduler]
+if Schedule[CallID].ScheduleID then
+timer.removeFunction(Schedule[CallID].ScheduleID)
+Schedule[CallID].ScheduleID=nil
+end
+else
+for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do
+self:Stop(Scheduler,CallID)
+end
+end
+end
+function SCHEDULEDISPATCHER:Clear(Scheduler)
+self:F2({Scheduler=Scheduler})
+for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do
+self:Stop(Scheduler,CallID)
+end
+end
+EVENT={
+ClassName="EVENT",
+ClassID=0,
+}
+world.event.S_EVENT_NEW_CARGO=world.event.S_EVENT_MAX+1000
+world.event.S_EVENT_DELETE_CARGO=world.event.S_EVENT_MAX+1001
+EVENTS={
+Shot=world.event.S_EVENT_SHOT,
+Hit=world.event.S_EVENT_HIT,
+Takeoff=world.event.S_EVENT_TAKEOFF,
+Land=world.event.S_EVENT_LAND,
+Crash=world.event.S_EVENT_CRASH,
+Ejection=world.event.S_EVENT_EJECTION,
+Refueling=world.event.S_EVENT_REFUELING,
+Dead=world.event.S_EVENT_DEAD,
+PilotDead=world.event.S_EVENT_PILOT_DEAD,
+BaseCaptured=world.event.S_EVENT_BASE_CAPTURED,
+MissionStart=world.event.S_EVENT_MISSION_START,
+MissionEnd=world.event.S_EVENT_MISSION_END,
+TookControl=world.event.S_EVENT_TOOK_CONTROL,
+RefuelingStop=world.event.S_EVENT_REFUELING_STOP,
+Birth=world.event.S_EVENT_BIRTH,
+HumanFailure=world.event.S_EVENT_HUMAN_FAILURE,
+EngineStartup=world.event.S_EVENT_ENGINE_STARTUP,
+EngineShutdown=world.event.S_EVENT_ENGINE_SHUTDOWN,
+PlayerEnterUnit=world.event.S_EVENT_PLAYER_ENTER_UNIT,
+PlayerLeaveUnit=world.event.S_EVENT_PLAYER_LEAVE_UNIT,
+PlayerComment=world.event.S_EVENT_PLAYER_COMMENT,
+ShootingStart=world.event.S_EVENT_SHOOTING_START,
+ShootingEnd=world.event.S_EVENT_SHOOTING_END,
+NewCargo=world.event.S_EVENT_NEW_CARGO,
+DeleteCargo=world.event.S_EVENT_DELETE_CARGO,
+}
+local _EVENTMETA={
+[world.event.S_EVENT_SHOT]={
+Order=1,
+Side="I",
+Event="OnEventShot",
+Text="S_EVENT_SHOT"
+},
+[world.event.S_EVENT_HIT]={
+Order=1,
+Side="T",
+Event="OnEventHit",
+Text="S_EVENT_HIT"
+},
+[world.event.S_EVENT_TAKEOFF]={
+Order=1,
+Side="I",
+Event="OnEventTakeoff",
+Text="S_EVENT_TAKEOFF"
+},
+[world.event.S_EVENT_LAND]={
+Order=1,
+Side="I",
+Event="OnEventLand",
+Text="S_EVENT_LAND"
+},
+[world.event.S_EVENT_CRASH]={
+Order=-1,
+Side="I",
+Event="OnEventCrash",
+Text="S_EVENT_CRASH"
+},
+[world.event.S_EVENT_EJECTION]={
+Order=1,
+Side="I",
+Event="OnEventEjection",
+Text="S_EVENT_EJECTION"
+},
+[world.event.S_EVENT_REFUELING]={
+Order=1,
+Side="I",
+Event="OnEventRefueling",
+Text="S_EVENT_REFUELING"
+},
+[world.event.S_EVENT_DEAD]={
+Order=-1,
+Side="I",
+Event="OnEventDead",
+Text="S_EVENT_DEAD"
+},
+[world.event.S_EVENT_PILOT_DEAD]={
+Order=1,
+Side="I",
+Event="OnEventPilotDead",
+Text="S_EVENT_PILOT_DEAD"
+},
+[world.event.S_EVENT_BASE_CAPTURED]={
+Order=1,
+Side="I",
+Event="OnEventBaseCaptured",
+Text="S_EVENT_BASE_CAPTURED"
+},
+[world.event.S_EVENT_MISSION_START]={
+Order=1,
+Side="N",
+Event="OnEventMissionStart",
+Text="S_EVENT_MISSION_START"
+},
+[world.event.S_EVENT_MISSION_END]={
+Order=1,
+Side="N",
+Event="OnEventMissionEnd",
+Text="S_EVENT_MISSION_END"
+},
+[world.event.S_EVENT_TOOK_CONTROL]={
+Order=1,
+Side="N",
+Event="OnEventTookControl",
+Text="S_EVENT_TOOK_CONTROL"
+},
+[world.event.S_EVENT_REFUELING_STOP]={
+Order=1,
+Side="I",
+Event="OnEventRefuelingStop",
+Text="S_EVENT_REFUELING_STOP"
+},
+[world.event.S_EVENT_BIRTH]={
+Order=1,
+Side="I",
+Event="OnEventBirth",
+Text="S_EVENT_BIRTH"
+},
+[world.event.S_EVENT_HUMAN_FAILURE]={
+Order=1,
+Side="I",
+Event="OnEventHumanFailure",
+Text="S_EVENT_HUMAN_FAILURE"
+},
+[world.event.S_EVENT_ENGINE_STARTUP]={
+Order=1,
+Side="I",
+Event="OnEventEngineStartup",
+Text="S_EVENT_ENGINE_STARTUP"
+},
+[world.event.S_EVENT_ENGINE_SHUTDOWN]={
+Order=1,
+Side="I",
+Event="OnEventEngineShutdown",
+Text="S_EVENT_ENGINE_SHUTDOWN"
+},
+[world.event.S_EVENT_PLAYER_ENTER_UNIT]={
+Order=1,
+Side="I",
+Event="OnEventPlayerEnterUnit",
+Text="S_EVENT_PLAYER_ENTER_UNIT"
+},
+[world.event.S_EVENT_PLAYER_LEAVE_UNIT]={
+Order=-1,
+Side="I",
+Event="OnEventPlayerLeaveUnit",
+Text="S_EVENT_PLAYER_LEAVE_UNIT"
+},
+[world.event.S_EVENT_PLAYER_COMMENT]={
+Order=1,
+Side="I",
+Event="OnEventPlayerComment",
+Text="S_EVENT_PLAYER_COMMENT"
+},
+[world.event.S_EVENT_SHOOTING_START]={
+Order=1,
+Side="I",
+Event="OnEventShootingStart",
+Text="S_EVENT_SHOOTING_START"
+},
+[world.event.S_EVENT_SHOOTING_END]={
+Order=1,
+Side="I",
+Event="OnEventShootingEnd",
+Text="S_EVENT_SHOOTING_END"
+},
+[EVENTS.NewCargo]={
+Order=1,
+Event="OnEventNewCargo",
+Text="S_EVENT_NEW_CARGO"
+},
+[EVENTS.DeleteCargo]={
+Order=1,
+Event="OnEventDeleteCargo",
+Text="S_EVENT_DELETE_CARGO"
+},
+}
+function EVENT:New()
+local self=BASE:Inherit(self,BASE:New())
+self:F2()
+self.EventHandler=world.addEventHandler(self)
+return self
+end
+function EVENT:Init(EventID,EventClass)
+self:F3({_EVENTMETA[EventID].Text,EventClass})
+if not self.Events[EventID]then
+self.Events[EventID]={}
+end
+local EventPriority=EventClass:GetEventPriority()
+if not self.Events[EventID][EventPriority]then
+self.Events[EventID][EventPriority]=setmetatable({},{__mode="k"})
+end
+if not self.Events[EventID][EventPriority][EventClass]then
+self.Events[EventID][EventPriority][EventClass]={}
+end
+return self.Events[EventID][EventPriority][EventClass]
+end
+function EVENT:RemoveEvent(EventClass,EventID)
+self:F2({"Removing subscription for class: ",EventClass:GetClassNameAndID()})
+local EventPriority=EventClass:GetEventPriority()
+self.Events=self.Events or{}
+self.Events[EventID]=self.Events[EventID]or{}
+self.Events[EventID][EventPriority]=self.Events[EventID][EventPriority]or{}
+self.Events[EventID][EventPriority][EventClass]=self.Events[EventID][EventPriority][EventClass]
+self.Events[EventID][EventPriority][EventClass]=nil
+end
+function EVENT:Reset(EventObject)
+self:E({"Resetting subscriptions for class: ",EventObject:GetClassNameAndID()})
+local EventPriority=EventObject:GetEventPriority()
+for EventID,EventData in pairs(self.Events)do
+if self.EventsDead then
+if self.EventsDead[EventID]then
+if self.EventsDead[EventID][EventPriority]then
+if self.EventsDead[EventID][EventPriority][EventObject]then
+self.Events[EventID][EventPriority][EventObject]=self.EventsDead[EventID][EventPriority][EventObject]
+end
+end
+end
+end
+end
+end
+function EVENT:RemoveAll(EventObject)
+self:F3({EventObject:GetClassNameAndID()})
+local EventClass=EventObject:GetClassNameAndID()
+local EventPriority=EventClass:GetEventPriority()
+for EventID,EventData in pairs(self.Events)do
+self.Events[EventID][EventPriority][EventClass]=nil
+end
+end
+function EVENT:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EventID)
+self:F2(EventTemplate.name)
+for EventUnitID,EventUnit in pairs(EventTemplate.units)do
+self:OnEventForUnit(EventUnit.name,EventFunction,EventClass,EventID)
+end
+return self
+end
+function EVENT:OnEventGeneric(EventFunction,EventClass,EventID)
+self:F2({EventID})
+local EventData=self:Init(EventID,EventClass)
+EventData.EventFunction=EventFunction
+return self
+end
+function EVENT:OnEventForUnit(UnitName,EventFunction,EventClass,EventID)
+self:F2(UnitName)
+local EventData=self:Init(EventID,EventClass)
+EventData.EventUnit=true
+EventData.EventFunction=EventFunction
+return self
+end
+function EVENT:OnEventForGroup(GroupName,EventFunction,EventClass,EventID,...)
+self:E(GroupName)
+local Event=self:Init(EventID,EventClass)
+Event.EventGroup=true
+Event.EventFunction=EventFunction
+Event.Params=arg
+return self
+end
+do
+function EVENT:OnBirthForTemplate(EventTemplate,EventFunction,EventClass)
+self:F2(EventTemplate.name)
+self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Birth)
+return self
+end
+end
+do
+function EVENT:OnCrashForTemplate(EventTemplate,EventFunction,EventClass)
+self:F2(EventTemplate.name)
+self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Crash)
+return self
+end
+end
+do
+function EVENT:OnDeadForTemplate(EventTemplate,EventFunction,EventClass)
+self:F2(EventTemplate.name)
+self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Dead)
+return self
+end
+end
+do
+function EVENT:OnLandForTemplate(EventTemplate,EventFunction,EventClass)
+self:F2(EventTemplate.name)
+self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Land)
+return self
+end
+end
+do
+function EVENT:OnTakeOffForTemplate(EventTemplate,EventFunction,EventClass)
+self:F2(EventTemplate.name)
+self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Takeoff)
+return self
+end
+end
+do
+function EVENT:OnEngineShutDownForTemplate(EventTemplate,EventFunction,EventClass)
+self:F2(EventTemplate.name)
+self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.EngineShutdown)
+return self
+end
+end
+do
+function EVENT:CreateEventNewCargo(Cargo)
+self:F({Cargo})
+local Event={
+id=EVENTS.NewCargo,
+time=timer.getTime(),
+cargo=Cargo,
+}
+world.onEvent(Event)
+end
+function EVENT:CreateEventDeleteCargo(Cargo)
+self:F({Cargo})
+local Event={
+id=EVENTS.DeleteCargo,
+time=timer.getTime(),
+cargo=Cargo,
+}
+world.onEvent(Event)
+end
+function EVENT:CreateEventPlayerEnterUnit(PlayerUnit)
+self:F({PlayerUnit})
+local Event={
+id=EVENTS.PlayerEnterUnit,
+time=timer.getTime(),
+initiator=PlayerUnit:GetDCSObject()
+}
+world.onEvent(Event)
+end
+end
+function EVENT:onEvent(Event)
+local ErrorHandler=function(errmsg)
+env.info("Error in SCHEDULER function:"..errmsg)
+if debug~=nil then
+env.info(debug.traceback())
+end
+return errmsg
+end
+local EventMeta=_EVENTMETA[Event.id]
+if self and
+self.Events and
+self.Events[Event.id]and
+(Event.initiator~=nil or(Event.initiator==nil and Event.id~=EVENTS.PlayerLeaveUnit))then
+if Event.initiator then
+Event.IniObjectCategory=Event.initiator:getCategory()
+if Event.IniObjectCategory==Object.Category.UNIT then
+Event.IniDCSUnit=Event.initiator
+Event.IniDCSUnitName=Event.IniDCSUnit:getName()
+Event.IniUnitName=Event.IniDCSUnitName
+Event.IniDCSGroup=Event.IniDCSUnit:getGroup()
+Event.IniUnit=UNIT:FindByName(Event.IniDCSUnitName)
+if not Event.IniUnit then
+Event.IniUnit=CLIENT:FindByName(Event.IniDCSUnitName,'',true)
+end
+Event.IniDCSGroupName=""
+if Event.IniDCSGroup and Event.IniDCSGroup:isExist()then
+Event.IniDCSGroupName=Event.IniDCSGroup:getName()
+Event.IniGroup=GROUP:FindByName(Event.IniDCSGroupName)
+if Event.IniGroup then
+Event.IniGroupName=Event.IniDCSGroupName
+end
+end
+Event.IniPlayerName=Event.IniDCSUnit:getPlayerName()
+Event.IniCoalition=Event.IniDCSUnit:getCoalition()
+Event.IniTypeName=Event.IniDCSUnit:getTypeName()
+Event.IniCategory=Event.IniDCSUnit:getDesc().category
+end
+if Event.IniObjectCategory==Object.Category.STATIC then
+Event.IniDCSUnit=Event.initiator
+Event.IniDCSUnitName=Event.IniDCSUnit:getName()
+Event.IniUnitName=Event.IniDCSUnitName
+Event.IniUnit=STATIC:FindByName(Event.IniDCSUnitName,false)
+Event.IniCoalition=Event.IniDCSUnit:getCoalition()
+Event.IniCategory=Event.IniDCSUnit:getDesc().category
+Event.IniTypeName=Event.IniDCSUnit:getTypeName()
+end
+if Event.IniObjectCategory==Object.Category.SCENERY then
+Event.IniDCSUnit=Event.initiator
+Event.IniDCSUnitName=Event.IniDCSUnit:getName()
+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"
+end
+end
+if Event.target then
+Event.TgtObjectCategory=Event.target:getCategory()
+if Event.TgtObjectCategory==Object.Category.UNIT then
+Event.TgtDCSUnit=Event.target
+Event.TgtDCSGroup=Event.TgtDCSUnit:getGroup()
+Event.TgtDCSUnitName=Event.TgtDCSUnit:getName()
+Event.TgtUnitName=Event.TgtDCSUnitName
+Event.TgtUnit=UNIT:FindByName(Event.TgtDCSUnitName)
+Event.TgtDCSGroupName=""
+if Event.TgtDCSGroup and Event.TgtDCSGroup:isExist()then
+Event.TgtDCSGroupName=Event.TgtDCSGroup:getName()
+Event.TgtGroup=GROUP:FindByName(Event.TgtDCSGroupName)
+if Event.TgtGroup then
+Event.TgtGroupName=Event.TgtDCSGroupName
+end
+end
+Event.TgtPlayerName=Event.TgtDCSUnit:getPlayerName()
+Event.TgtCoalition=Event.TgtDCSUnit:getCoalition()
+Event.TgtCategory=Event.TgtDCSUnit:getDesc().category
+Event.TgtTypeName=Event.TgtDCSUnit:getTypeName()
+end
+if Event.TgtObjectCategory==Object.Category.STATIC then
+Event.TgtDCSUnit=Event.target
+Event.TgtDCSUnitName=Event.TgtDCSUnit:getName()
+Event.TgtUnitName=Event.TgtDCSUnitName
+Event.TgtUnit=STATIC:FindByName(Event.TgtDCSUnitName)
+Event.TgtCoalition=Event.TgtDCSUnit:getCoalition()
+Event.TgtCategory=Event.TgtDCSUnit:getDesc().category
+Event.TgtTypeName=Event.TgtDCSUnit:getTypeName()
+end
+if Event.TgtObjectCategory==Object.Category.SCENERY then
+Event.TgtDCSUnit=Event.target
+Event.TgtDCSUnitName=Event.TgtDCSUnit:getName()
+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
+if Event.weapon then
+Event.Weapon=Event.weapon
+Event.WeaponName=Event.Weapon:getTypeName()
+Event.WeaponUNIT=CLIENT:Find(Event.Weapon,'',true)
+Event.WeaponPlayerName=Event.WeaponUNIT and Event.Weapon:getPlayerName()
+Event.WeaponCoalition=Event.WeaponUNIT and Event.Weapon:getCoalition()
+Event.WeaponCategory=Event.WeaponUNIT and Event.Weapon:getDesc().category
+Event.WeaponTypeName=Event.WeaponUNIT and Event.Weapon:getTypeName()
+end
+if Event.cargo then
+Event.Cargo=Event.cargo
+Event.CargoName=Event.cargo.Name
+end
+local PriorityOrder=EventMeta.Order
+local PriorityBegin=PriorityOrder==-1 and 5 or 1
+local PriorityEnd=PriorityOrder==-1 and 1 or 5
+if Event.IniObjectCategory~=Object.Category.STATIC then
+self:E({EventMeta.Text,Event,Event.IniDCSUnitName,Event.TgtDCSUnitName,PriorityOrder})
+end
+for EventPriority=PriorityBegin,PriorityEnd,PriorityOrder do
+if self.Events[Event.id][EventPriority]then
+for EventClass,EventData in pairs(self.Events[Event.id][EventPriority])do
+Event.IniGroup=GROUP:FindByName(Event.IniDCSGroupName)
+Event.TgtGroup=GROUP:FindByName(Event.TgtDCSGroupName)
+if EventData.EventUnit then
+if EventClass:IsAlive()or
+Event.id==EVENTS.Crash or
+Event.id==EVENTS.Dead then
+local UnitName=EventClass:GetName()
+if(EventMeta.Side=="I"and UnitName==Event.IniDCSUnitName)or
+(EventMeta.Side=="T"and UnitName==Event.TgtDCSUnitName)then
+if EventData.EventFunction then
+if Event.IniObjectCategory~=3 then
+self:E({"Calling EventFunction for UNIT ",EventClass:GetClassNameAndID(),", Unit ",Event.IniUnitName,EventPriority})
+end
+local Result,Value=xpcall(
+function()
+return EventData.EventFunction(EventClass,Event)
+end,ErrorHandler)
+else
+local EventFunction=EventClass[EventMeta.Event]
+if EventFunction and type(EventFunction)=="function"then
+if Event.IniObjectCategory~=3 then
+self:E({"Calling "..EventMeta.Event.." for Class ",EventClass:GetClassNameAndID(),EventPriority})
+end
+local Result,Value=xpcall(
+function()
+return EventFunction(EventClass,Event)
+end,ErrorHandler)
+end
+end
+end
+else
+self:RemoveEvent(EventClass,Event.id)
+end
+else
+if EventData.EventGroup then
+if EventClass:IsAlive()or
+Event.id==EVENTS.Crash or
+Event.id==EVENTS.Dead then
+local GroupName=EventClass:GetName()
+if(EventMeta.Side=="I"and GroupName==Event.IniDCSGroupName)or
+(EventMeta.Side=="T"and GroupName==Event.TgtDCSGroupName)then
+if EventData.EventFunction then
+if Event.IniObjectCategory~=3 then
+self:E({"Calling EventFunction for GROUP ",EventClass:GetClassNameAndID(),", Unit ",Event.IniUnitName,EventPriority})
+end
+local Result,Value=xpcall(
+function()
+return EventData.EventFunction(EventClass,Event,unpack(EventData.Params))
+end,ErrorHandler)
+else
+local EventFunction=EventClass[EventMeta.Event]
+if EventFunction and type(EventFunction)=="function"then
+if Event.IniObjectCategory~=3 then
+self:E({"Calling "..EventMeta.Event.." for GROUP ",EventClass:GetClassNameAndID(),EventPriority})
+end
+local Result,Value=xpcall(
+function()
+return EventFunction(EventClass,Event,unpack(EventData.Params))
+end,ErrorHandler)
+end
+end
+end
+else
+end
+else
+if not EventData.EventUnit then
+if EventData.EventFunction then
+if Event.IniObjectCategory~=3 then
+self:F2({"Calling EventFunction for Class ",EventClass:GetClassNameAndID(),EventPriority})
+end
+local Result,Value=xpcall(
+function()
+return EventData.EventFunction(EventClass,Event)
+end,ErrorHandler)
+else
+local EventFunction=EventClass[EventMeta.Event]
+if EventFunction and type(EventFunction)=="function"then
+if Event.IniObjectCategory~=3 then
+self:F2({"Calling "..EventMeta.Event.." for Class ",EventClass:GetClassNameAndID(),EventPriority})
+end
+local Result,Value=xpcall(
+function()
+local Result,Value=EventFunction(EventClass,Event)
+return Result,Value
+end,ErrorHandler)
+end
+end
+end
+end
+end
+end
+end
+end
+else
+self:E({EventMeta.Text,Event})
+end
+Event=nil
+end
+EVENTHANDLER={
+ClassName="EVENTHANDLER",
+ClassID=0,
+}
+function EVENTHANDLER:New()
+self=BASE:Inherit(self,BASE:New())
+return self
+end
+SETTINGS={
+ClassName="SETTINGS",
+}
+do
+function SETTINGS:Set(PlayerName)
+if PlayerName==nil then
+local self=BASE:Inherit(self,BASE:New())
+self:SetMetric()
+self:SetA2G_BR()
+self:SetA2A_BRAA()
+self:SetLL_Accuracy(3)
+self:SetMGRS_Accuracy(5)
+self:SetMessageTime(MESSAGE.Type.Briefing,180)
+self:SetMessageTime(MESSAGE.Type.Detailed,60)
+self:SetMessageTime(MESSAGE.Type.Information,30)
+self:SetMessageTime(MESSAGE.Type.Overview,60)
+self:SetMessageTime(MESSAGE.Type.Update,15)
+return self
+else
+local Settings=_DATABASE:GetPlayerSettings(PlayerName)
+if not Settings then
+Settings=BASE:Inherit(self,BASE:New())
+_DATABASE:SetPlayerSettings(PlayerName,Settings)
+end
+return Settings
+end
+end
+function SETTINGS:SetMetric()
+self.Metric=true
+end
+function SETTINGS:IsMetric()
+return(self.Metric~=nil and self.Metric==true)or(self.Metric==nil and _SETTINGS:IsMetric())
+end
+function SETTINGS:SetImperial()
+self.Metric=false
+end
+function SETTINGS:IsImperial()
+return(self.Metric~=nil and self.Metric==false)or(self.Metric==nil and _SETTINGS:IsMetric())
+end
+function SETTINGS:SetLL_Accuracy(LL_Accuracy)
+self.LL_Accuracy=LL_Accuracy
+end
+function SETTINGS:GetLL_DDM_Accuracy()
+return self.LL_DDM_Accuracy or _SETTINGS:GetLL_DDM_Accuracy()
+end
+function SETTINGS:SetMGRS_Accuracy(MGRS_Accuracy)
+self.MGRS_Accuracy=MGRS_Accuracy
+end
+function SETTINGS:GetMGRS_Accuracy()
+return self.MGRS_Accuracy or _SETTINGS:GetMGRS_Accuracy()
+end
+function SETTINGS:SetMessageTime(MessageType,MessageTime)
+self.MessageTypeTimings=self.MessageTypeTimings or{}
+self.MessageTypeTimings[MessageType]=MessageTime
+end
+function SETTINGS:GetMessageTime(MessageType)
+return(self.MessageTypeTimings and self.MessageTypeTimings[MessageType])or _SETTINGS:GetMessageTime(MessageType)
+end
+function SETTINGS:SetA2G_LL_DMS()
+self.A2GSystem="LL DMS"
+end
+function SETTINGS:SetA2G_LL_DDM()
+self.A2GSystem="LL DDM"
+end
+function SETTINGS:IsA2G_LL_DMS()
+return(self.A2GSystem and self.A2GSystem=="LL DMS")or(not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS())
+end
+function SETTINGS:IsA2G_LL_DDM()
+return(self.A2GSystem and self.A2GSystem=="LL DDM")or(not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM())
+end
+function SETTINGS:SetA2G_MGRS()
+self.A2GSystem="MGRS"
+end
+function SETTINGS:IsA2G_MGRS()
+return(self.A2GSystem and self.A2GSystem=="MGRS")or(not self.A2GSystem and _SETTINGS:IsA2G_MGRS())
+end
+function SETTINGS:SetA2G_BR()
+self.A2GSystem="BR"
+end
+function SETTINGS:IsA2G_BR()
+return(self.A2GSystem and self.A2GSystem=="BR")or(not self.A2GSystem and _SETTINGS:IsA2G_BR())
+end
+function SETTINGS:SetA2A_BRAA()
+self.A2ASystem="BRAA"
+end
+function SETTINGS:IsA2A_BRAA()
+self:E({BRA=(self.A2ASystem and self.A2ASystem=="BRAA")or(not self.A2ASystem and _SETTINGS:IsA2A_BRAA())})
+return(self.A2ASystem and self.A2ASystem=="BRAA")or(not self.A2ASystem and _SETTINGS:IsA2A_BRAA())
+end
+function SETTINGS:SetA2A_BULLS()
+self.A2ASystem="BULLS"
+end
+function SETTINGS:IsA2A_BULLS()
+return(self.A2ASystem and self.A2ASystem=="BULLS")or(not self.A2ASystem and _SETTINGS:IsA2A_BULLS())
+end
+function SETTINGS:SetA2A_LL_DMS()
+self.A2ASystem="LL DMS"
+end
+function SETTINGS:SetA2A_LL_DDM()
+self.A2ASystem="LL DDM"
+end
+function SETTINGS:IsA2A_LL_DMS()
+return(self.A2ASystem and self.A2ASystem=="LL DMS")or(not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS())
+end
+function SETTINGS:IsA2A_LL_DDM()
+return(self.A2ASystem and self.A2ASystem=="LL DDM")or(not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM())
+end
+function SETTINGS:SetA2A_MGRS()
+self.A2ASystem="MGRS"
+end
+function SETTINGS:IsA2A_MGRS()
+return(self.A2ASystem and self.A2ASystem=="MGRS")or(not self.A2ASystem and _SETTINGS:IsA2A_MGRS())
+end
+function SETTINGS:SetSystemMenu(MenuGroup,RootMenu)
+local MenuText="System Settings"
+local MenuTime=timer.getTime()
+local SettingsMenu=MENU_GROUP:New(MenuGroup,MenuText,RootMenu):SetTime(MenuTime)
+local A2GCoordinateMenu=MENU_GROUP:New(MenuGroup,"A2G Coordinate System",SettingsMenu):SetTime(MenuTime)
+if not self:IsA2G_LL_DMS()then
+MENU_GROUP_COMMAND:New(MenuGroup,"Lat/Lon Degree Min Sec (LL DMS)",A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"LL DMS"):SetTime(MenuTime)
+end
+if not self:IsA2G_LL_DDM()then
+MENU_GROUP_COMMAND:New(MenuGroup,"Lat/Lon Degree Dec Min (LL DDM)",A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"LL DDM"):SetTime(MenuTime)
+end
+if self:IsA2G_LL_DDM()then
+MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 1",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 2",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 3",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime)
+end
+if not self:IsA2G_BR()then
+MENU_GROUP_COMMAND:New(MenuGroup,"Bearing, Range (BR)",A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"BR"):SetTime(MenuTime)
+end
+if not self:IsA2G_MGRS()then
+MENU_GROUP_COMMAND:New(MenuGroup,"Military Grid (MGRS)",A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"MGRS"):SetTime(MenuTime)
+end
+if self:IsA2G_MGRS()then
+MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 1",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 2",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 3",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 4",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,4):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 5",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,5):SetTime(MenuTime)
+end
+local A2ACoordinateMenu=MENU_GROUP:New(MenuGroup,"A2A Coordinate System",SettingsMenu):SetTime(MenuTime)
+if not self:IsA2A_LL_DMS()then
+MENU_GROUP_COMMAND:New(MenuGroup,"Lat/Lon Degree Min Sec (LL DMS)",A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"LL DMS"):SetTime(MenuTime)
+end
+if not self:IsA2A_LL_DDM()then
+MENU_GROUP_COMMAND:New(MenuGroup,"Lat/Lon Degree Dec Min (LL DDM)",A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"LL DDM"):SetTime(MenuTime)
+end
+if self:IsA2A_LL_DDM()then
+MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 1",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 2",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 3",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime)
+end
+if not self:IsA2A_BULLS()then
+MENU_GROUP_COMMAND:New(MenuGroup,"Bullseye (BULLS)",A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"BULLS"):SetTime(MenuTime)
+end
+if not self:IsA2A_BRAA()then
+MENU_GROUP_COMMAND:New(MenuGroup,"Bearing Range Altitude Aspect (BRAA)",A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"BRAA"):SetTime(MenuTime)
+end
+if not self:IsA2A_MGRS()then
+MENU_GROUP_COMMAND:New(MenuGroup,"Military Grid (MGRS)",A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"MGRS"):SetTime(MenuTime)
+end
+if self:IsA2A_MGRS()then
+MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 1",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 2",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 3",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 4",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,4):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 5",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,5):SetTime(MenuTime)
+end
+local MetricsMenu=MENU_GROUP:New(MenuGroup,"Measures and Weights System",SettingsMenu):SetTime(MenuTime)
+if self:IsMetric()then
+MENU_GROUP_COMMAND:New(MenuGroup,"Imperial (Miles,Feet)",MetricsMenu,self.MenuMWSystem,self,MenuGroup,RootMenu,false):SetTime(MenuTime)
+end
+if self:IsImperial()then
+MENU_GROUP_COMMAND:New(MenuGroup,"Metric (Kilometers,Meters)",MetricsMenu,self.MenuMWSystem,self,MenuGroup,RootMenu,true):SetTime(MenuTime)
+end
+local MessagesMenu=MENU_GROUP:New(MenuGroup,"Messages and Reports",SettingsMenu):SetTime(MenuTime)
+local UpdateMessagesMenu=MENU_GROUP:New(MenuGroup,"Update Messages",MessagesMenu):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"Off",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,0):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"5 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,5):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"10 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,10):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,15):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,30):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,60):SetTime(MenuTime)
+local InformationMessagesMenu=MENU_GROUP:New(MenuGroup,"Information Messages",MessagesMenu):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"5 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,5):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"10 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,10):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,15):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,30):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,60):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,120):SetTime(MenuTime)
+local BriefingReportsMenu=MENU_GROUP:New(MenuGroup,"Briefing Reports",MessagesMenu):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,15):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,30):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,60):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,120):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,180):SetTime(MenuTime)
+local OverviewReportsMenu=MENU_GROUP:New(MenuGroup,"Overview Reports",MessagesMenu):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,15):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,30):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,60):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,120):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,180):SetTime(MenuTime)
+local DetailedReportsMenu=MENU_GROUP:New(MenuGroup,"Detailed Reports",MessagesMenu):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,15):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,30):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,60):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,120):SetTime(MenuTime)
+MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,180):SetTime(MenuTime)
+SettingsMenu:Remove(MenuTime)
+return self
+end
+function SETTINGS:SetPlayerMenu(PlayerUnit)
+local PlayerGroup=PlayerUnit:GetGroup()
+local PlayerName=PlayerUnit:GetPlayerName()
+local PlayerNames=PlayerGroup:GetPlayerNames()
+local PlayerMenu=MENU_GROUP:New(PlayerGroup,'Settings "'..PlayerName..'"')
+self.PlayerMenu=PlayerMenu
+local A2GCoordinateMenu=MENU_GROUP:New(PlayerGroup,"A2G Coordinate System",PlayerMenu)
+if not self:IsA2G_LL_DMS()then
+MENU_GROUP_COMMAND:New(PlayerGroup,"Lat/Lon Degree Min Sec (LL DMS)",A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DMS")
+end
+if not self:IsA2G_LL_DDM()then
+MENU_GROUP_COMMAND:New(PlayerGroup,"Lat/Lon Degree Dec Min (LL DDM)",A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DDM")
+end
+if self:IsA2G_LL_DDM()then
+MENU_GROUP_COMMAND:New(PlayerGroup,"LL DDM Accuracy 1",A2GCoordinateMenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,1)
+MENU_GROUP_COMMAND:New(PlayerGroup,"LL DDM Accuracy 2",A2GCoordinateMenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,2)
+MENU_GROUP_COMMAND:New(PlayerGroup,"LL DDM Accuracy 3",A2GCoordinateMenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,3)
+end
+if not self:IsA2G_BR()then
+MENU_GROUP_COMMAND:New(PlayerGroup,"Bearing, Range (BR)",A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"BR")
+end
+if not self:IsA2G_MGRS()then
+MENU_GROUP_COMMAND:New(PlayerGroup,"Military Grid (MGRS)",A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"MGRS")
+end
+if self:IsA2G_MGRS()then
+MENU_GROUP_COMMAND:New(PlayerGroup,"MGRS Accuracy 1",A2GCoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,1)
+MENU_GROUP_COMMAND:New(PlayerGroup,"MGRS Accuracy 2",A2GCoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,2)
+MENU_GROUP_COMMAND:New(PlayerGroup,"MGRS Accuracy 3",A2GCoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,3)
+MENU_GROUP_COMMAND:New(PlayerGroup,"MGRS Accuracy 4",A2GCoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,4)
+MENU_GROUP_COMMAND:New(PlayerGroup,"MGRS Accuracy 5",A2GCoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,5)
+end
+local A2ACoordinateMenu=MENU_GROUP:New(PlayerGroup,"A2A Coordinate System",PlayerMenu)
+if not self:IsA2A_LL_DMS()then
+MENU_GROUP_COMMAND:New(PlayerGroup,"Lat/Lon Degree Min Sec (LL DMS)",A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DMS")
+end
+if not self:IsA2A_LL_DDM()then
+MENU_GROUP_COMMAND:New(PlayerGroup,"Lat/Lon Degree Dec Min (LL DDM)",A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DDM")
+end
+if self:IsA2A_LL_DDM()then
+MENU_GROUP_COMMAND:New(PlayerGroup,"LL DDM Accuracy 1",A2GCoordinateMenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,1)
+MENU_GROUP_COMMAND:New(PlayerGroup,"LL DDM Accuracy 2",A2GCoordinateMenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,2)
+MENU_GROUP_COMMAND:New(PlayerGroup,"LL DDM Accuracy 3",A2GCoordinateMenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,3)
+end
+if not self:IsA2A_BULLS()then
+MENU_GROUP_COMMAND:New(PlayerGroup,"Bullseye (BULLS)",A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"BULLS")
+end
+if not self:IsA2A_BRAA()then
+MENU_GROUP_COMMAND:New(PlayerGroup,"Bearing Range Altitude Aspect (BRAA)",A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"BRAA")
+end
+if not self:IsA2A_MGRS()then
+MENU_GROUP_COMMAND:New(PlayerGroup,"Military Grid (MGRS)",A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"MGRS")
+end
+if self:IsA2A_MGRS()then
+MENU_GROUP_COMMAND:New(PlayerGroup,"Military Grid (MGRS) Accuracy 1",A2ACoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,1)
+MENU_GROUP_COMMAND:New(PlayerGroup,"Military Grid (MGRS) Accuracy 2",A2ACoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,2)
+MENU_GROUP_COMMAND:New(PlayerGroup,"Military Grid (MGRS) Accuracy 3",A2ACoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,3)
+MENU_GROUP_COMMAND:New(PlayerGroup,"Military Grid (MGRS) Accuracy 4",A2ACoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,4)
+MENU_GROUP_COMMAND:New(PlayerGroup,"Military Grid (MGRS) Accuracy 5",A2ACoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,5)
+end
+local MetricsMenu=MENU_GROUP:New(PlayerGroup,"Measures and Weights System",PlayerMenu)
+if self:IsMetric()then
+MENU_GROUP_COMMAND:New(PlayerGroup,"Imperial (Miles,Feet)",MetricsMenu,self.MenuGroupMWSystem,self,PlayerUnit,PlayerGroup,PlayerName,false)
+end
+if self:IsImperial()then
+MENU_GROUP_COMMAND:New(PlayerGroup,"Metric (Kilometers,Meters)",MetricsMenu,self.MenuGroupMWSystem,self,PlayerUnit,PlayerGroup,PlayerName,true)
+end
+local MessagesMenu=MENU_GROUP:New(PlayerGroup,"Messages and Reports",PlayerMenu)
+local UpdateMessagesMenu=MENU_GROUP:New(PlayerGroup,"Update Messages",MessagesMenu)
+MENU_GROUP_COMMAND:New(PlayerGroup,"Off",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,0)
+MENU_GROUP_COMMAND:New(PlayerGroup,"5 seconds",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,5)
+MENU_GROUP_COMMAND:New(PlayerGroup,"10 seconds",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,10)
+MENU_GROUP_COMMAND:New(PlayerGroup,"15 seconds",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,15)
+MENU_GROUP_COMMAND:New(PlayerGroup,"30 seconds",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,30)
+MENU_GROUP_COMMAND:New(PlayerGroup,"1 minute",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,60)
+local InformationMessagesMenu=MENU_GROUP:New(PlayerGroup,"Information Messages",MessagesMenu)
+MENU_GROUP_COMMAND:New(PlayerGroup,"5 seconds",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,5)
+MENU_GROUP_COMMAND:New(PlayerGroup,"10 seconds",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,10)
+MENU_GROUP_COMMAND:New(PlayerGroup,"15 seconds",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,15)
+MENU_GROUP_COMMAND:New(PlayerGroup,"30 seconds",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,30)
+MENU_GROUP_COMMAND:New(PlayerGroup,"1 minute",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,60)
+MENU_GROUP_COMMAND:New(PlayerGroup,"2 minutes",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,120)
+local BriefingReportsMenu=MENU_GROUP:New(PlayerGroup,"Briefing Reports",MessagesMenu)
+MENU_GROUP_COMMAND:New(PlayerGroup,"15 seconds",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,15)
+MENU_GROUP_COMMAND:New(PlayerGroup,"30 seconds",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,30)
+MENU_GROUP_COMMAND:New(PlayerGroup,"1 minute",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,60)
+MENU_GROUP_COMMAND:New(PlayerGroup,"2 minutes",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,120)
+MENU_GROUP_COMMAND:New(PlayerGroup,"3 minutes",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,180)
+local OverviewReportsMenu=MENU_GROUP:New(PlayerGroup,"Overview Reports",MessagesMenu)
+MENU_GROUP_COMMAND:New(PlayerGroup,"15 seconds",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,15)
+MENU_GROUP_COMMAND:New(PlayerGroup,"30 seconds",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,30)
+MENU_GROUP_COMMAND:New(PlayerGroup,"1 minute",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,60)
+MENU_GROUP_COMMAND:New(PlayerGroup,"2 minutes",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,120)
+MENU_GROUP_COMMAND:New(PlayerGroup,"3 minutes",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,180)
+local DetailedReportsMenu=MENU_GROUP:New(PlayerGroup,"Detailed Reports",MessagesMenu)
+MENU_GROUP_COMMAND:New(PlayerGroup,"15 seconds",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,15)
+MENU_GROUP_COMMAND:New(PlayerGroup,"30 seconds",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,30)
+MENU_GROUP_COMMAND:New(PlayerGroup,"1 minute",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,60)
+MENU_GROUP_COMMAND:New(PlayerGroup,"2 minutes",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,120)
+MENU_GROUP_COMMAND:New(PlayerGroup,"3 minutes",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,180)
+return self
+end
+function SETTINGS:RemovePlayerMenu(PlayerUnit)
+if self.PlayerMenu then
+self.PlayerMenu:Remove()
+end
+return self
+end
+function SETTINGS:A2GMenuSystem(MenuGroup,RootMenu,A2GSystem)
+self.A2GSystem=A2GSystem
+MESSAGE:New(string.format("Settings: Default A2G coordinate system set to %s for all players!",A2GSystem),5):ToAll()
+self:SetSystemMenu(MenuGroup,RootMenu)
+end
+function SETTINGS:A2AMenuSystem(MenuGroup,RootMenu,A2ASystem)
+self.A2ASystem=A2ASystem
+MESSAGE:New(string.format("Settings: Default A2A coordinate system set to %s for all players!",A2ASystem),5):ToAll()
+self:SetSystemMenu(MenuGroup,RootMenu)
+end
+function SETTINGS:MenuLL_DDM_Accuracy(MenuGroup,RootMenu,LL_Accuracy)
+self.LL_Accuracy=LL_Accuracy
+MESSAGE:New(string.format("Settings: Default LL accuracy set to %s for all players!",LL_Accuracy),5):ToAll()
+self:SetSystemMenu(MenuGroup,RootMenu)
+end
+function SETTINGS:MenuMGRS_Accuracy(MenuGroup,RootMenu,MGRS_Accuracy)
+self.MGRS_Accuracy=MGRS_Accuracy
+MESSAGE:New(string.format("Settings: Default MGRS accuracy set to %s for all players!",MGRS_Accuracy),5):ToAll()
+self:SetSystemMenu(MenuGroup,RootMenu)
+end
+function SETTINGS:MenuMWSystem(MenuGroup,RootMenu,MW)
+self.Metric=MW
+MESSAGE:New(string.format("Settings: Default measurement format set to %s for all players!",MW and"Metric"or"Imperial"),5):ToAll()
+self:SetSystemMenu(MenuGroup,RootMenu)
+end
+function SETTINGS:MenuMessageTimingsSystem(MenuGroup,RootMenu,MessageType,MessageTime)
+self:SetMessageTime(MessageType,MessageTime)
+MESSAGE:New(string.format("Settings: Default message time set for %s to %d.",MessageType,MessageTime),5):ToAll()
+end
+do
+function SETTINGS:MenuGroupA2GSystem(PlayerUnit,PlayerGroup,PlayerName,A2GSystem)
+BASE:E({self,PlayerUnit:GetName(),A2GSystem})
+self.A2GSystem=A2GSystem
+MESSAGE:New(string.format("Settings: A2G format set to %s for player %s.",A2GSystem,PlayerName),5):ToGroup(PlayerGroup)
+self:RemovePlayerMenu(PlayerUnit)
+self:SetPlayerMenu(PlayerUnit)
+end
+function SETTINGS:MenuGroupA2ASystem(PlayerUnit,PlayerGroup,PlayerName,A2ASystem)
+self.A2ASystem=A2ASystem
+MESSAGE:New(string.format("Settings: A2A format set to %s for player %s.",A2ASystem,PlayerName),5):ToGroup(PlayerGroup)
+self:RemovePlayerMenu(PlayerUnit)
+self:SetPlayerMenu(PlayerUnit)
+end
+function SETTINGS:MenuGroupLL_DDM_AccuracySystem(PlayerUnit,PlayerGroup,PlayerName,LL_Accuracy)
+self.LL_Accuracy=LL_Accuracy
+MESSAGE:New(string.format("Settings: A2G LL format accuracy set to %d for player %s.",LL_Accuracy,PlayerName),5):ToGroup(PlayerGroup)
+self:RemovePlayerMenu(PlayerUnit)
+self:SetPlayerMenu(PlayerUnit)
+end
+function SETTINGS:MenuGroupMGRS_AccuracySystem(PlayerUnit,PlayerGroup,PlayerName,MGRS_Accuracy)
+self.MGRS_Accuracy=MGRS_Accuracy
+MESSAGE:New(string.format("Settings: A2G MGRS format accuracy set to %d for player %s.",MGRS_Accuracy,PlayerName),5):ToGroup(PlayerGroup)
+self:RemovePlayerMenu(PlayerUnit)
+self:SetPlayerMenu(PlayerUnit)
+end
+function SETTINGS:MenuGroupMWSystem(PlayerUnit,PlayerGroup,PlayerName,MW)
+self.Metric=MW
+MESSAGE:New(string.format("Settings: Measurement format set to %s for player %s.",MW and"Metric"or"Imperial",PlayerName),5):ToGroup(PlayerGroup)
+self:RemovePlayerMenu(PlayerUnit)
+self:SetPlayerMenu(PlayerUnit)
+end
+function SETTINGS:MenuGroupMessageTimingsSystem(PlayerUnit,PlayerGroup,PlayerName,MessageType,MessageTime)
+self:SetMessageTime(MessageType,MessageTime)
+MESSAGE:New(string.format("Settings: Default message time set for %s to %d.",MessageType,MessageTime),5):ToGroup(PlayerGroup)
+end
+end
+end
+do
+MENU_BASE={
+ClassName="MENU_BASE",
+MenuPath=nil,
+MenuText="",
+MenuParentPath=nil
+}
+function MENU_BASE:New(MenuText,ParentMenu)
+local MenuParentPath={}
+if ParentMenu~=nil then
+MenuParentPath=ParentMenu.MenuPath
+end
+local self=BASE:Inherit(self,BASE:New())
+self.MenuPath=nil
+self.MenuText=MenuText
+self.MenuParentPath=MenuParentPath
+self.Menus={}
+self.MenuCount=0
+self.MenuRemoveParent=false
+self.MenuTime=timer.getTime()
+return self
+end
+function MENU_BASE:GetMenu(MenuText)
+self:F2({Menu=self.Menus[MenuText]})
+return self.Menus[MenuText]
+end
+function MENU_BASE:SetRemoveParent(RemoveParent)
+self:F2({RemoveParent})
+self.MenuRemoveParent=RemoveParent
+return self
+end
+function MENU_BASE:SetTime(MenuTime)
+self.MenuTime=MenuTime
+return self
+end
+function MENU_BASE:SetTag(MenuTag)
+self.MenuTag=MenuTag
+return self
+end
+end
+do
+MENU_COMMAND_BASE={
+ClassName="MENU_COMMAND_BASE",
+CommandMenuFunction=nil,
+CommandMenuArgument=nil,
+MenuCallHandler=nil,
+}
+function MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,CommandMenuArguments)
+local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
+local ErrorHandler=function(errmsg)
+env.info("MOOSE error in MENU COMMAND function: "..errmsg)
+if debug~=nil then
+env.info(debug.traceback())
+end
+return errmsg
+end
+self:SetCommandMenuFunction(CommandMenuFunction)
+self:SetCommandMenuArguments(CommandMenuArguments)
+self.MenuCallHandler=function()
+local function MenuFunction()
+return self.CommandMenuFunction(unpack(self.CommandMenuArguments))
+end
+local Status,Result=xpcall(MenuFunction,ErrorHandler)
+end
+return self
+end
+function MENU_COMMAND_BASE:SetCommandMenuFunction(CommandMenuFunction)
+self.CommandMenuFunction=CommandMenuFunction
+return self
+end
+function MENU_COMMAND_BASE:SetCommandMenuArguments(CommandMenuArguments)
+self.CommandMenuArguments=CommandMenuArguments
+return self
+end
+end
+do
+MENU_MISSION={
+ClassName="MENU_MISSION"
+}
+function MENU_MISSION:New(MenuText,ParentMenu)
+local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
+self:F({MenuText,ParentMenu})
+self.MenuText=MenuText
+self.ParentMenu=ParentMenu
+self.Menus={}
+self:T({MenuText})
+self.MenuPath=missionCommands.addSubMenu(MenuText,self.MenuParentPath)
+self:T({self.MenuPath})
+if ParentMenu and ParentMenu.Menus then
+ParentMenu.Menus[self.MenuPath]=self
+end
+return self
+end
+function MENU_MISSION:RemoveSubMenus()
+self:F(self.MenuPath)
+for MenuID,Menu in pairs(self.Menus)do
+Menu:Remove()
+end
+end
+function MENU_MISSION:Remove()
+self:F(self.MenuPath)
+self:RemoveSubMenus()
+missionCommands.removeItem(self.MenuPath)
+if self.ParentMenu then
+self.ParentMenu.Menus[self.MenuPath]=nil
+end
+return nil
+end
+end
+do
+MENU_MISSION_COMMAND={
+ClassName="MENU_MISSION_COMMAND"
+}
+function MENU_MISSION_COMMAND:New(MenuText,ParentMenu,CommandMenuFunction,...)
+local self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg))
+self.MenuText=MenuText
+self.ParentMenu=ParentMenu
+self:T({MenuText,CommandMenuFunction,arg})
+self.MenuPath=missionCommands.addCommand(MenuText,self.MenuParentPath,self.MenuCallHandler)
+ParentMenu.Menus[self.MenuPath]=self
+return self
+end
+function MENU_MISSION_COMMAND:Remove()
+self:F(self.MenuPath)
+missionCommands.removeItem(self.MenuPath)
+if self.ParentMenu then
+self.ParentMenu.Menus[self.MenuPath]=nil
+end
+return nil
+end
+end
+do
+MENU_COALITION={
+ClassName="MENU_COALITION"
+}
+function MENU_COALITION:New(Coalition,MenuText,ParentMenu)
+local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
+self:F({Coalition,MenuText,ParentMenu})
+self.Coalition=Coalition
+self.MenuText=MenuText
+self.ParentMenu=ParentMenu
+self.Menus={}
+self:T({MenuText})
+self.MenuPath=missionCommands.addSubMenuForCoalition(Coalition,MenuText,self.MenuParentPath)
+self:T({self.MenuPath})
+if ParentMenu and ParentMenu.Menus then
+ParentMenu.Menus[self.MenuPath]=self
+end
+return self
+end
+function MENU_COALITION:RemoveSubMenus()
+self:F(self.MenuPath)
+for MenuID,Menu in pairs(self.Menus)do
+Menu:Remove()
+end
+end
+function MENU_COALITION:Remove()
+self:F(self.MenuPath)
+self:RemoveSubMenus()
+missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath)
+if self.ParentMenu then
+self.ParentMenu.Menus[self.MenuPath]=nil
+end
+return nil
+end
+end
+do
+MENU_COALITION_COMMAND={
+ClassName="MENU_COALITION_COMMAND"
+}
+function MENU_COALITION_COMMAND:New(Coalition,MenuText,ParentMenu,CommandMenuFunction,...)
+local self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg))
+self.MenuCoalition=Coalition
+self.MenuText=MenuText
+self.ParentMenu=ParentMenu
+self:T({MenuText,CommandMenuFunction,arg})
+self.MenuPath=missionCommands.addCommandForCoalition(self.MenuCoalition,MenuText,self.MenuParentPath,self.MenuCallHandler)
+ParentMenu.Menus[self.MenuPath]=self
+return self
+end
+function MENU_COALITION_COMMAND:Remove()
+self:F(self.MenuPath)
+missionCommands.removeItemForCoalition(self.MenuCoalition,self.MenuPath)
+if self.ParentMenu then
+self.ParentMenu.Menus[self.MenuPath]=nil
+end
+return nil
+end
+end
+do
+local _MENUCLIENTS={}
+MENU_CLIENT={
+ClassName="MENU_CLIENT"
+}
+function MENU_CLIENT:New(Client,MenuText,ParentMenu)
+local MenuParentPath={}
+if ParentMenu~=nil then
+MenuParentPath=ParentMenu.MenuPath
+end
+local self=BASE:Inherit(self,MENU_BASE:New(MenuText,MenuParentPath))
+self:F({Client,MenuText,ParentMenu})
+self.MenuClient=Client
+self.MenuClientGroupID=Client:GetClientGroupID()
+self.MenuParentPath=MenuParentPath
+self.MenuText=MenuText
+self.ParentMenu=ParentMenu
+self.Menus={}
+if not _MENUCLIENTS[self.MenuClientGroupID]then
+_MENUCLIENTS[self.MenuClientGroupID]={}
+end
+local MenuPath=_MENUCLIENTS[self.MenuClientGroupID]
+self:T({Client:GetClientGroupName(),MenuPath[table.concat(MenuParentPath)],MenuParentPath,MenuText})
+local MenuPathID=table.concat(MenuParentPath).."/"..MenuText
+if MenuPath[MenuPathID]then
+missionCommands.removeItemForGroup(self.MenuClient:GetClientGroupID(),MenuPath[MenuPathID])
+end
+self.MenuPath=missionCommands.addSubMenuForGroup(self.MenuClient:GetClientGroupID(),MenuText,MenuParentPath)
+MenuPath[MenuPathID]=self.MenuPath
+self:T({Client:GetClientGroupName(),self.MenuPath})
+if ParentMenu and ParentMenu.Menus then
+ParentMenu.Menus[self.MenuPath]=self
+end
+return self
+end
+function MENU_CLIENT:RemoveSubMenus()
+self:F(self.MenuPath)
+for MenuID,Menu in pairs(self.Menus)do
+Menu:Remove()
+end
+end
+function MENU_CLIENT:Remove()
+self:F(self.MenuPath)
+self:RemoveSubMenus()
+if not _MENUCLIENTS[self.MenuClientGroupID]then
+_MENUCLIENTS[self.MenuClientGroupID]={}
+end
+local MenuPath=_MENUCLIENTS[self.MenuClientGroupID]
+if MenuPath[table.concat(self.MenuParentPath).."/"..self.MenuText]then
+MenuPath[table.concat(self.MenuParentPath).."/"..self.MenuText]=nil
+end
+missionCommands.removeItemForGroup(self.MenuClient:GetClientGroupID(),self.MenuPath)
+self.ParentMenu.Menus[self.MenuPath]=nil
+return nil
+end
+MENU_CLIENT_COMMAND={
+ClassName="MENU_CLIENT_COMMAND"
+}
+function MENU_CLIENT_COMMAND:New(Client,MenuText,ParentMenu,CommandMenuFunction,...)
+local MenuParentPath={}
+if ParentMenu~=nil then
+MenuParentPath=ParentMenu.MenuPath
+end
+local self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,MenuParentPath,CommandMenuFunction,arg))
+self.MenuClient=Client
+self.MenuClientGroupID=Client:GetClientGroupID()
+self.MenuParentPath=MenuParentPath
+self.MenuText=MenuText
+self.ParentMenu=ParentMenu
+if not _MENUCLIENTS[self.MenuClientGroupID]then
+_MENUCLIENTS[self.MenuClientGroupID]={}
+end
+local MenuPath=_MENUCLIENTS[self.MenuClientGroupID]
+self:T({Client:GetClientGroupName(),MenuPath[table.concat(MenuParentPath)],MenuParentPath,MenuText,CommandMenuFunction,arg})
+local MenuPathID=table.concat(MenuParentPath).."/"..MenuText
+if MenuPath[MenuPathID]then
+missionCommands.removeItemForGroup(self.MenuClient:GetClientGroupID(),MenuPath[MenuPathID])
+end
+self.MenuPath=missionCommands.addCommandForGroup(self.MenuClient:GetClientGroupID(),MenuText,MenuParentPath,self.MenuCallHandler)
+MenuPath[MenuPathID]=self.MenuPath
+if ParentMenu and ParentMenu.Menus then
+ParentMenu.Menus[self.MenuPath]=self
+end
+return self
+end
+function MENU_CLIENT_COMMAND:Remove()
+self:F(self.MenuPath)
+if not _MENUCLIENTS[self.MenuClientGroupID]then
+_MENUCLIENTS[self.MenuClientGroupID]={}
+end
+local MenuPath=_MENUCLIENTS[self.MenuClientGroupID]
+if MenuPath[table.concat(self.MenuParentPath).."/"..self.MenuText]then
+MenuPath[table.concat(self.MenuParentPath).."/"..self.MenuText]=nil
+end
+missionCommands.removeItemForGroup(self.MenuClient:GetClientGroupID(),self.MenuPath)
+self.ParentMenu.Menus[self.MenuPath]=nil
+return nil
+end
+end
+do
+local _MENUGROUPS={}
+MENU_GROUP={
+ClassName="MENU_GROUP"
+}
+function MENU_GROUP:New(MenuGroup,MenuText,ParentMenu)
+MenuGroup._Menus=MenuGroup._Menus or{}
+local Path=(ParentMenu and(table.concat(ParentMenu.MenuPath or{},"@").."@"..MenuText))or MenuText
+if MenuGroup._Menus[Path]then
+self=MenuGroup._Menus[Path]
+else
+self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
+MenuGroup._Menus[Path]=self
+self.MenuGroup=MenuGroup
+self.Path=Path
+self.MenuGroupID=MenuGroup:GetID()
+self.MenuText=MenuText
+self.ParentMenu=ParentMenu
+self:T({"Adding Menu ",MenuText,self.MenuParentPath})
+self.MenuPath=missionCommands.addSubMenuForGroup(self.MenuGroupID,MenuText,self.MenuParentPath)
+if self.ParentMenu and self.ParentMenu.Menus then
+self.ParentMenu.Menus[MenuText]=self
+self:F({self.ParentMenu.Menus,MenuText})
+self.ParentMenu.MenuCount=self.ParentMenu.MenuCount+1
+end
+end
+return self
+end
+function MENU_GROUP:RemoveSubMenus(MenuTime,MenuTag)
+self:T({"Removing Group SubMenus:",MenuTime,MenuTag,self.MenuGroup:GetName(),self.MenuPath})
+for MenuText,Menu in pairs(self.Menus)do
+Menu:Remove(MenuTime,MenuTag)
+end
+end
+function MENU_GROUP:Remove(MenuTime,MenuTag)
+self:RemoveSubMenus(MenuTime,MenuTag)
+if not MenuTime or self.MenuTime~=MenuTime then
+if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
+if self.MenuGroup._Menus[self.Path]then
+self=self.MenuGroup._Menus[self.Path]
+missionCommands.removeItemForGroup(self.MenuGroupID,self.MenuPath)
+if self.ParentMenu then
+self.ParentMenu.Menus[self.MenuText]=nil
+self.ParentMenu.MenuCount=self.ParentMenu.MenuCount-1
+if self.ParentMenu.MenuCount==0 then
+if self.MenuRemoveParent==true then
+self:T2("Removing Parent Menu ")
+self.ParentMenu:Remove()
+end
+end
+end
+end
+self:T({"Removing Group Menu:",MenuGroup=self.MenuGroup:GetName()})
+self.MenuGroup._Menus[self.Path]=nil
+self=nil
+end
+end
+return nil
+end
+MENU_GROUP_COMMAND={
+ClassName="MENU_GROUP_COMMAND"
+}
+function MENU_GROUP_COMMAND:New(MenuGroup,MenuText,ParentMenu,CommandMenuFunction,...)
+MenuGroup._Menus=MenuGroup._Menus or{}
+local Path=(ParentMenu and(table.concat(ParentMenu.MenuPath or{},"@").."@"..MenuText))or MenuText
+if MenuGroup._Menus[Path]then
+self=MenuGroup._Menus[Path]
+self:SetCommandMenuFunction(CommandMenuFunction)
+self:SetCommandMenuArguments(arg)
+return self
+end
+self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg))
+MenuGroup._Menus[Path]=self
+self.Path=Path
+self.MenuGroup=MenuGroup
+self.MenuGroupID=MenuGroup:GetID()
+self.MenuText=MenuText
+self.ParentMenu=ParentMenu
+self:F({"Adding Group Command Menu:",MenuGroup=MenuGroup:GetName(),MenuText=MenuText,MenuPath=self.MenuParentPath})
+self.MenuPath=missionCommands.addCommandForGroup(self.MenuGroupID,MenuText,self.MenuParentPath,self.MenuCallHandler)
+if self.ParentMenu and self.ParentMenu.Menus then
+self.ParentMenu.Menus[MenuText]=self
+self.ParentMenu.MenuCount=self.ParentMenu.MenuCount+1
+self:F2({ParentMenu.Menus,MenuText})
+end
+return self
+end
+function MENU_GROUP_COMMAND:Remove(MenuTime,MenuTag)
+if not MenuTime or self.MenuTime~=MenuTime then
+if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
+if self.MenuGroup._Menus[self.Path]then
+self=self.MenuGroup._Menus[self.Path]
+missionCommands.removeItemForGroup(self.MenuGroupID,self.MenuPath)
+self.ParentMenu.Menus[self.MenuText]=nil
+self.ParentMenu.MenuCount=self.ParentMenu.MenuCount-1
+if self.ParentMenu.MenuCount==0 then
+if self.MenuRemoveParent==true then
+self:T2("Removing Parent Menu ")
+self.ParentMenu:Remove()
+end
+end
+self.MenuGroup._Menus[self.Path]=nil
+self=nil
+end
+end
+end
+return nil
+end
+end
+ZONE_BASE={
+ClassName="ZONE_BASE",
+ZoneName="",
+ZoneProbability=1,
+}
+function ZONE_BASE:New(ZoneName)
+local self=BASE:Inherit(self,BASE:New())
+self:F(ZoneName)
+self.ZoneName=ZoneName
+return self
+end
+function ZONE_BASE:GetName()
+self:F2()
+return self.ZoneName
+end
+function ZONE_BASE:SetName(ZoneName)
+self:F2()
+self.ZoneName=ZoneName
+end
+function ZONE_BASE:IsVec2InZone(Vec2)
+self:F2(Vec2)
+return false
+end
+function ZONE_BASE:IsVec3InZone(Vec3)
+self:F2(Vec3)
+local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z})
+return InZone
+end
+function ZONE_BASE:IsPointVec2InZone(PointVec2)
+self:F2(PointVec2)
+local InZone=self:IsVec2InZone(PointVec2:GetVec2())
+return InZone
+end
+function ZONE_BASE:IsPointVec3InZone(PointVec3)
+self:F2(PointVec3)
+local InZone=self:IsPointVec2InZone(PointVec3)
+return InZone
+end
+function ZONE_BASE:GetVec2()
+self:F2(self.ZoneName)
+return nil
+end
+function ZONE_BASE:GetPointVec2()
+self:F2(self.ZoneName)
+local Vec2=self:GetVec2()
+local PointVec2=POINT_VEC2:NewFromVec2(Vec2)
+self:T2({PointVec2})
+return PointVec2
+end
+function ZONE_BASE:GetCoordinate()
+self:F2(self.ZoneName)
+local Vec2=self:GetVec2()
+local Coordinate=COORDINATE:NewFromVec2(Vec2)
+self:T2({Coordinate})
+return Coordinate
+end
+function ZONE_BASE:GetVec3(Height)
+self:F2(self.ZoneName)
+Height=Height or 0
+local Vec2=self:GetVec2()
+local Vec3={x=Vec2.x,y=Height and Height or land.getHeight(self:GetVec2()),z=Vec2.y}
+self:T2({Vec3})
+return Vec3
+end
+function ZONE_BASE:GetPointVec3(Height)
+self:F2(self.ZoneName)
+local Vec3=self:GetVec3(Height)
+local PointVec3=POINT_VEC3:NewFromVec3(Vec3)
+self:T2({PointVec3})
+return PointVec3
+end
+function ZONE_BASE:GetCoordinate(Height)
+self:F2(self.ZoneName)
+local Vec3=self:GetVec3(Height)
+local PointVec3=COORDINATE:NewFromVec3(Vec3)
+self:T2({PointVec3})
+return PointVec3
+end
+function ZONE_BASE:GetRandomVec2()
+return nil
+end
+function ZONE_BASE:GetRandomPointVec2()
+return nil
+end
+function ZONE_BASE:GetRandomPointVec3()
+return nil
+end
+function ZONE_BASE:GetBoundingSquare()
+return nil
+end
+function ZONE_BASE:BoundZone()
+self:F2()
+end
+function ZONE_BASE:SmokeZone(SmokeColor)
+self:F2(SmokeColor)
+end
+function ZONE_BASE:SetZoneProbability(ZoneProbability)
+self:F2(ZoneProbability)
+self.ZoneProbability=ZoneProbability or 1
+return self
+end
+function ZONE_BASE:GetZoneProbability()
+self:F2()
+return self.ZoneProbability
+end
+function ZONE_BASE:GetZoneMaybe()
+self:F2()
+local Randomization=math.random()
+if Randomization<=self.ZoneProbability then
+return self
+else
+return nil
+end
+end
+ZONE_RADIUS={
+ClassName="ZONE_RADIUS",
+}
+function ZONE_RADIUS:New(ZoneName,Vec2,Radius)
+local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName))
+self:F({ZoneName,Vec2,Radius})
+self.Radius=Radius
+self.Vec2=Vec2
+return self
+end
+function ZONE_RADIUS:BoundZone(Points,CountryID,UnBound)
+local Point={}
+local Vec2=self:GetVec2()
+Points=Points and Points or 360
+local Angle
+local RadialBase=math.pi*2
+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()
+local CountryName=_DATABASE.COUNTRY_NAME[CountryID]
+local Tire={
+["country"]=CountryName,
+["category"]="Fortifications",
+["canCargo"]=false,
+["shape_name"]="H-tyre_B_WF",
+["type"]="Black_Tyre_WF",
+["y"]=Point.y,
+["x"]=Point.x,
+["name"]=string.format("%s-Tire #%0d",self:GetName(),Angle),
+["heading"]=0,
+}
+local Group=coalition.addStaticObject(CountryID,Tire)
+if UnBound and UnBound==true then
+Group:destroy()
+end
+end
+return self
+end
+function ZONE_RADIUS:SmokeZone(SmokeColor,Points)
+self:F2(SmokeColor)
+local Point={}
+local Vec2=self:GetVec2()
+Points=Points and Points or 360
+local Angle
+local RadialBase=math.pi*2
+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):Smoke(SmokeColor)
+end
+return self
+end
+function ZONE_RADIUS:FlareZone(FlareColor,Points,Azimuth)
+self:F2({FlareColor,Azimuth})
+local Point={}
+local Vec2=self:GetVec2()
+Points=Points and Points or 360
+local Angle
+local RadialBase=math.pi*2
+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):Flare(FlareColor,Azimuth)
+end
+return self
+end
+function ZONE_RADIUS:GetRadius()
+self:F2(self.ZoneName)
+self:T2({self.Radius})
+return self.Radius
+end
+function ZONE_RADIUS:SetRadius(Radius)
+self:F2(self.ZoneName)
+self.Radius=Radius
+self:T2({self.Radius})
+return self.Radius
+end
+function ZONE_RADIUS:GetVec2()
+self:F2(self.ZoneName)
+self:T2({self.Vec2})
+return self.Vec2
+end
+function ZONE_RADIUS:SetVec2(Vec2)
+self:F2(self.ZoneName)
+self.Vec2=Vec2
+self:T2({self.Vec2})
+return self.Vec2
+end
+function ZONE_RADIUS:GetVec3(Height)
+self:F2({self.ZoneName,Height})
+Height=Height or 0
+local Vec2=self:GetVec2()
+local Vec3={x=Vec2.x,y=land.getHeight(self:GetVec2())+Height,z=Vec2.y}
+self:T2({Vec3})
+return Vec3
+end
+function ZONE_RADIUS:Scan()
+self.Coalitions={}
+local ZoneCoord=self:GetCoordinate()
+local ZoneRadius=self:GetRadius()
+self:E({ZoneCoord=ZoneCoord,ZoneRadius=ZoneRadius,ZoneCoordLL=ZoneCoord:ToStringLLDMS()})
+local SphereSearch={
+id=world.VolumeType.SPHERE,
+params={
+point=ZoneCoord:GetVec3(),
+radius=ZoneRadius,
+}
+}
+local function EvaluateZone(ZoneDCSUnit)
+if ZoneDCSUnit:isExist()then
+local CategoryDCSUnit=ZoneDCSUnit:getCategory()
+if(CategoryDCSUnit==Object.Category.UNIT and ZoneDCSUnit:isActive())or
+CategoryDCSUnit==Object.Category.STATIC then
+local CoalitionDCSUnit=ZoneDCSUnit:getCoalition()
+self.Coalitions[CoalitionDCSUnit]=true
+self:E({Name=ZoneDCSUnit:getName(),Coalition=CoalitionDCSUnit})
+end
+end
+return true
+end
+world.searchObjects({Object.Category.UNIT,Object.Category.STATIC},SphereSearch,EvaluateZone)
+end
+function ZONE_RADIUS:CountCoalitions()
+local Count=0
+for CoalitionID,Coalition in pairs(self.Coalitions)do
+Count=Count+1
+end
+return Count
+end
+function ZONE_RADIUS:IsAllInZoneOfCoalition(Coalition)
+return self:CountCoalitions()==1 and self.Coalitions[Coalition]==true
+end
+function ZONE_RADIUS:IsAllInZoneOfOtherCoalition(Coalition)
+self:E({Coalitions=self.Coalitions,Count=self:CountCoalitions()})
+return self:CountCoalitions()==1 and self.Coalitions[Coalition]==nil
+end
+function ZONE_RADIUS:IsSomeInZoneOfCoalition(Coalition)
+return self:CountCoalitions()>1 and self.Coalitions[Coalition]==true
+end
+function ZONE_RADIUS:IsNoneInZoneOfCoalition(Coalition)
+return self.Coalitions[Coalition]==nil
+end
+function ZONE_RADIUS:IsNoneInZone()
+return self:CountCoalitions()==0
+end
+function ZONE_RADIUS:GetCoalition()
+local Count=0
+local ReturnCoalition=nil
+for CoalitionID,Coalition in pairs(self.Coalitions)do
+Count=Count+1
+ReturnCoalition=CoalitionID
+end
+if Count~=1 then
+ReturnCoalition=nil
+end
+return ReturnCoalition
+end
+function ZONE_RADIUS:SearchZone(EvaluateFunction)
+local SearchZoneResult=true
+local ZoneCoord=self:GetCoordinate()
+local ZoneRadius=self:GetRadius()
+self:E({ZoneCoord=ZoneCoord,ZoneRadius=ZoneRadius,ZoneCoordLL=ZoneCoord:ToStringLLDMS()})
+local SphereSearch={
+id=world.VolumeType.SPHERE,
+params={
+point=ZoneCoord:GetVec3(),
+radius=ZoneRadius/2,
+}
+}
+local function EvaluateZone(ZoneDCSUnit)
+env.info(ZoneDCSUnit:getName())
+local ZoneUnit=UNIT:Find(ZoneDCSUnit)
+return EvaluateFunction(ZoneUnit)
+end
+world.searchObjects(Object.Category.UNIT,SphereSearch,EvaluateZone)
+end
+function ZONE_RADIUS:IsVec2InZone(Vec2)
+self:F2(Vec2)
+local ZoneVec2=self:GetVec2()
+if ZoneVec2 then
+if((Vec2.x-ZoneVec2.x)^2+(Vec2.y-ZoneVec2.y)^2)^0.5<=self:GetRadius()then
+return true
+end
+end
+return false
+end
+function ZONE_RADIUS:IsVec3InZone(Vec3)
+self:F2(Vec3)
+local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z})
+return InZone
+end
+function ZONE_RADIUS:GetRandomVec2(inner,outer)
+self:F(self.ZoneName,inner,outer)
+local Point={}
+local Vec2=self:GetVec2()
+local _inner=inner or 0
+local _outer=outer or self:GetRadius()
+local angle=math.random()*math.pi*2;
+Point.x=Vec2.x+math.cos(angle)*math.random(_inner,_outer);
+Point.y=Vec2.y+math.sin(angle)*math.random(_inner,_outer);
+self:T({Point})
+return Point
+end
+function ZONE_RADIUS:GetRandomPointVec2(inner,outer)
+self:F(self.ZoneName,inner,outer)
+local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2())
+self:T3({PointVec2})
+return PointVec2
+end
+function ZONE_RADIUS:GetRandomPointVec3(inner,outer)
+self:F(self.ZoneName,inner,outer)
+local PointVec3=POINT_VEC3:NewFromVec2(self:GetRandomVec2())
+self:T3({PointVec3})
+return PointVec3
+end
+function ZONE_RADIUS:GetRandomCoordinate(inner,outer)
+self:F(self.ZoneName,inner,outer)
+local Coordinate=COORDINATE:NewFromVec2(self:GetRandomVec2())
+self:T3({Coordinate=Coordinate})
+return Coordinate
+end
+ZONE={
+ClassName="ZONE",
+}
+function ZONE:New(ZoneName)
+local Zone=trigger.misc.getZone(ZoneName)
+if not Zone then
+error("Zone "..ZoneName.." does not exist.")
+return nil
+end
+local self=BASE:Inherit(self,ZONE_RADIUS:New(ZoneName,{x=Zone.point.x,y=Zone.point.z},Zone.radius))
+self:F(ZoneName)
+self.Zone=Zone
+return self
+end
+ZONE_UNIT={
+ClassName="ZONE_UNIT",
+}
+function ZONE_UNIT:New(ZoneName,ZoneUNIT,Radius)
+local self=BASE:Inherit(self,ZONE_RADIUS:New(ZoneName,ZoneUNIT:GetVec2(),Radius))
+self:F({ZoneName,ZoneUNIT:GetVec2(),Radius})
+self.ZoneUNIT=ZoneUNIT
+self.LastVec2=ZoneUNIT:GetVec2()
+return self
+end
+function ZONE_UNIT:GetVec2()
+self:F2(self.ZoneName)
+local ZoneVec2=self.ZoneUNIT:GetVec2()
+if ZoneVec2 then
+self.LastVec2=ZoneVec2
+return ZoneVec2
+else
+return self.LastVec2
+end
+self:T2({ZoneVec2})
+return nil
+end
+function ZONE_UNIT:GetRandomVec2()
+self:F(self.ZoneName)
+local RandomVec2={}
+local Vec2=self.ZoneUNIT:GetVec2()
+if not Vec2 then
+Vec2=self.LastVec2
+end
+local angle=math.random()*math.pi*2;
+RandomVec2.x=Vec2.x+math.cos(angle)*math.random()*self:GetRadius();
+RandomVec2.y=Vec2.y+math.sin(angle)*math.random()*self:GetRadius();
+self:T({RandomVec2})
+return RandomVec2
+end
+function ZONE_UNIT:GetVec3(Height)
+self:F2(self.ZoneName)
+Height=Height or 0
+local Vec2=self:GetVec2()
+local Vec3={x=Vec2.x,y=land.getHeight(self:GetVec2())+Height,z=Vec2.y}
+self:T2({Vec3})
+return Vec3
+end
+ZONE_GROUP={
+ClassName="ZONE_GROUP",
+}
+function ZONE_GROUP:New(ZoneName,ZoneGROUP,Radius)
+local self=BASE:Inherit(self,ZONE_RADIUS:New(ZoneName,ZoneGROUP:GetVec2(),Radius))
+self:F({ZoneName,ZoneGROUP:GetVec2(),Radius})
+self._.ZoneGROUP=ZoneGROUP
+return self
+end
+function ZONE_GROUP:GetVec2()
+self:F(self.ZoneName)
+local ZoneVec2=self._.ZoneGROUP:GetVec2()
+self:T({ZoneVec2})
+return ZoneVec2
+end
+function ZONE_GROUP:GetRandomVec2()
+self:F(self.ZoneName)
+local Point={}
+local Vec2=self._.ZoneGROUP:GetVec2()
+local angle=math.random()*math.pi*2;
+Point.x=Vec2.x+math.cos(angle)*math.random()*self:GetRadius();
+Point.y=Vec2.y+math.sin(angle)*math.random()*self:GetRadius();
+self:T({Point})
+return Point
+end
+function ZONE_GROUP:GetRandomPointVec2(inner,outer)
+self:F(self.ZoneName,inner,outer)
+local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2())
+self:T3({PointVec2})
+return PointVec2
+end
+ZONE_POLYGON_BASE={
+ClassName="ZONE_POLYGON_BASE",
+}
+function ZONE_POLYGON_BASE:New(ZoneName,PointsArray)
+local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName))
+self:F({ZoneName,PointsArray})
+local i=0
+self._.Polygon={}
+for i=1,#PointsArray do
+self._.Polygon[i]={}
+self._.Polygon[i].x=PointsArray[i].x
+self._.Polygon[i].y=PointsArray[i].y
+end
+return self
+end
+function ZONE_POLYGON_BASE:GetVec2()
+self:F(self.ZoneName)
+local Bounds=self:GetBoundingSquare()
+return{x=(Bounds.x2+Bounds.x1)/2,y=(Bounds.y2+Bounds.y1)/2}
+end
+function ZONE_POLYGON_BASE:Flush()
+self:F2()
+self:E({Polygon=self.ZoneName,Coordinates=self._.Polygon})
+return self
+end
+function ZONE_POLYGON_BASE:BoundZone(UnBound)
+local i
+local j
+local Segments=10
+i=1
+j=#self._.Polygon
+while i<=#self._.Polygon do
+self:T({i,j,self._.Polygon[i],self._.Polygon[j]})
+local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x
+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)
+local Tire={
+["country"]="USA",
+["category"]="Fortifications",
+["canCargo"]=false,
+["shape_name"]="H-tyre_B_WF",
+["type"]="Black_Tyre_WF",
+["y"]=PointY,
+["x"]=PointX,
+["name"]=string.format("%s-Tire #%0d",self:GetName(),((i-1)*Segments)+Segment),
+["heading"]=0,
+}
+local Group=coalition.addStaticObject(country.id.USA,Tire)
+if UnBound and UnBound==true then
+Group:destroy()
+end
+end
+j=i
+i=i+1
+end
+return self
+end
+function ZONE_POLYGON_BASE:SmokeZone(SmokeColor)
+self:F2(SmokeColor)
+local i
+local j
+local Segments=10
+i=1
+j=#self._.Polygon
+while i<=#self._.Polygon do
+self:T({i,j,self._.Polygon[i],self._.Polygon[j]})
+local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x
+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)
+end
+j=i
+i=i+1
+end
+return self
+end
+function ZONE_POLYGON_BASE:IsVec2InZone(Vec2)
+self:F2(Vec2)
+local Next
+local Prev
+local InPolygon=false
+Next=1
+Prev=#self._.Polygon
+while Next<=#self._.Polygon do
+self:T({Next,Prev,self._.Polygon[Next],self._.Polygon[Prev]})
+if(((self._.Polygon[Next].y>Vec2.y)~=(self._.Polygon[Prev].y>Vec2.y))and
+(Vec2.x<(self._.Polygon[Prev].x-self._.Polygon[Next].x)*(Vec2.y-self._.Polygon[Next].y)/(self._.Polygon[Prev].y-self._.Polygon[Next].y)+self._.Polygon[Next].x)
+)then
+InPolygon=not InPolygon
+end
+self:T2({InPolygon=InPolygon})
+Prev=Next
+Next=Next+1
+end
+self:T({InPolygon=InPolygon})
+return InPolygon
+end
+function ZONE_POLYGON_BASE:GetRandomVec2()
+self:F2()
+local Vec2Found=false
+local Vec2
+local BS=self:GetBoundingSquare()
+self:T2(BS)
+while Vec2Found==false do
+Vec2={x=math.random(BS.x1,BS.x2),y=math.random(BS.y1,BS.y2)}
+self:T2(Vec2)
+if self:IsVec2InZone(Vec2)then
+Vec2Found=true
+end
+end
+self:T2(Vec2)
+return Vec2
+end
+function ZONE_POLYGON_BASE:GetRandomPointVec2()
+self:F2()
+local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2())
+self:T2(PointVec2)
+return PointVec2
+end
+function ZONE_POLYGON_BASE:GetRandomPointVec3()
+self:F2()
+local PointVec3=POINT_VEC3:NewFromVec2(self:GetRandomVec2())
+self:T2(PointVec3)
+return PointVec3
+end
+function ZONE_POLYGON_BASE:GetRandomCoordinate()
+self:F2()
+local Coordinate=COORDINATE:NewFromVec2(self:GetRandomVec2())
+self:T2(Coordinate)
+return Coordinate
+end
+function ZONE_POLYGON_BASE:GetBoundingSquare()
+local x1=self._.Polygon[1].x
+local y1=self._.Polygon[1].y
+local x2=self._.Polygon[1].x
+local y2=self._.Polygon[1].y
+for i=2,#self._.Polygon do
+self:T2({self._.Polygon[i],x1,y1,x2,y2})
+x1=(x1>self._.Polygon[i].x)and self._.Polygon[i].x or x1
+x2=(x2self._.Polygon[i].y)and self._.Polygon[i].y or y1
+y2=(y20))then
+for group_num,Template in pairs(obj_type_data.group)do
+if obj_type_name~="static"and Template and Template.units and type(Template.units)=='table'then
+self:_RegisterGroupTemplate(
+Template,
+CoalitionSide,
+_DATABASECategory[string.lower(CategoryName)],
+CountryID
+)
+else
+self:_RegisterStaticTemplate(
+Template,
+CoalitionSide,
+_DATABASECategory[string.lower(CategoryName)],
+CountryID
+)
+end
+end
+end
+end
+end
+end
+end
+end
+end
+end
+for ZoneID,ZoneData in pairs(env.mission.triggers.zones)do
+local ZoneName=ZoneData.name
+self.ZONENAMES[ZoneName]=ZoneName
+end
+return self
+end
+function DATABASE:AccountHits(Event)
+self:F({Event})
+if Event.IniPlayerName~=nil then
+self:T("Hitting Something")
+if Event.TgtCategory then
+self.HITS[Event.TgtUnitName]=self.HITS[Event.TgtUnitName]or{}
+local Hit=self.HITS[Event.TgtUnitName]
+Hit.Players=Hit.Players or{}
+Hit.Players[Event.IniPlayerName]=true
+end
+end
+if Event.WeaponPlayerName~=nil then
+self:T("Hitting Scenery")
+if Event.TgtCategory then
+if Event.IniCoalition then
+self.HITS[Event.TgtUnitName]=self.HITS[Event.TgtUnitName]or{}
+local Hit=self.HITS[Event.TgtUnitName]
+Hit.Players=Hit.Players or{}
+Hit.Players[Event.WeaponPlayerName]=true
+else
+end
+end
+end
+end
+function DATABASE:AccountDestroys(Event)
+self:F({Event})
+local TargetUnit=nil
+local TargetGroup=nil
+local TargetUnitName=""
+local TargetGroupName=""
+local TargetPlayerName=""
+local TargetCoalition=nil
+local TargetCategory=nil
+local TargetType=nil
+local TargetUnitCoalition=nil
+local TargetUnitCategory=nil
+local TargetUnitType=nil
+if Event.IniDCSUnit then
+TargetUnit=Event.IniUnit
+TargetUnitName=Event.IniDCSUnitName
+TargetGroup=Event.IniDCSGroup
+TargetGroupName=Event.IniDCSGroupName
+TargetPlayerName=Event.IniPlayerName
+TargetCoalition=Event.IniCoalition
+TargetCategory=Event.IniCategory
+TargetType=Event.IniTypeName
+TargetUnitType=TargetType
+self:T({TargetUnitName,TargetGroupName,TargetPlayerName,TargetCoalition,TargetCategory,TargetType})
+end
+self:T("Something got destroyed")
+local Destroyed=false
+if self.HITS[Event.IniUnitName]then
+self.DESTROYS[Event.IniUnitName]=self.DESTROYS[Event.IniUnitName]or{}
+self.DESTROYS[Event.IniUnitName]=true
+end
+end
+SET_BASE={
+ClassName="SET_BASE",
+Filter={},
+Set={},
+List={},
+Index={},
+}
+function SET_BASE:New(Database)
+local self=BASE:Inherit(self,BASE:New())
+self.Database=Database
+self.YieldInterval=10
+self.TimeInterval=0.001
+self.Set={}
+self.Index={}
+self.CallScheduler=SCHEDULER:New(self)
+self:SetEventPriority(2)
+return self
+end
+function SET_BASE:_Find(ObjectName)
+local ObjectFound=self.Set[ObjectName]
+return ObjectFound
+end
+function SET_BASE:GetSet()
+self:F2()
+return self.Set
+end
+function SET_BASE:Add(ObjectName,Object)
+self:F(ObjectName)
+self.Set[ObjectName]=Object
+table.insert(self.Index,ObjectName)
+end
+function SET_BASE:AddObject(Object)
+self:F2(Object.ObjectName)
+self:T(Object.UnitName)
+self:T(Object.ObjectName)
+self:Add(Object.ObjectName,Object)
+end
+function SET_BASE:Remove(ObjectName)
+local Object=self.Set[ObjectName]
+self:F3({ObjectName,Object})
+if Object then
+for Index,Key in ipairs(self.Index)do
+if Key==ObjectName then
+table.remove(self.Index,Index)
+self.Set[ObjectName]=nil
+break
+end
+end
+end
+end
+function SET_BASE:Get(ObjectName)
+self:F(ObjectName)
+local Object=self.Set[ObjectName]
+self:T3({ObjectName,Object})
+return Object
+end
+function SET_BASE:GetFirst()
+local ObjectName=self.Index[1]
+local FirstObject=self.Set[ObjectName]
+self:T3({FirstObject})
+return FirstObject
+end
+function SET_BASE:GetLast()
+local ObjectName=self.Index[#self.Index]
+local LastObject=self.Set[ObjectName]
+self:T3({LastObject})
+return LastObject
+end
+function SET_BASE:GetRandom()
+local RandomItem=self.Set[self.Index[math.random(#self.Index)]]
+self:T3({RandomItem})
+return RandomItem
+end
+function SET_BASE:Count()
+return self.Index and#self.Index or 0
+end
+function SET_BASE:SetDatabase(BaseSet)
+local OtherFilter=routines.utils.deepCopy(BaseSet.Filter)
+self.Filter=OtherFilter
+self.Database=BaseSet:GetSet()
+return self
+end
+function SET_BASE:SetIteratorIntervals(YieldInterval,TimeInterval)
+self.YieldInterval=YieldInterval
+self.TimeInterval=TimeInterval
+return self
+end
+function SET_BASE:FilterOnce()
+for ObjectName,Object in pairs(self.Database)do
+if self:IsIncludeObject(Object)then
+self:Add(ObjectName,Object)
+end
+end
+return self
+end
+function SET_BASE:_FilterStart()
+for ObjectName,Object in pairs(self.Database)do
+if self:IsIncludeObject(Object)then
+self:E({"Adding Object:",ObjectName})
+self:Add(ObjectName,Object)
+end
+end
+self:HandleEvent(EVENTS.Birth,self._EventOnBirth)
+self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
+self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
+self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventOnPlayerEnterUnit)
+self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventOnPlayerLeaveUnit)
+return self
+end
+function SET_BASE:FilterDeads()
+self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
+return self
+end
+function SET_BASE:FilterCrashes()
+self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
+return self
+end
+function SET_BASE:FilterStop()
+self:UnHandleEvent(EVENTS.Birth)
+self:UnHandleEvent(EVENTS.Dead)
+self:UnHandleEvent(EVENTS.Crash)
+return self
+end
+function SET_BASE:FindNearestObjectFromPointVec2(PointVec2)
+self:F2(PointVec2)
+local NearestObject=nil
+local ClosestDistance=nil
+for ObjectID,ObjectData in pairs(self.Set)do
+if NearestObject==nil then
+NearestObject=ObjectData
+ClosestDistance=PointVec2:DistanceFromVec2(ObjectData:GetVec2())
+else
+local Distance=PointVec2:DistanceFromVec2(ObjectData:GetVec2())
+if DistanceMaxThreatLevelA2G then
+MaxThreatLevelA2G=ThreatLevelA2G
+MaxThreatText=ThreatText
+end
+end
+self:F({MaxThreatLevelA2G=MaxThreatLevelA2G,MaxThreatText=MaxThreatText})
+return MaxThreatLevelA2G,MaxThreatText
+end
+function SET_UNIT:GetCoordinate()
+local Coordinate=self:GetFirst():GetCoordinate()
+local x1=Coordinate.x
+local x2=Coordinate.x
+local y1=Coordinate.y
+local y2=Coordinate.y
+local z1=Coordinate.z
+local z2=Coordinate.z
+local MaxVelocity=0
+local AvgHeading=nil
+local MovingCount=0
+for UnitName,UnitData in pairs(self:GetSet())do
+local Unit=UnitData
+local Coordinate=Unit:GetCoordinate()
+x1=(Coordinate.xx2)and Coordinate.x or x2
+y1=(Coordinate.yy2)and Coordinate.y or y2
+z1=(Coordinate.yz2)and Coordinate.z or z2
+local Velocity=Coordinate:GetVelocity()
+if Velocity~=0 then
+MaxVelocity=(MaxVelocity5 then
+HeadingSet=nil
+break
+end
+end
+end
+end
+return HeadingSet
+end
+function SET_UNIT:HasRadar(RadarType)
+self:F2(RadarType)
+local RadarCount=0
+for UnitID,UnitData in pairs(self:GetSet())do
+local UnitSensorTest=UnitData
+local HasSensors
+if RadarType then
+HasSensors=UnitSensorTest:HasSensors(Unit.SensorType.RADAR,RadarType)
+else
+HasSensors=UnitSensorTest:HasSensors(Unit.SensorType.RADAR)
+end
+self:T3(HasSensors)
+if HasSensors then
+RadarCount=RadarCount+1
+end
+end
+return RadarCount
+end
+function SET_UNIT:HasSEAD()
+self:F2()
+local SEADCount=0
+for UnitID,UnitData in pairs(self:GetSet())do
+local UnitSEAD=UnitData
+if UnitSEAD:IsAlive()then
+local UnitSEADAttributes=UnitSEAD:GetDesc().attributes
+local HasSEAD=UnitSEAD:HasSEAD()
+self:T3(HasSEAD)
+if HasSEAD then
+SEADCount=SEADCount+1
+end
+end
+end
+return SEADCount
+end
+function SET_UNIT:HasGroundUnits()
+self:F2()
+local GroundUnitCount=0
+for UnitID,UnitData in pairs(self:GetSet())do
+local UnitTest=UnitData
+if UnitTest:IsGround()then
+GroundUnitCount=GroundUnitCount+1
+end
+end
+return GroundUnitCount
+end
+function SET_UNIT:HasFriendlyUnits(FriendlyCoalition)
+self:F2()
+local FriendlyUnitCount=0
+for UnitID,UnitData in pairs(self:GetSet())do
+local UnitTest=UnitData
+if UnitTest:IsFriendly(FriendlyCoalition)then
+FriendlyUnitCount=FriendlyUnitCount+1
+end
+end
+return FriendlyUnitCount
+end
+function SET_UNIT:IsIncludeObject(MUnit)
+self:F2(MUnit)
+local MUnitInclude=true
+if self.Filter.Coalitions then
+local MUnitCoalition=false
+for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
+self:E({"Coalition:",MUnit:GetCoalition(),self.FilterMeta.Coalitions[CoalitionName],CoalitionName})
+if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MUnit:GetCoalition()then
+MUnitCoalition=true
+end
+end
+MUnitInclude=MUnitInclude and MUnitCoalition
+end
+if self.Filter.Categories then
+local MUnitCategory=false
+for CategoryID,CategoryName in pairs(self.Filter.Categories)do
+self:T3({"Category:",MUnit:GetDesc().category,self.FilterMeta.Categories[CategoryName],CategoryName})
+if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MUnit:GetDesc().category then
+MUnitCategory=true
+end
+end
+MUnitInclude=MUnitInclude and MUnitCategory
+end
+if self.Filter.Types then
+local MUnitType=false
+for TypeID,TypeName in pairs(self.Filter.Types)do
+self:T3({"Type:",MUnit:GetTypeName(),TypeName})
+if TypeName==MUnit:GetTypeName()then
+MUnitType=true
+end
+end
+MUnitInclude=MUnitInclude and MUnitType
+end
+if self.Filter.Countries then
+local MUnitCountry=false
+for CountryID,CountryName in pairs(self.Filter.Countries)do
+self:T3({"Country:",MUnit:GetCountry(),CountryName})
+if country.id[CountryName]==MUnit:GetCountry()then
+MUnitCountry=true
+end
+end
+MUnitInclude=MUnitInclude and MUnitCountry
+end
+if self.Filter.UnitPrefixes then
+local MUnitPrefix=false
+for UnitPrefixId,UnitPrefix in pairs(self.Filter.UnitPrefixes)do
+self:T3({"Prefix:",string.find(MUnit:GetName(),UnitPrefix,1),UnitPrefix})
+if string.find(MUnit:GetName(),UnitPrefix,1)then
+MUnitPrefix=true
+end
+end
+MUnitInclude=MUnitInclude and MUnitPrefix
+end
+if self.Filter.RadarTypes then
+local MUnitRadar=false
+for RadarTypeID,RadarType in pairs(self.Filter.RadarTypes)do
+self:T3({"Radar:",RadarType})
+if MUnit:HasSensors(Unit.SensorType.RADAR,RadarType)==true then
+if MUnit:GetRadar()==true then
+self:T3("RADAR Found")
+end
+MUnitRadar=true
+end
+end
+MUnitInclude=MUnitInclude and MUnitRadar
+end
+if self.Filter.SEAD then
+local MUnitSEAD=false
+if MUnit:HasSEAD()==true then
+self:T3("SEAD Found")
+MUnitSEAD=true
+end
+MUnitInclude=MUnitInclude and MUnitSEAD
+end
+self:T2(MUnitInclude)
+return MUnitInclude
+end
+function SET_UNIT:GetTypeNames(Delimiter)
+Delimiter=Delimiter or", "
+local TypeReport=REPORT:New()
+local Types={}
+for UnitName,UnitData in pairs(self:GetSet())do
+local Unit=UnitData
+local UnitTypeName=Unit:GetTypeName()
+if not Types[UnitTypeName]then
+Types[UnitTypeName]=UnitTypeName
+TypeReport:Add(UnitTypeName)
+end
+end
+return TypeReport:Text(Delimiter)
+end
+end
+do
+SET_STATIC={
+ClassName="SET_STATIC",
+Statics={},
+Filter={
+Coalitions=nil,
+Categories=nil,
+Types=nil,
+Countries=nil,
+StaticPrefixes=nil,
+},
+FilterMeta={
+Coalitions={
+red=coalition.side.RED,
+blue=coalition.side.BLUE,
+neutral=coalition.side.NEUTRAL,
+},
+Categories={
+plane=Unit.Category.AIRPLANE,
+helicopter=Unit.Category.HELICOPTER,
+ground=Unit.Category.GROUND_STATIC,
+ship=Unit.Category.SHIP,
+structure=Unit.Category.STRUCTURE,
+},
+},
+}
+function SET_STATIC:New()
+local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.STATICS))
+return self
+end
+function SET_STATIC:AddStatic(AddStatic)
+self:F2(AddStatic:GetName())
+self:Add(AddStatic:GetName(),AddStatic)
+return self
+end
+function SET_STATIC:AddStaticsByName(AddStaticNames)
+local AddStaticNamesArray=(type(AddStaticNames)=="table")and AddStaticNames or{AddStaticNames}
+self:T(AddStaticNamesArray)
+for AddStaticID,AddStaticName in pairs(AddStaticNamesArray)do
+self:Add(AddStaticName,STATIC:FindByName(AddStaticName))
+end
+return self
+end
+function SET_STATIC:RemoveStaticsByName(RemoveStaticNames)
+local RemoveStaticNamesArray=(type(RemoveStaticNames)=="table")and RemoveStaticNames or{RemoveStaticNames}
+for RemoveStaticID,RemoveStaticName in pairs(RemoveStaticNamesArray)do
+self:Remove(RemoveStaticName)
+end
+return self
+end
+function SET_STATIC:FindStatic(StaticName)
+local StaticFound=self.Set[StaticName]
+return StaticFound
+end
+function SET_STATIC:FilterCoalitions(Coalitions)
+if not self.Filter.Coalitions then
+self.Filter.Coalitions={}
+end
+if type(Coalitions)~="table"then
+Coalitions={Coalitions}
+end
+for CoalitionID,Coalition in pairs(Coalitions)do
+self.Filter.Coalitions[Coalition]=Coalition
+end
+return self
+end
+function SET_STATIC:FilterCategories(Categories)
+if not self.Filter.Categories then
+self.Filter.Categories={}
+end
+if type(Categories)~="table"then
+Categories={Categories}
+end
+for CategoryID,Category in pairs(Categories)do
+self.Filter.Categories[Category]=Category
+end
+return self
+end
+function SET_STATIC:FilterTypes(Types)
+if not self.Filter.Types then
+self.Filter.Types={}
+end
+if type(Types)~="table"then
+Types={Types}
+end
+for TypeID,Type in pairs(Types)do
+self.Filter.Types[Type]=Type
+end
+return self
+end
+function SET_STATIC:FilterCountries(Countries)
+if not self.Filter.Countries then
+self.Filter.Countries={}
+end
+if type(Countries)~="table"then
+Countries={Countries}
+end
+for CountryID,Country in pairs(Countries)do
+self.Filter.Countries[Country]=Country
+end
+return self
+end
+function SET_STATIC:FilterPrefixes(Prefixes)
+if not self.Filter.StaticPrefixes then
+self.Filter.StaticPrefixes={}
+end
+if type(Prefixes)~="table"then
+Prefixes={Prefixes}
+end
+for PrefixID,Prefix in pairs(Prefixes)do
+self.Filter.StaticPrefixes[Prefix]=Prefix
+end
+return self
+end
+function SET_STATIC:FilterStart()
+if _DATABASE then
+self:_FilterStart()
+end
+return self
+end
+function SET_STATIC:AddInDatabase(Event)
+self:F3({Event})
+if Event.IniObjectCategory==Object.Category.STATIC then
+if not self.Database[Event.IniDCSStaticName]then
+self.Database[Event.IniDCSStaticName]=STATIC:Register(Event.IniDCSStaticName)
+self:T3(self.Database[Event.IniDCSStaticName])
+end
+end
+return Event.IniDCSStaticName,self.Database[Event.IniDCSStaticName]
+end
+function SET_STATIC:FindInDatabase(Event)
+self:F2({Event.IniDCSStaticName,self.Set[Event.IniDCSStaticName],Event})
+return Event.IniDCSStaticName,self.Set[Event.IniDCSStaticName]
+end
+do
+function SET_STATIC:IsPatriallyInZone(Zone)
+local IsPartiallyInZone=false
+local function EvaluateZone(ZoneStatic)
+local ZoneStaticName=ZoneStatic:GetName()
+if self:FindStatic(ZoneStaticName)then
+IsPartiallyInZone=true
+return false
+end
+return true
+end
+return IsPartiallyInZone
+end
+function SET_STATIC:IsNotInZone(Zone)
+local IsNotInZone=true
+local function EvaluateZone(ZoneStatic)
+local ZoneStaticName=ZoneStatic:GetName()
+if self:FindStatic(ZoneStaticName)then
+IsNotInZone=false
+return false
+end
+return true
+end
+Zone:Search(EvaluateZone)
+return IsNotInZone
+end
+function SET_STATIC:ForEachStaticInZone(IteratorFunction,...)
+self:F2(arg)
+self:ForEach(IteratorFunction,arg,self.Set)
+return self
+end
+end
+function SET_STATIC:ForEachStatic(IteratorFunction,...)
+self:F2(arg)
+self:ForEach(IteratorFunction,arg,self.Set)
+return self
+end
+function SET_STATIC:ForEachStaticCompletelyInZone(ZoneObject,IteratorFunction,...)
+self:F2(arg)
+self:ForEach(IteratorFunction,arg,self.Set,
+function(ZoneObject,StaticObject)
+if StaticObject:IsInZone(ZoneObject)then
+return true
+else
+return false
+end
+end,{ZoneObject})
+return self
+end
+function SET_STATIC:ForEachStaticNotInZone(ZoneObject,IteratorFunction,...)
+self:F2(arg)
+self:ForEach(IteratorFunction,arg,self.Set,
+function(ZoneObject,StaticObject)
+if StaticObject:IsNotInZone(ZoneObject)then
+return true
+else
+return false
+end
+end,{ZoneObject})
+return self
+end
+function SET_STATIC:GetStaticTypes()
+self:F2()
+local MT={}
+local StaticTypes={}
+for StaticID,StaticData in pairs(self:GetSet())do
+local TextStatic=StaticData
+if TextStatic:IsAlive()then
+local StaticType=TextStatic:GetTypeName()
+if not StaticTypes[StaticType]then
+StaticTypes[StaticType]=1
+else
+StaticTypes[StaticType]=StaticTypes[StaticType]+1
+end
+end
+end
+for StaticTypeID,StaticType in pairs(StaticTypes)do
+MT[#MT+1]=StaticType.." of "..StaticTypeID
+end
+return StaticTypes
+end
+function SET_STATIC:GetStaticTypesText()
+self:F2()
+local MT={}
+local StaticTypes=self:GetStaticTypes()
+for StaticTypeID,StaticType in pairs(StaticTypes)do
+MT[#MT+1]=StaticType.." of "..StaticTypeID
+end
+return table.concat(MT,", ")
+end
+function SET_STATIC:GetCoordinate()
+local Coordinate=self:GetFirst():GetCoordinate()
+local x1=Coordinate.x
+local x2=Coordinate.x
+local y1=Coordinate.y
+local y2=Coordinate.y
+local z1=Coordinate.z
+local z2=Coordinate.z
+local MaxVelocity=0
+local AvgHeading=nil
+local MovingCount=0
+for StaticName,StaticData in pairs(self:GetSet())do
+local Static=StaticData
+local Coordinate=Static:GetCoordinate()
+x1=(Coordinate.xx2)and Coordinate.x or x2
+y1=(Coordinate.yy2)and Coordinate.y or y2
+z1=(Coordinate.yz2)and Coordinate.z or z2
+local Velocity=Coordinate:GetVelocity()
+if Velocity~=0 then
+MaxVelocity=(MaxVelocity5 then
+HeadingSet=nil
+break
+end
+end
+end
+end
+return HeadingSet
+end
+function SET_STATIC:IsIncludeObject(MStatic)
+self:F2(MStatic)
+local MStaticInclude=true
+if self.Filter.Coalitions then
+local MStaticCoalition=false
+for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
+self:T3({"Coalition:",MStatic:GetCoalition(),self.FilterMeta.Coalitions[CoalitionName],CoalitionName})
+if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MStatic:GetCoalition()then
+MStaticCoalition=true
+end
+end
+MStaticInclude=MStaticInclude and MStaticCoalition
+end
+if self.Filter.Categories then
+local MStaticCategory=false
+for CategoryID,CategoryName in pairs(self.Filter.Categories)do
+self:T3({"Category:",MStatic:GetDesc().category,self.FilterMeta.Categories[CategoryName],CategoryName})
+if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MStatic:GetDesc().category then
+MStaticCategory=true
+end
+end
+MStaticInclude=MStaticInclude and MStaticCategory
+end
+if self.Filter.Types then
+local MStaticType=false
+for TypeID,TypeName in pairs(self.Filter.Types)do
+self:T3({"Type:",MStatic:GetTypeName(),TypeName})
+if TypeName==MStatic:GetTypeName()then
+MStaticType=true
+end
+end
+MStaticInclude=MStaticInclude and MStaticType
+end
+if self.Filter.Countries then
+local MStaticCountry=false
+for CountryID,CountryName in pairs(self.Filter.Countries)do
+self:T3({"Country:",MStatic:GetCountry(),CountryName})
+if country.id[CountryName]==MStatic:GetCountry()then
+MStaticCountry=true
+end
+end
+MStaticInclude=MStaticInclude and MStaticCountry
+end
+if self.Filter.StaticPrefixes then
+local MStaticPrefix=false
+for StaticPrefixId,StaticPrefix in pairs(self.Filter.StaticPrefixes)do
+self:T3({"Prefix:",string.find(MStatic:GetName(),StaticPrefix,1),StaticPrefix})
+if string.find(MStatic:GetName(),StaticPrefix,1)then
+MStaticPrefix=true
+end
+end
+MStaticInclude=MStaticInclude and MStaticPrefix
+end
+self:T2(MStaticInclude)
+return MStaticInclude
+end
+function SET_STATIC:GetTypeNames(Delimiter)
+Delimiter=Delimiter or", "
+local TypeReport=REPORT:New()
+local Types={}
+for StaticName,StaticData in pairs(self:GetSet())do
+local Static=StaticData
+local StaticTypeName=Static:GetTypeName()
+if not Types[StaticTypeName]then
+Types[StaticTypeName]=StaticTypeName
+TypeReport:Add(StaticTypeName)
+end
+end
+return TypeReport:Text(Delimiter)
+end
+end
+SET_CLIENT={
+ClassName="SET_CLIENT",
+Clients={},
+Filter={
+Coalitions=nil,
+Categories=nil,
+Types=nil,
+Countries=nil,
+ClientPrefixes=nil,
+},
+FilterMeta={
+Coalitions={
+red=coalition.side.RED,
+blue=coalition.side.BLUE,
+neutral=coalition.side.NEUTRAL,
+},
+Categories={
+plane=Unit.Category.AIRPLANE,
+helicopter=Unit.Category.HELICOPTER,
+ground=Unit.Category.GROUND_UNIT,
+ship=Unit.Category.SHIP,
+structure=Unit.Category.STRUCTURE,
+},
+},
+}
+function SET_CLIENT:New()
+local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.CLIENTS))
+return self
+end
+function SET_CLIENT:AddClientsByName(AddClientNames)
+local AddClientNamesArray=(type(AddClientNames)=="table")and AddClientNames or{AddClientNames}
+for AddClientID,AddClientName in pairs(AddClientNamesArray)do
+self:Add(AddClientName,CLIENT:FindByName(AddClientName))
+end
+return self
+end
+function SET_CLIENT:RemoveClientsByName(RemoveClientNames)
+local RemoveClientNamesArray=(type(RemoveClientNames)=="table")and RemoveClientNames or{RemoveClientNames}
+for RemoveClientID,RemoveClientName in pairs(RemoveClientNamesArray)do
+self:Remove(RemoveClientName.ClientName)
+end
+return self
+end
+function SET_CLIENT:FindClient(ClientName)
+local ClientFound=self.Set[ClientName]
+return ClientFound
+end
+function SET_CLIENT:FilterCoalitions(Coalitions)
+if not self.Filter.Coalitions then
+self.Filter.Coalitions={}
+end
+if type(Coalitions)~="table"then
+Coalitions={Coalitions}
+end
+for CoalitionID,Coalition in pairs(Coalitions)do
+self.Filter.Coalitions[Coalition]=Coalition
+end
+return self
+end
+function SET_CLIENT:FilterCategories(Categories)
+if not self.Filter.Categories then
+self.Filter.Categories={}
+end
+if type(Categories)~="table"then
+Categories={Categories}
+end
+for CategoryID,Category in pairs(Categories)do
+self.Filter.Categories[Category]=Category
+end
+return self
+end
+function SET_CLIENT:FilterTypes(Types)
+if not self.Filter.Types then
+self.Filter.Types={}
+end
+if type(Types)~="table"then
+Types={Types}
+end
+for TypeID,Type in pairs(Types)do
+self.Filter.Types[Type]=Type
+end
+return self
+end
+function SET_CLIENT:FilterCountries(Countries)
+if not self.Filter.Countries then
+self.Filter.Countries={}
+end
+if type(Countries)~="table"then
+Countries={Countries}
+end
+for CountryID,Country in pairs(Countries)do
+self.Filter.Countries[Country]=Country
+end
+return self
+end
+function SET_CLIENT:FilterPrefixes(Prefixes)
+if not self.Filter.ClientPrefixes then
+self.Filter.ClientPrefixes={}
+end
+if type(Prefixes)~="table"then
+Prefixes={Prefixes}
+end
+for PrefixID,Prefix in pairs(Prefixes)do
+self.Filter.ClientPrefixes[Prefix]=Prefix
+end
+return self
+end
+function SET_CLIENT:FilterStart()
+if _DATABASE then
+self:_FilterStart()
+end
+return self
+end
+function SET_CLIENT:AddInDatabase(Event)
+self:F3({Event})
+return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
+end
+function SET_CLIENT:FindInDatabase(Event)
+self:F3({Event})
+return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
+end
+function SET_CLIENT:ForEachClient(IteratorFunction,...)
+self:F2(arg)
+self:ForEach(IteratorFunction,arg,self.Set)
+return self
+end
+function SET_CLIENT:ForEachClientInZone(ZoneObject,IteratorFunction,...)
+self:F2(arg)
+self:ForEach(IteratorFunction,arg,self.Set,
+function(ZoneObject,ClientObject)
+if ClientObject:IsInZone(ZoneObject)then
+return true
+else
+return false
+end
+end,{ZoneObject})
+return self
+end
+function SET_CLIENT:ForEachClientNotInZone(ZoneObject,IteratorFunction,...)
+self:F2(arg)
+self:ForEach(IteratorFunction,arg,self.Set,
+function(ZoneObject,ClientObject)
+if ClientObject:IsNotInZone(ZoneObject)then
+return true
+else
+return false
+end
+end,{ZoneObject})
+return self
+end
+function SET_CLIENT:IsIncludeObject(MClient)
+self:F2(MClient)
+local MClientInclude=true
+if MClient then
+local MClientName=MClient.UnitName
+if self.Filter.Coalitions then
+local MClientCoalition=false
+for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
+local ClientCoalitionID=_DATABASE:GetCoalitionFromClientTemplate(MClientName)
+self:T3({"Coalition:",ClientCoalitionID,self.FilterMeta.Coalitions[CoalitionName],CoalitionName})
+if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==ClientCoalitionID then
+MClientCoalition=true
+end
+end
+self:T({"Evaluated Coalition",MClientCoalition})
+MClientInclude=MClientInclude and MClientCoalition
+end
+if self.Filter.Categories then
+local MClientCategory=false
+for CategoryID,CategoryName in pairs(self.Filter.Categories)do
+local ClientCategoryID=_DATABASE:GetCategoryFromClientTemplate(MClientName)
+self:T3({"Category:",ClientCategoryID,self.FilterMeta.Categories[CategoryName],CategoryName})
+if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==ClientCategoryID then
+MClientCategory=true
+end
+end
+self:T({"Evaluated Category",MClientCategory})
+MClientInclude=MClientInclude and MClientCategory
+end
+if self.Filter.Types then
+local MClientType=false
+for TypeID,TypeName in pairs(self.Filter.Types)do
+self:T3({"Type:",MClient:GetTypeName(),TypeName})
+if TypeName==MClient:GetTypeName()then
+MClientType=true
+end
+end
+self:T({"Evaluated Type",MClientType})
+MClientInclude=MClientInclude and MClientType
+end
+if self.Filter.Countries then
+local MClientCountry=false
+for CountryID,CountryName in pairs(self.Filter.Countries)do
+local ClientCountryID=_DATABASE:GetCountryFromClientTemplate(MClientName)
+self:T3({"Country:",ClientCountryID,country.id[CountryName],CountryName})
+if country.id[CountryName]and country.id[CountryName]==ClientCountryID then
+MClientCountry=true
+end
+end
+self:T({"Evaluated Country",MClientCountry})
+MClientInclude=MClientInclude and MClientCountry
+end
+if self.Filter.ClientPrefixes then
+local MClientPrefix=false
+for ClientPrefixId,ClientPrefix in pairs(self.Filter.ClientPrefixes)do
+self:T3({"Prefix:",string.find(MClient.UnitName,ClientPrefix,1),ClientPrefix})
+if string.find(MClient.UnitName,ClientPrefix,1)then
+MClientPrefix=true
+end
+end
+self:T({"Evaluated Prefix",MClientPrefix})
+MClientInclude=MClientInclude and MClientPrefix
+end
+end
+self:T2(MClientInclude)
+return MClientInclude
+end
+SET_AIRBASE={
+ClassName="SET_AIRBASE",
+Airbases={},
+Filter={
+Coalitions=nil,
+},
+FilterMeta={
+Coalitions={
+red=coalition.side.RED,
+blue=coalition.side.BLUE,
+neutral=coalition.side.NEUTRAL,
+},
+Categories={
+airdrome=Airbase.Category.AIRDROME,
+helipad=Airbase.Category.HELIPAD,
+ship=Airbase.Category.SHIP,
+},
+},
+}
+function SET_AIRBASE:New()
+local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.AIRBASES))
+return self
+end
+function SET_AIRBASE:AddAirbasesByName(AddAirbaseNames)
+local AddAirbaseNamesArray=(type(AddAirbaseNames)=="table")and AddAirbaseNames or{AddAirbaseNames}
+for AddAirbaseID,AddAirbaseName in pairs(AddAirbaseNamesArray)do
+self:Add(AddAirbaseName,AIRBASE:FindByName(AddAirbaseName))
+end
+return self
+end
+function SET_AIRBASE:RemoveAirbasesByName(RemoveAirbaseNames)
+local RemoveAirbaseNamesArray=(type(RemoveAirbaseNames)=="table")and RemoveAirbaseNames or{RemoveAirbaseNames}
+for RemoveAirbaseID,RemoveAirbaseName in pairs(RemoveAirbaseNamesArray)do
+self:Remove(RemoveAirbaseName.AirbaseName)
+end
+return self
+end
+function SET_AIRBASE:FindAirbase(AirbaseName)
+local AirbaseFound=self.Set[AirbaseName]
+return AirbaseFound
+end
+function SET_AIRBASE:FilterCoalitions(Coalitions)
+if not self.Filter.Coalitions then
+self.Filter.Coalitions={}
+end
+if type(Coalitions)~="table"then
+Coalitions={Coalitions}
+end
+for CoalitionID,Coalition in pairs(Coalitions)do
+self.Filter.Coalitions[Coalition]=Coalition
+end
+return self
+end
+function SET_AIRBASE:FilterCategories(Categories)
+if not self.Filter.Categories then
+self.Filter.Categories={}
+end
+if type(Categories)~="table"then
+Categories={Categories}
+end
+for CategoryID,Category in pairs(Categories)do
+self.Filter.Categories[Category]=Category
+end
+return self
+end
+function SET_AIRBASE:FilterStart()
+if _DATABASE then
+self:_FilterStart()
+end
+return self
+end
+function SET_AIRBASE:AddInDatabase(Event)
+self:F3({Event})
+return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
+end
+function SET_AIRBASE:FindInDatabase(Event)
+self:F3({Event})
+return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
+end
+function SET_AIRBASE:ForEachAirbase(IteratorFunction,...)
+self:F2(arg)
+self:ForEach(IteratorFunction,arg,self.Set)
+return self
+end
+function SET_AIRBASE:FindNearestAirbaseFromPointVec2(PointVec2)
+self:F2(PointVec2)
+local NearestAirbase=self:FindNearestObjectFromPointVec2(PointVec2)
+return NearestAirbase
+end
+function SET_AIRBASE:IsIncludeObject(MAirbase)
+self:F2(MAirbase)
+local MAirbaseInclude=true
+if MAirbase then
+local MAirbaseName=MAirbase:GetName()
+if self.Filter.Coalitions then
+local MAirbaseCoalition=false
+for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
+local AirbaseCoalitionID=_DATABASE:GetCoalitionFromAirbase(MAirbaseName)
+self:T3({"Coalition:",AirbaseCoalitionID,self.FilterMeta.Coalitions[CoalitionName],CoalitionName})
+if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==AirbaseCoalitionID then
+MAirbaseCoalition=true
+end
+end
+self:T({"Evaluated Coalition",MAirbaseCoalition})
+MAirbaseInclude=MAirbaseInclude and MAirbaseCoalition
+end
+if self.Filter.Categories then
+local MAirbaseCategory=false
+for CategoryID,CategoryName in pairs(self.Filter.Categories)do
+local AirbaseCategoryID=_DATABASE:GetCategoryFromAirbase(MAirbaseName)
+self:T3({"Category:",AirbaseCategoryID,self.FilterMeta.Categories[CategoryName],CategoryName})
+if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==AirbaseCategoryID then
+MAirbaseCategory=true
+end
+end
+self:T({"Evaluated Category",MAirbaseCategory})
+MAirbaseInclude=MAirbaseInclude and MAirbaseCategory
+end
+end
+self:T2(MAirbaseInclude)
+return MAirbaseInclude
+end
+SET_CARGO={
+ClassName="SET_CARGO",
+Cargos={},
+Filter={
+Coalitions=nil,
+Types=nil,
+Countries=nil,
+ClientPrefixes=nil,
+},
+FilterMeta={
+Coalitions={
+red=coalition.side.RED,
+blue=coalition.side.BLUE,
+neutral=coalition.side.NEUTRAL,
+},
+},
+}
+function SET_CARGO:New()
+local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.CARGOS))
+return self
+end
+function SET_CARGO:AddCargosByName(AddCargoNames)
+local AddCargoNamesArray=(type(AddCargoNames)=="table")and AddCargoNames or{AddCargoNames}
+for AddCargoID,AddCargoName in pairs(AddCargoNamesArray)do
+self:Add(AddCargoName,CARGO:FindByName(AddCargoName))
+end
+return self
+end
+function SET_CARGO:RemoveCargosByName(RemoveCargoNames)
+local RemoveCargoNamesArray=(type(RemoveCargoNames)=="table")and RemoveCargoNames or{RemoveCargoNames}
+for RemoveCargoID,RemoveCargoName in pairs(RemoveCargoNamesArray)do
+self:Remove(RemoveCargoName.CargoName)
+end
+return self
+end
+function SET_CARGO:FindCargo(CargoName)
+local CargoFound=self.Set[CargoName]
+return CargoFound
+end
+function SET_CARGO:FilterCoalitions(Coalitions)
+if not self.Filter.Coalitions then
+self.Filter.Coalitions={}
+end
+if type(Coalitions)~="table"then
+Coalitions={Coalitions}
+end
+for CoalitionID,Coalition in pairs(Coalitions)do
+self.Filter.Coalitions[Coalition]=Coalition
+end
+return self
+end
+function SET_CARGO:FilterTypes(Types)
+if not self.Filter.Types then
+self.Filter.Types={}
+end
+if type(Types)~="table"then
+Types={Types}
+end
+for TypeID,Type in pairs(Types)do
+self.Filter.Types[Type]=Type
+end
+return self
+end
+function SET_CARGO:FilterCountries(Countries)
+if not self.Filter.Countries then
+self.Filter.Countries={}
+end
+if type(Countries)~="table"then
+Countries={Countries}
+end
+for CountryID,Country in pairs(Countries)do
+self.Filter.Countries[Country]=Country
+end
+return self
+end
+function SET_CARGO:FilterPrefixes(Prefixes)
+if not self.Filter.CargoPrefixes then
+self.Filter.CargoPrefixes={}
+end
+if type(Prefixes)~="table"then
+Prefixes={Prefixes}
+end
+for PrefixID,Prefix in pairs(Prefixes)do
+self.Filter.CargoPrefixes[Prefix]=Prefix
+end
+return self
+end
+function SET_CARGO:FilterStart()
+if _DATABASE then
+self:_FilterStart()
+end
+self:HandleEvent(EVENTS.NewCargo)
+self:HandleEvent(EVENTS.DeleteCargo)
+return self
+end
+function SET_CARGO:AddInDatabase(Event)
+self:F3({Event})
+return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
+end
+function SET_CARGO:FindInDatabase(Event)
+self:F3({Event})
+return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
+end
+function SET_CARGO:ForEachCargo(IteratorFunction,...)
+self:F2(arg)
+self:ForEach(IteratorFunction,arg,self.Set)
+return self
+end
+function SET_CARGO:FindNearestCargoFromPointVec2(PointVec2)
+self:F2(PointVec2)
+local NearestCargo=self:FindNearestObjectFromPointVec2(PointVec2)
+return NearestCargo
+end
+function SET_CARGO:IsIncludeObject(MCargo)
+self:F2(MCargo)
+local MCargoInclude=true
+if MCargo then
+local MCargoName=MCargo:GetName()
+if self.Filter.Coalitions then
+local MCargoCoalition=false
+for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
+local CargoCoalitionID=MCargo:GetCoalition()
+self:T3({"Coalition:",CargoCoalitionID,self.FilterMeta.Coalitions[CoalitionName],CoalitionName})
+if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==CargoCoalitionID then
+MCargoCoalition=true
+end
+end
+self:T({"Evaluated Coalition",MCargoCoalition})
+MCargoInclude=MCargoInclude and MCargoCoalition
+end
+if self.Filter.Types then
+local MCargoType=false
+for TypeID,TypeName in pairs(self.Filter.Types)do
+self:T3({"Type:",MCargo:GetType(),TypeName})
+if TypeName==MCargo:GetType()then
+MCargoType=true
+end
+end
+self:T({"Evaluated Type",MCargoType})
+MCargoInclude=MCargoInclude and MCargoType
+end
+if self.Filter.CargoPrefixes then
+local MCargoPrefix=false
+for CargoPrefixId,CargoPrefix in pairs(self.Filter.CargoPrefixes)do
+self:T3({"Prefix:",string.find(MCargo.Name,CargoPrefix,1),CargoPrefix})
+if string.find(MCargo.Name,CargoPrefix,1)then
+MCargoPrefix=true
+end
+end
+self:T({"Evaluated Prefix",MCargoPrefix})
+MCargoInclude=MCargoInclude and MCargoPrefix
+end
+end
+self:T2(MCargoInclude)
+return MCargoInclude
+end
+function SET_CARGO:OnEventNewCargo(EventData)
+if EventData.Cargo then
+if EventData.Cargo and self:IsIncludeObject(EventData.Cargo)then
+self:Add(EventData.Cargo.Name,EventData.Cargo)
+end
+end
+end
+function SET_CARGO:OnEventDeleteCargo(EventData)
+self:F3({EventData})
+if EventData.Cargo then
+local Cargo=_DATABASE:FindCargo(EventData.Cargo.Name)
+if Cargo and Cargo.Name then
+self:Remove(Cargo.Name)
+end
+end
+end
+do
+COORDINATE={
+ClassName="COORDINATE",
+}
+function COORDINATE:New(x,y,z)
+local self=BASE:Inherit(self,BASE:New())
+self.x=x
+self.y=y
+self.z=z
+return self
+end
+function COORDINATE:NewFromVec2(Vec2,LandHeightAdd)
+local LandHeight=land.getHeight(Vec2)
+LandHeightAdd=LandHeightAdd or 0
+LandHeight=LandHeight+LandHeightAdd
+local self=self:New(Vec2.x,LandHeight,Vec2.y)
+self:F2(self)
+return self
+end
+function COORDINATE:NewFromVec3(Vec3)
+local self=self:New(Vec3.x,Vec3.y,Vec3.z)
+self:F2(self)
+return self
+end
+function COORDINATE:GetVec3()
+return{x=self.x,y=self.y,z=self.z}
+end
+function COORDINATE:GetVec2()
+return{x=self.x,y=self.z}
+end
+function COORDINATE:DistanceFromVec2(Vec2Reference)
+self:F2(Vec2Reference)
+local Distance=((Vec2Reference.x-self.x)^2+(Vec2Reference.y-self.z)^2)^0.5
+self:T2(Distance)
+return Distance
+end
+function COORDINATE:Translate(Distance,Angle)
+local SX=self.x
+local SY=self.z
+local Radians=Angle/180*math.pi
+local TX=Distance*math.cos(Radians)+SX
+local TY=Distance*math.sin(Radians)+SY
+return COORDINATE:NewFromVec2({x=TX,y=TY})
+end
+function COORDINATE:GetRandomVec2InRadius(OuterRadius,InnerRadius)
+self:F2({OuterRadius,InnerRadius})
+local Theta=2*math.pi*math.random()
+local Radials=math.random()+math.random()
+if Radials>1 then
+Radials=2-Radials
+end
+local RadialMultiplier
+if InnerRadius and InnerRadius<=OuterRadius then
+RadialMultiplier=(OuterRadius-InnerRadius)*Radials+InnerRadius
+else
+RadialMultiplier=OuterRadius*Radials
+end
+local RandomVec2
+if OuterRadius>0 then
+RandomVec2={x=math.cos(Theta)*RadialMultiplier+self.x,y=math.sin(Theta)*RadialMultiplier+self.z}
+else
+RandomVec2={x=self.x,y=self.z}
+end
+return RandomVec2
+end
+function COORDINATE:GetRandomVec3InRadius(OuterRadius,InnerRadius)
+local RandomVec2=self:GetRandomVec2InRadius(OuterRadius,InnerRadius)
+local y=self.y+math.random(InnerRadius,OuterRadius)
+local RandomVec3={x=RandomVec2.x,y=y,z=RandomVec2.y}
+return RandomVec3
+end
+function COORDINATE:GetLandHeight()
+local Vec2={x=self.x,y=self.z}
+return land.getHeight(Vec2)
+end
+function COORDINATE:SetHeading(Heading)
+self.Heading=Heading
+end
+function COORDINATE:GetHeading()
+return self.Heading
+end
+function COORDINATE:SetVelocity(Velocity)
+self.Velocity=Velocity
+end
+function COORDINATE:GetVelocity()
+local Velocity=self.Velocity
+return Velocity or 0
+end
+function COORDINATE:GetMovingText(Settings)
+return self:GetVelocityText(Settings)..", "..self:GetHeadingText(Settings)
+end
+function COORDINATE:GetDirectionVec3(TargetCoordinate)
+return{x=TargetCoordinate.x-self.x,y=TargetCoordinate.y-self.y,z=TargetCoordinate.z-self.z}
+end
+function COORDINATE:GetNorthCorrectionRadians()
+local TargetVec3=self:GetVec3()
+local lat,lon=coord.LOtoLL(TargetVec3)
+local north_posit=coord.LLtoLO(lat+1,lon)
+return math.atan2(north_posit.z-TargetVec3.z,north_posit.x-TargetVec3.x)
+end
+function COORDINATE:GetAngleRadians(DirectionVec3)
+local DirectionRadians=math.atan2(DirectionVec3.z,DirectionVec3.x)
+if DirectionRadians<0 then
+DirectionRadians=DirectionRadians+2*math.pi
+end
+return DirectionRadians
+end
+function COORDINATE:GetAngleDegrees(DirectionVec3)
+local AngleRadians=self:GetAngleRadians(DirectionVec3)
+local Angle=UTILS.ToDegree(AngleRadians)
+return Angle
+end
+function COORDINATE:Get2DDistance(TargetCoordinate)
+local TargetVec3=TargetCoordinate:GetVec3()
+local SourceVec3=self:GetVec3()
+return((TargetVec3.x-SourceVec3.x)^2+(TargetVec3.z-SourceVec3.z)^2)^0.5
+end
+function COORDINATE:Get3DDistance(TargetCoordinate)
+local TargetVec3=TargetCoordinate:GetVec3()
+local SourceVec3=self:GetVec3()
+return((TargetVec3.x-SourceVec3.x)^2+(TargetVec3.y-SourceVec3.y)^2+(TargetVec3.z-SourceVec3.z)^2)^0.5
+end
+function COORDINATE:GetBearingText(AngleRadians,Precision,Settings)
+local Settings=Settings or _SETTINGS
+local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),Precision)
+local s=string.format('%03d°',AngleDegrees)
+return s
+end
+function COORDINATE:GetDistanceText(Distance,Settings)
+local Settings=Settings or _SETTINGS
+local DistanceText
+if Settings:IsMetric()then
+DistanceText=" for "..UTILS.Round(Distance/1000,2).." km"
+else
+DistanceText=" for "..UTILS.Round(UTILS.MetersToNM(Distance),2).." miles"
+end
+return DistanceText
+end
+function COORDINATE:GetAltitudeText(Settings)
+local Altitude=self.y
+local Settings=Settings or _SETTINGS
+if Altitude~=0 then
+if Settings:IsMetric()then
+return" at "..UTILS.Round(self.y,-3).." meters"
+else
+return" at "..UTILS.Round(UTILS.MetersToFeet(self.y),-3).." feet"
+end
+else
+return""
+end
+end
+function COORDINATE:GetVelocityText(Settings)
+local Velocity=self:GetVelocity()
+local Settings=Settings or _SETTINGS
+if Velocity then
+if Settings:IsMetric()then
+return string.format(" moving at %d km/h",UTILS.MpsToKmph(Velocity))
+else
+return string.format(" moving at %d mi/h",UTILS.MpsToKmph(Velocity)/1.852)
+end
+else
+return" stationary"
+end
+end
+function COORDINATE:GetHeadingText(Settings)
+local Heading=self:GetHeading()
+if Heading then
+return string.format(" bearing %3d°",Heading)
+else
+return" bearing unknown"
+end
+end
+function COORDINATE:GetBRText(AngleRadians,Distance,Settings)
+local Settings=Settings or _SETTINGS
+local BearingText=self:GetBearingText(AngleRadians,0,Settings)
+local DistanceText=self:GetDistanceText(Distance,Settings)
+local BRText=BearingText..DistanceText
+return BRText
+end
+function COORDINATE:GetBRAText(AngleRadians,Distance,Settings)
+local Settings=Settings or _SETTINGS
+local BearingText=self:GetBearingText(AngleRadians,0,Settings)
+local DistanceText=self:GetDistanceText(Distance,Settings)
+local AltitudeText=self:GetAltitudeText(Settings)
+local BRAText=BearingText..DistanceText..AltitudeText
+return BRAText
+end
+function COORDINATE:Translate(Distance,Angle)
+local SX=self.x
+local SZ=self.z
+local Radians=Angle/180*math.pi
+local TX=Distance*math.cos(Radians)+SX
+local TZ=Distance*math.sin(Radians)+SZ
+return COORDINATE:New(TX,self.y,TZ)
+end
+function COORDINATE:WaypointAir(AltType,Type,Action,Speed,SpeedLocked)
+self:F2({AltType,Type,Action,Speed,SpeedLocked})
+local RoutePoint={}
+RoutePoint.x=self.x
+RoutePoint.y=self.z
+RoutePoint.alt=self.y
+RoutePoint.alt_type=AltType or"RADIO"
+RoutePoint.type=Type or nil
+RoutePoint.action=Action or nil
+RoutePoint.speed=(Speed and Speed/3.6)or(500/3.6)
+RoutePoint.speed_locked=true
+RoutePoint.task={}
+RoutePoint.task.id="ComboTask"
+RoutePoint.task.params={}
+RoutePoint.task.params.tasks={}
+return RoutePoint
+end
+function COORDINATE:WaypointGround(Speed,Formation)
+self:F2({Formation,Speed})
+local RoutePoint={}
+RoutePoint.x=self.x
+RoutePoint.y=self.z
+RoutePoint.action=Formation or""
+RoutePoint.speed=(Speed or 999)/3.6
+RoutePoint.speed_locked=true
+RoutePoint.task={}
+RoutePoint.task.id="ComboTask"
+RoutePoint.task.params={}
+RoutePoint.task.params.tasks={}
+return RoutePoint
+end
+function COORDINATE:Explosion(ExplosionIntensity)
+self:F2({ExplosionIntensity})
+trigger.action.explosion(self:GetVec3(),ExplosionIntensity)
+end
+function COORDINATE:IlluminationBomb()
+self:F2()
+trigger.action.illuminationBomb(self:GetVec3())
+end
+function COORDINATE:Smoke(SmokeColor)
+self:F2({SmokeColor})
+trigger.action.smoke(self:GetVec3(),SmokeColor)
+end
+function COORDINATE:SmokeGreen()
+self:F2()
+self:Smoke(SMOKECOLOR.Green)
+end
+function COORDINATE:SmokeRed()
+self:F2()
+self:Smoke(SMOKECOLOR.Red)
+end
+function COORDINATE:SmokeWhite()
+self:F2()
+self:Smoke(SMOKECOLOR.White)
+end
+function COORDINATE:SmokeOrange()
+self:F2()
+self:Smoke(SMOKECOLOR.Orange)
+end
+function COORDINATE:SmokeBlue()
+self:F2()
+self:Smoke(SMOKECOLOR.Blue)
+end
+function COORDINATE:Flare(FlareColor,Azimuth)
+self:F2({FlareColor})
+trigger.action.signalFlare(self:GetVec3(),FlareColor,Azimuth and Azimuth or 0)
+end
+function COORDINATE:FlareWhite(Azimuth)
+self:F2(Azimuth)
+self:Flare(FLARECOLOR.White,Azimuth)
+end
+function COORDINATE:FlareYellow(Azimuth)
+self:F2(Azimuth)
+self:Flare(FLARECOLOR.Yellow,Azimuth)
+end
+function COORDINATE:FlareGreen(Azimuth)
+self:F2(Azimuth)
+self:Flare(FLARECOLOR.Green,Azimuth)
+end
+function COORDINATE:FlareRed(Azimuth)
+self:F2(Azimuth)
+self:Flare(FLARECOLOR.Red,Azimuth)
+end
+do
+function COORDINATE:MarkToAll(MarkText)
+local MarkID=UTILS.GetMarkID()
+trigger.action.markToAll(MarkID,MarkText,self:GetVec3())
+return MarkID
+end
+function COORDINATE:MarkToCoalition(MarkText,Coalition)
+local MarkID=UTILS.GetMarkID()
+trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),Coalition)
+return MarkID
+end
+function COORDINATE:MarkToCoalitionRed(MarkText)
+return self:MarkToCoalition(MarkText,coalition.side.RED)
+end
+function COORDINATE:MarkToCoalitionBlue(MarkText)
+return self:MarkToCoalition(MarkText,coalition.side.BLUE)
+end
+function COORDINATE:MarkToGroup(MarkText,MarkGroup)
+local MarkID=UTILS.GetMarkID()
+trigger.action.markToGroup(MarkID,MarkText,self:GetVec3(),MarkGroup:GetID())
+return MarkID
+end
+function COORDINATE:RemoveMark(MarkID)
+trigger.action.removeMark(MarkID)
+end
+end
+function COORDINATE:IsLOS(ToCoordinate)
+local FromVec3=self:GetVec3()
+FromVec3.y=FromVec3.y+2
+local ToVec3=ToCoordinate:GetVec3()
+ToVec3.y=ToVec3.y+2
+local IsLOS=land.isVisible(FromVec3,ToVec3)
+return IsLOS
+end
+function COORDINATE:IsInRadius(Coordinate,Radius)
+local InVec2=self:GetVec2()
+local Vec2=Coordinate:GetVec2()
+local InRadius=UTILS.IsInRadius(InVec2,Vec2,Radius)
+return InRadius
+end
+function COORDINATE:IsInSphere(Coordinate,Radius)
+local InVec3=self:GetVec3()
+local Vec3=Coordinate:GetVec3()
+local InSphere=UTILS.IsInSphere(InVec3,Vec3,Radius)
+return InSphere
+end
+function COORDINATE:ToStringBR(FromCoordinate,Settings)
+local DirectionVec3=FromCoordinate:GetDirectionVec3(self)
+local AngleRadians=self:GetAngleRadians(DirectionVec3)
+local Distance=self:Get2DDistance(FromCoordinate)
+return"BR, "..self:GetBRText(AngleRadians,Distance,Settings)
+end
+function COORDINATE:ToStringBRA(FromCoordinate,Settings)
+local DirectionVec3=FromCoordinate:GetDirectionVec3(self)
+local AngleRadians=self:GetAngleRadians(DirectionVec3)
+local Distance=FromCoordinate:Get2DDistance(self)
+local Altitude=self:GetAltitudeText()
+return"BRA, "..self:GetBRAText(AngleRadians,Distance,Settings)
+end
+function COORDINATE:ToStringBULLS(Coalition,Settings)
+local TargetCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(Coalition))
+local DirectionVec3=self:GetDirectionVec3(TargetCoordinate)
+local AngleRadians=self:GetAngleRadians(DirectionVec3)
+local Distance=self:Get2DDistance(TargetCoordinate)
+local Altitude=self:GetAltitudeText()
+return"BULLS, "..self:GetBRText(AngleRadians,Distance,Settings)
+end
+function COORDINATE:ToStringAspect(TargetCoordinate)
+local Heading=self.Heading
+local DirectionVec3=self:GetDirectionVec3(TargetCoordinate)
+local Angle=self:GetAngleDegrees(DirectionVec3)
+if Heading then
+local Aspect=Angle-Heading
+if Aspect>-135 and Aspect<=-45 then
+return"Flanking"
+end
+if Aspect>-45 and Aspect<=45 then
+return"Hot"
+end
+if Aspect>45 and Aspect<=135 then
+return"Flanking"
+end
+if Aspect>135 or Aspect<=-135 then
+return"Cold"
+end
+end
+return""
+end
+function COORDINATE:ToStringLLDMS(Settings)
+local LL_Accuracy=Settings and Settings.LL_Accuracy or _SETTINGS.LL_Accuracy
+local lat,lon=coord.LOtoLL(self:GetVec3())
+return"LL DMS, "..UTILS.tostringLL(lat,lon,LL_Accuracy,true)
+end
+function COORDINATE:ToStringLLDDM(Settings)
+local LL_Accuracy=Settings and Settings.LL_Accuracy or _SETTINGS.LL_Accuracy
+local lat,lon=coord.LOtoLL(self:GetVec3())
+return"LL DDM, "..UTILS.tostringLL(lat,lon,LL_Accuracy,false)
+end
+function COORDINATE:ToStringMGRS(Settings)
+local MGRS_Accuracy=Settings and Settings.MGRS_Accuracy or _SETTINGS.MGRS_Accuracy
+local lat,lon=coord.LOtoLL(self:GetVec3())
+local MGRS=coord.LLtoMGRS(lat,lon)
+return"MGRS, "..UTILS.tostringMGRS(MGRS,MGRS_Accuracy)
+end
+function COORDINATE:ToStringFromRP(ReferenceCoord,ReferenceName,Controllable,Settings)
+self:E({ReferenceCoord=ReferenceCoord,ReferenceName=ReferenceName})
+local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
+local IsAir=Controllable and Controllable:IsAirPlane()or false
+if IsAir then
+local DirectionVec3=ReferenceCoord:GetDirectionVec3(self)
+local AngleRadians=self:GetAngleRadians(DirectionVec3)
+local Distance=self:Get2DDistance(ReferenceCoord)
+return"Targets are the last seen "..self:GetBRText(AngleRadians,Distance,Settings).." from "..ReferenceName
+else
+local DirectionVec3=ReferenceCoord:GetDirectionVec3(self)
+local AngleRadians=self:GetAngleRadians(DirectionVec3)
+local Distance=self:Get2DDistance(ReferenceCoord)
+return"Target are located "..self:GetBRText(AngleRadians,Distance,Settings).." from "..ReferenceName
+end
+return nil
+end
+function COORDINATE:ToStringA2G(Controllable,Settings)
+self:F({Controllable=Controllable and Controllable:GetName()})
+local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
+if Settings:IsA2G_BR()then
+if Controllable then
+local Coordinate=Controllable:GetCoordinate()
+return Controllable and self:ToStringBR(Coordinate,Settings)or self:ToStringMGRS(Settings)
+else
+return self:ToStringMGRS(Settings)
+end
+end
+if Settings:IsA2G_LL_DMS()then
+return self:ToStringLLDMS(Settings)
+end
+if Settings:IsA2G_LL_DDM()then
+return self:ToStringLLDDM(Settings)
+end
+if Settings:IsA2G_MGRS()then
+return self:ToStringMGRS(Settings)
+end
+return nil
+end
+function COORDINATE:ToStringA2A(Controllable,Settings)
+self:F({Controllable=Controllable and Controllable:GetName()})
+local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
+if Settings:IsA2A_BRAA()then
+if Controllable then
+local Coordinate=Controllable:GetCoordinate()
+return self:ToStringBRA(Coordinate,Settings)
+else
+return self:ToStringMGRS(Settings)
+end
+end
+if Settings:IsA2A_BULLS()then
+local Coalition=Controllable:GetCoalition()
+return self:ToStringBULLS(Coalition,Settings)
+end
+if Settings:IsA2A_LL_DMS()then
+return self:ToStringLLDMS(Settings)
+end
+if Settings:IsA2A_LL_DDM()then
+return self:ToStringLLDDM(Settings)
+end
+if Settings:IsA2A_MGRS()then
+return self:ToStringMGRS(Settings)
+end
+return nil
+end
+function COORDINATE:ToString(Controllable,Settings,Task)
+self:F({Controllable=Controllable and Controllable:GetName()})
+local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
+local ModeA2A=false
+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
+else
+ModeA2A=false
+end
+end
+end
+else
+local IsAir=Controllable and Controllable:IsAirPlane()or false
+if IsAir then
+ModeA2A=true
+else
+ModeA2A=false
+end
+end
+if ModeA2A==true then
+return self:ToStringA2A(Controllable,Settings)
+else
+return self:ToStringA2G(Controllable,Settings)
+end
+return nil
+end
+end
+do
+POINT_VEC3={
+ClassName="POINT_VEC3",
+Metric=true,
+RoutePointAltType={
+BARO="BARO",
+},
+RoutePointType={
+TakeOffParking="TakeOffParking",
+TurningPoint="Turning Point",
+},
+RoutePointAction={
+FromParkingArea="From Parking Area",
+TurningPoint="Turning Point",
+},
+}
+function POINT_VEC3:New(x,y,z)
+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={
+ClassName="POINT_VEC2",
+}
+function POINT_VEC2:New(x,y,LandHeightAdd)
+local LandHeight=land.getHeight({["x"]=x,["y"]=y})
+LandHeightAdd=LandHeightAdd or 0
+LandHeight=LandHeight+LandHeightAdd
+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
+MESSAGE={
+ClassName="MESSAGE",
+MessageCategory=0,
+MessageID=0,
+}
+MESSAGE.Type={
+Update="Update",
+Information="Information",
+Briefing="Briefing Report",
+Overview="Overview Report",
+Detailed="Detailed Report"
+}
+function MESSAGE:New(MessageText,MessageDuration,MessageCategory)
+local self=BASE:Inherit(self,BASE:New())
+self:F({MessageText,MessageDuration,MessageCategory})
+self.MessageType=nil
+if MessageCategory and MessageCategory~=""then
+if MessageCategory:sub(-1)~="\n"then
+self.MessageCategory=MessageCategory..": "
+else
+self.MessageCategory=MessageCategory:sub(1,-2)..":\n"
+end
+else
+self.MessageCategory=""
+end
+self.MessageDuration=MessageDuration or 5
+self.MessageTime=timer.getTime()
+self.MessageText=MessageText:gsub("^\n","",1):gsub("\n$","",1)
+self.MessageSent=false
+self.MessageGroup=false
+self.MessageCoalition=false
+return self
+end
+function MESSAGE:NewType(MessageText,MessageType)
+local self=BASE:Inherit(self,BASE:New())
+self:F({MessageText})
+self.MessageType=MessageType
+self.MessageTime=timer.getTime()
+self.MessageText=MessageText:gsub("^\n","",1):gsub("\n$","",1)
+return self
+end
+function MESSAGE:ToClient(Client,Settings)
+self:F(Client)
+if Client and Client:GetClientGroupID()then
+if self.MessageType then
+local Settings=Settings or(Client and _DATABASE:GetPlayerSettings(Client:GetPlayerName()))or _SETTINGS
+self.MessageDuration=Settings:GetMessageTime(self.MessageType)
+self.MessageCategory=""
+end
+if self.MessageDuration~=0 then
+local ClientGroupID=Client:GetClientGroupID()
+self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration)
+trigger.action.outTextForGroup(ClientGroupID,self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration)
+end
+end
+return self
+end
+function MESSAGE:ToGroup(Group,Settings)
+self:F(Group.GroupName)
+if Group then
+if self.MessageType then
+local Settings=Settings or(Group and _DATABASE:GetPlayerSettings(Group:GetPlayerName()))or _SETTINGS
+self.MessageDuration=Settings:GetMessageTime(self.MessageType)
+self.MessageCategory=""
+end
+if self.MessageDuration~=0 then
+self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration)
+trigger.action.outTextForGroup(Group:GetID(),self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration)
+end
+end
+return self
+end
+function MESSAGE:ToBlue()
+self:F()
+self:ToCoalition(coalition.side.BLUE)
+return self
+end
+function MESSAGE:ToRed()
+self:F()
+self:ToCoalition(coalition.side.RED)
+return self
+end
+function MESSAGE:ToCoalition(CoalitionSide,Settings)
+self:F(CoalitionSide)
+if self.MessageType then
+local Settings=Settings or _SETTINGS
+self.MessageDuration=Settings:GetMessageTime(self.MessageType)
+self.MessageCategory=""
+end
+if CoalitionSide then
+if self.MessageDuration~=0 then
+self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration)
+trigger.action.outTextForCoalition(CoalitionSide,self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration)
+end
+end
+return self
+end
+function MESSAGE:ToCoalitionIf(CoalitionSide,Condition)
+self:F(CoalitionSide)
+if Condition and Condition==true then
+self:ToCoalition(CoalitionSide)
+end
+return self
+end
+function MESSAGE:ToAll()
+self:F()
+if self.MessageType then
+local Settings=Settings or _SETTINGS
+self.MessageDuration=Settings:GetMessageTime(self.MessageType)
+self.MessageCategory=""
+end
+if self.MessageDuration~=0 then
+self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration)
+trigger.action.outText(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration)
+end
+return self
+end
+function MESSAGE:ToAllIf(Condition)
+if Condition and Condition==true then
+self:ToAll()
+end
+return self
+end
+do
+FSM={
+ClassName="FSM",
+}
+function FSM:New(FsmT)
+self=BASE:Inherit(self,BASE:New())
+self.options=options or{}
+self.options.subs=self.options.subs or{}
+self.current=self.options.initial or'none'
+self.Events={}
+self.subs={}
+self.endstates={}
+self.Scores={}
+self._StartState="none"
+self._Transitions={}
+self._Processes={}
+self._EndStates={}
+self._Scores={}
+self._EventSchedules={}
+self.CallScheduler=SCHEDULER:New(self)
+return self
+end
+function FSM:SetStartState(State)
+self._StartState=State
+self.current=State
+end
+function FSM:GetStartState()
+return self._StartState or{}
+end
+function FSM:AddTransition(From,Event,To)
+local Transition={}
+Transition.From=From
+Transition.Event=Event
+Transition.To=To
+self:T2(Transition)
+self._Transitions[Transition]=Transition
+self:_eventmap(self.Events,Transition)
+end
+function FSM:GetTransitions()
+return self._Transitions or{}
+end
+function FSM:AddProcess(From,Event,Process,ReturnEvents)
+self:T({From,Event})
+local Sub={}
+Sub.From=From
+Sub.Event=Event
+Sub.fsm=Process
+Sub.StartEvent="Start"
+Sub.ReturnEvents=ReturnEvents
+self._Processes[Sub]=Sub
+self:_submap(self.subs,Sub,nil)
+self:AddTransition(From,Event,From)
+return Process
+end
+function FSM:GetProcesses()
+return self._Processes or{}
+end
+function FSM:GetProcess(From,Event)
+for ProcessID,Process in pairs(self:GetProcesses())do
+if Process.From==From and Process.Event==Event then
+return Process.fsm
+end
+end
+error("Sub-Process from state "..From.." with event "..Event.." not found!")
+end
+function FSM:AddEndState(State)
+self._EndStates[State]=State
+self.endstates[State]=State
+end
+function FSM:GetEndStates()
+return self._EndStates or{}
+end
+function FSM:AddScore(State,ScoreText,Score)
+self:F({State,ScoreText,Score})
+self._Scores[State]=self._Scores[State]or{}
+self._Scores[State].ScoreText=ScoreText
+self._Scores[State].Score=Score
+return self
+end
+function FSM:AddScoreProcess(From,Event,State,ScoreText,Score)
+self:F({From,Event,State,ScoreText,Score})
+local Process=self:GetProcess(From,Event)
+Process._Scores[State]=Process._Scores[State]or{}
+Process._Scores[State].ScoreText=ScoreText
+Process._Scores[State].Score=Score
+self:T(Process._Scores)
+return Process
+end
+function FSM:GetScores()
+return self._Scores or{}
+end
+function FSM:GetSubs()
+return self.options.subs
+end
+function FSM:LoadCallBacks(CallBackTable)
+for name,callback in pairs(CallBackTable or{})do
+self[name]=callback
+end
+end
+function FSM:_eventmap(Events,EventStructure)
+local Event=EventStructure.Event
+local __Event="__"..EventStructure.Event
+self[Event]=self[Event]or self:_create_transition(Event)
+self[__Event]=self[__Event]or self:_delayed_transition(Event)
+self:T2("Added methods: "..Event..", "..__Event)
+Events[Event]=self.Events[Event]or{map={}}
+self:_add_to_map(Events[Event].map,EventStructure)
+end
+function FSM:_submap(subs,sub,name)
+subs[sub.From]=subs[sub.From]or{}
+subs[sub.From][sub.Event]=subs[sub.From][sub.Event]or{}
+subs[sub.From][sub.Event][sub]={}
+subs[sub.From][sub.Event][sub].fsm=sub.fsm
+subs[sub.From][sub.Event][sub].StartEvent=sub.StartEvent
+subs[sub.From][sub.Event][sub].ReturnEvents=sub.ReturnEvents or{}
+subs[sub.From][sub.Event][sub].name=name
+subs[sub.From][sub.Event][sub].fsmparent=self
+end
+function FSM:_call_handler(handler,params,EventName)
+local ErrorHandler=function(errmsg)
+env.info("Error in SCHEDULER function:"..errmsg)
+if debug~=nil then
+env.info(debug.traceback())
+end
+return errmsg
+end
+if self[handler]then
+self:T2("Calling "..handler)
+self._EventSchedules[EventName]=nil
+local Result,Value=xpcall(function()return self[handler](self,unpack(params))end,ErrorHandler)
+return Value
+end
+end
+function FSM._handler(self,EventName,...)
+local Can,to=self:can(EventName)
+if to=="*"then
+to=self.current
+end
+if Can then
+local from=self.current
+local params={from,EventName,to,...}
+if self.Controllable then
+self:T("FSM Transition for "..self.Controllable.ControllableName.." :"..self.current.." --> "..EventName.." --> "..to)
+else
+self:T("FSM Transition:"..self.current.." --> "..EventName.." --> "..to)
+end
+if(self:_call_handler("onbefore"..EventName,params,EventName)==false)
+or(self:_call_handler("OnBefore"..EventName,params,EventName)==false)
+or(self:_call_handler("onleave"..from,params,EventName)==false)
+or(self:_call_handler("OnLeave"..from,params,EventName)==false)then
+self:T("Cancel Transition")
+return false
+end
+self.current=to
+local execute=true
+local subtable=self:_gosub(from,EventName)
+for _,sub in pairs(subtable)do
+self:T("calling sub start event: "..sub.StartEvent)
+sub.fsm.fsmparent=self
+sub.fsm.ReturnEvents=sub.ReturnEvents
+sub.fsm[sub.StartEvent](sub.fsm)
+execute=false
+end
+local fsmparent,Event=self:_isendstate(to)
+if fsmparent and Event then
+self:F2({"end state: ",fsmparent,Event})
+self:_call_handler("onenter"..to,params,EventName)
+self:_call_handler("OnEnter"..to,params,EventName)
+self:_call_handler("onafter"..EventName,params,EventName)
+self:_call_handler("OnAfter"..EventName,params,EventName)
+self:_call_handler("onstatechange",params,EventName)
+fsmparent[Event](fsmparent)
+execute=false
+end
+if execute then
+self:_call_handler("onenter"..to,params,EventName)
+self:_call_handler("OnEnter"..to,params,EventName)
+self:_call_handler("onafter"..EventName,params,EventName)
+self:_call_handler("OnAfter"..EventName,params,EventName)
+self:_call_handler("onstatechange",params,EventName)
+end
+else
+self:T("Cannot execute transition.")
+self:T({From=self.current,Event=EventName,To=to,Can=Can})
+end
+return nil
+end
+function FSM:_delayed_transition(EventName)
+return function(self,DelaySeconds,...)
+self:T2("Delayed Event: "..EventName)
+local CallID=0
+if DelaySeconds~=nil then
+if DelaySeconds<0 then
+DelaySeconds=math.abs(DelaySeconds)
+if not self._EventSchedules[EventName]then
+CallID=self.CallScheduler:Schedule(self,self._handler,{EventName,...},DelaySeconds or 1)
+self._EventSchedules[EventName]=CallID
+else
+end
+else
+CallID=self.CallScheduler:Schedule(self,self._handler,{EventName,...},DelaySeconds or 1)
+end
+else
+error("FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this.")
+end
+self:T2({CallID=CallID})
+end
+end
+function FSM:_create_transition(EventName)
+return function(self,...)return self._handler(self,EventName,...)end
+end
+function FSM:_gosub(ParentFrom,ParentEvent)
+local fsmtable={}
+if self.subs[ParentFrom]and self.subs[ParentFrom][ParentEvent]then
+self:T({ParentFrom,ParentEvent,self.subs[ParentFrom],self.subs[ParentFrom][ParentEvent]})
+return self.subs[ParentFrom][ParentEvent]
+else
+return{}
+end
+end
+function FSM:_isendstate(Current)
+local FSMParent=self.fsmparent
+if FSMParent and self.endstates[Current]then
+self:T({state=Current,endstates=self.endstates,endstate=self.endstates[Current]})
+FSMParent.current=Current
+local ParentFrom=FSMParent.current
+self:T(ParentFrom)
+self:T(self.ReturnEvents)
+local Event=self.ReturnEvents[Current]
+self:T({ParentFrom,Event,self.ReturnEvents})
+if Event then
+return FSMParent,Event
+else
+self:T({"Could not find parent event name for state ",ParentFrom})
+end
+end
+return nil
+end
+function FSM:_add_to_map(Map,Event)
+self:F3({Map,Event})
+if type(Event.From)=='string'then
+Map[Event.From]=Event.To
+else
+for _,From in ipairs(Event.From)do
+Map[From]=Event.To
+end
+end
+self:T3({Map,Event})
+end
+function FSM:GetState()
+return self.current
+end
+function FSM:Is(State)
+return self.current==State
+end
+function FSM:is(state)
+return self.current==state
+end
+function FSM:can(e)
+local Event=self.Events[e]
+self:F3({self.current,Event})
+local To=Event and Event.map[self.current]or Event.map['*']
+return To~=nil,To
+end
+function FSM:cannot(e)
+return not self:can(e)
+end
+end
+do
+FSM_CONTROLLABLE={
+ClassName="FSM_CONTROLLABLE",
+}
+function FSM_CONTROLLABLE:New(FSMT,Controllable)
+local self=BASE:Inherit(self,FSM:New(FSMT))
+if Controllable then
+self:SetControllable(Controllable)
+end
+self:AddTransition("*","Stop","Stopped")
+return self
+end
+function FSM_CONTROLLABLE:OnAfterStop(Controllable,From,Event,To)
+self.CallScheduler:Clear()
+end
+function FSM_CONTROLLABLE:SetControllable(FSMControllable)
+self.Controllable=FSMControllable
+end
+function FSM_CONTROLLABLE:GetControllable()
+return self.Controllable
+end
+function FSM_CONTROLLABLE:_call_handler(handler,params,EventName)
+local ErrorHandler=function(errmsg)
+env.info("Error in SCHEDULER function:"..errmsg)
+if debug~=nil then
+env.info(debug.traceback())
+end
+return errmsg
+end
+if self[handler]then
+self:F3("Calling "..handler)
+self._EventSchedules[EventName]=nil
+local Result,Value=xpcall(function()return self[handler](self,self.Controllable,unpack(params))end,ErrorHandler)
+return Value
+end
+end
+end
+do
+FSM_PROCESS={
+ClassName="FSM_PROCESS",
+}
+function FSM_PROCESS:New(Controllable,Task)
+local self=BASE:Inherit(self,FSM_CONTROLLABLE:New())
+self:Assign(Controllable,Task)
+return self
+end
+function FSM_PROCESS:Init(FsmProcess)
+self:T("No Initialisation")
+end
+function FSM_PROCESS:_call_handler(handler,params,EventName)
+local ErrorHandler=function(errmsg)
+env.info("Error in FSM_PROCESS call handler:"..errmsg)
+if debug~=nil then
+env.info(debug.traceback())
+end
+return errmsg
+end
+if self[handler]then
+self:F3("Calling "..handler)
+self._EventSchedules[EventName]=nil
+local Result,Value=xpcall(function()return self[handler](self,self.Controllable,self.Task,unpack(params))end,ErrorHandler)
+return Value
+end
+end
+function FSM_PROCESS:Copy(Controllable,Task)
+self:T({self:GetClassNameAndID()})
+local NewFsm=self:New(Controllable,Task)
+NewFsm:Assign(Controllable,Task)
+NewFsm:Init(self)
+NewFsm:SetStartState(self:GetStartState())
+for TransitionID,Transition in pairs(self:GetTransitions())do
+NewFsm:AddTransition(Transition.From,Transition.Event,Transition.To)
+end
+for ProcessID,Process in pairs(self:GetProcesses())do
+local FsmProcess=NewFsm:AddProcess(Process.From,Process.Event,Process.fsm:Copy(Controllable,Task),Process.ReturnEvents)
+end
+for EndStateID,EndState in pairs(self:GetEndStates())do
+self:T(EndState)
+NewFsm:AddEndState(EndState)
+end
+for ScoreID,Score in pairs(self:GetScores())do
+self:T(Score)
+NewFsm:AddScore(ScoreID,Score.ScoreText,Score.Score)
+end
+return NewFsm
+end
+function FSM_PROCESS:Remove()
+self:F({self:GetClassNameAndID()})
+self:F("Clearing Schedules")
+self.CallScheduler:Clear()
+for ProcessID,Process in pairs(self:GetProcesses())do
+if Process.fsm then
+Process.fsm:Remove()
+Process.fsm=nil
+end
+end
+return self
+end
+function FSM_PROCESS:SetTask(Task)
+self.Task=Task
+return self
+end
+function FSM_PROCESS:GetTask()
+return self.Task
+end
+function FSM_PROCESS:GetMission()
+return self.Task.Mission
+end
+function FSM_PROCESS:GetCommandCenter()
+return self:GetTask():GetMission():GetCommandCenter()
+end
+function FSM_PROCESS:Message(Message)
+self:F({Message=Message})
+local CC=self:GetCommandCenter()
+local TaskGroup=self.Controllable:GetGroup()
+local PlayerName=self.Controllable:GetPlayerName()
+PlayerName=PlayerName and" ("..PlayerName..")"or""
+local Callsign=self.Controllable:GetCallsign()
+local Prefix=Callsign and" @ "..Callsign..PlayerName or""
+Message=Prefix..": "..Message
+CC:MessageToGroup(Message,TaskGroup)
+end
+function FSM_PROCESS:Assign(ProcessUnit,Task)
+self:SetControllable(ProcessUnit)
+self:SetTask(Task)
+return self
+end
+function FSM_PROCESS:onenterAssigned(ProcessUnit)
+self:T("Assign")
+self.Task:Assign()
+end
+function FSM_PROCESS:onenterFailed(ProcessUnit)
+self:T("Failed")
+self.Task:Fail()
+end
+function FSM_PROCESS:onstatechange(ProcessUnit,Task,From,Event,To,Dummy)
+self:T({ProcessUnit:GetName(),From,Event,To,Dummy,self:IsTrace()})
+if self:IsTrace()then
+end
+self:T({Scores=self._Scores,To=To})
+if self._Scores[To]then
+local Task=self.Task
+local Scoring=Task:GetScoring()
+if Scoring then
+Scoring:_AddMissionTaskScore(Task.Mission,ProcessUnit,self._Scores[To].ScoreText,self._Scores[To].Score)
+end
+end
+end
+end
+do
+FSM_TASK={
+ClassName="FSM_TASK",
+}
+function FSM_TASK:New(FSMT)
+local self=BASE:Inherit(self,FSM_CONTROLLABLE:New(FSMT))
+self["onstatechange"]=self.OnStateChange
+return self
+end
+function FSM_TASK:_call_handler(handler,params,EventName)
+if self[handler]then
+self:T("Calling "..handler)
+self._EventSchedules[EventName]=nil
+return self[handler](self,unpack(params))
+end
+end
+end
+do
+FSM_SET={
+ClassName="FSM_SET",
+}
+function FSM_SET:New(FSMSet)
+self=BASE:Inherit(self,FSM:New())
+if FSMSet then
+self:Set(FSMSet)
+end
+return self
+end
+function FSM_SET:Set(FSMSet)
+self:F(FSMSet)
+self.Set=FSMSet
+end
+function FSM_SET:Get()
+return self.Controllable
+end
+function FSM_SET:_call_handler(handler,params,EventName)
+if self[handler]then
+self:T("Calling "..handler)
+self._EventSchedules[EventName]=nil
+return self[handler](self,self.Set,unpack(params))
+end
+end
+end
+RADIO={
+ClassName="RADIO",
+FileName="",
+Frequency=0,
+Modulation=radio.modulation.AM,
+Subtitle="",
+SubtitleDuration=0,
+Power=100,
+Loop=true,
+}
+function RADIO:New(Positionable)
+local self=BASE:Inherit(self,BASE:New())
+self.Loop=true
+self:F(Positionable)
+if Positionable:GetPointVec2()then
+self.Positionable=Positionable
+return self
+end
+self:E({"The passed positionable is invalid, no RADIO created",Positionable})
+return nil
+end
+function RADIO:SetFileName(FileName)
+self:F2(FileName)
+if type(FileName)=="string"then
+if FileName:find(".ogg")or FileName:find(".wav")then
+if not FileName:find("l10n/DEFAULT/")then
+FileName="l10n/DEFAULT/"..FileName
+end
+self.FileName=FileName
+return self
+end
+end
+self:E({"File name invalid. Maybe something wrong with the extension ?",self.FileName})
+return self
+end
+function RADIO:SetFrequency(Frequency)
+self:F2(Frequency)
+if type(Frequency)=="number"then
+if(Frequency>=30 and Frequency<88)or(Frequency>=108 and Frequency<152)or(Frequency>=225 and Frequency<400)then
+self.Frequency=Frequency*1000000
+if self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP"then
+self.Positionable:SetCommand({
+id="SetFrequency",
+params={
+frequency=self.Frequency,
+modulation=self.Modulation,
+}
+})
+end
+return self
+end
+end
+self:E({"Frequency is outside of DCS Frequency ranges (30-80, 108-152, 225-400). Frequency unchanged.",self.Frequency})
+return self
+end
+function RADIO:SetModulation(Modulation)
+self:F2(Modulation)
+if type(Modulation)=="number"then
+if Modulation==radio.modulation.AM or Modulation==radio.modulation.FM then
+self.Modulation=Modulation
+return self
+end
+end
+self:E({"Modulation is invalid. Use DCS's enum radio.modulation. Modulation unchanged.",self.Modulation})
+return self
+end
+function RADIO:SetPower(Power)
+self:F2(Power)
+if type(Power)=="number"then
+self.Power=math.floor(math.abs(Power))
+return self
+end
+self:E({"Power is invalid. Power unchanged.",self.Power})
+return self
+end
+function RADIO:SetLoop(Loop)
+self:F2(Loop)
+if type(Loop)=="boolean"then
+self.Loop=Loop
+return self
+end
+self:E({"Loop is invalid. Loop unchanged.",self.Loop})
+return self
+end
+function RADIO:SetSubtitle(Subtitle,SubtitleDuration)
+self:F2({Subtitle,SubtitleDuration})
+if type(Subtitle)=="string"then
+self.Subtitle=Subtitle
+else
+self.Subtitle=""
+self:E({"Subtitle is invalid. Subtitle reset.",self.Subtitle})
+end
+if type(SubtitleDuration)=="number"then
+if math.floor(math.abs(SubtitleDuration))==SubtitleDuration then
+self.SubtitleDuration=SubtitleDuration
+return self
+end
+end
+self.SubtitleDuration=0
+self:E({"SubtitleDuration is invalid. SubtitleDuration reset.",self.SubtitleDuration})
+end
+function RADIO:NewGenericTransmission(FileName,Frequency,Modulation,Power,Loop)
+self:F({FileName,Frequency,Modulation,Power})
+self:SetFileName(FileName)
+if Frequency then self:SetFrequency(Frequency)end
+if Modulation then self:SetModulation(Modulation)end
+if Power then self:SetPower(Power)end
+if Loop then self:SetLoop(Loop)end
+return self
+end
+function RADIO:NewUnitTransmission(FileName,Subtitle,SubtitleDuration,Frequency,Modulation,Loop)
+self:F({FileName,Subtitle,SubtitleDuration,Frequency,Modulation,Loop})
+self:SetFileName(FileName)
+if Subtitle then self:SetSubtitle(Subtitle)end
+if SubtitleDuration then self:SetSubtitleDuration(SubtitleDuration)end
+if Frequency then self:SetFrequency(Frequency)end
+if Modulation then self:SetModulation(Modulation)end
+if Loop then self:SetLoop(Loop)end
+return self
+end
+function RADIO:Broadcast()
+self:F()
+if self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP"then
+self:T2("Broadcasting from a UNIT or a GROUP")
+self.Positionable:SetCommand({
+id="TransmitMessage",
+params={
+file=self.FileName,
+duration=self.SubtitleDuration,
+subtitle=self.Subtitle,
+loop=self.Loop,
+}
+})
+else
+self:T2("Broadcasting from a POSITIONABLE")
+trigger.action.radioTransmission(self.FileName,self.Positionable:GetPositionVec3(),self.Modulation,self.Loop,self.Frequency,self.Power,tostring(self.ID))
+end
+return self
+end
+function RADIO:StopBroadcast()
+self:F()
+if self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP"then
+self.Positionable:SetCommand({
+id="StopTransmission",
+params={}
+})
+else
+trigger.action.stopRadioTransmission(tostring(self.ID))
+end
+return self
+end
+BEACON={
+ClassName="BEACON",
+}
+function BEACON:New(Positionable)
+local self=BASE:Inherit(self,BASE:New())
+self:F(Positionable)
+if Positionable:GetPointVec2()then
+self.Positionable=Positionable
+return self
+end
+self:E({"The passed positionable is invalid, no BEACON created",Positionable})
+return nil
+end
+function BEACON:_TACANToFrequency(TACANChannel,TACANMode)
+self:F3({TACANChannel,TACANMode})
+if type(TACANChannel)~="number"then
+if TACANMode~="X"and TACANMode~="Y"then
+return nil
+end
+end
+local A=1151
+local B=64
+if TACANChannel<64 then
+B=1
+end
+if TACANMode=='Y'then
+A=1025
+if TACANChannel<64 then
+A=1088
+end
+else
+if TACANChannel<64 then
+A=962
+end
+end
+return(A+TACANChannel-B)*1000000
+end
+function BEACON:AATACAN(TACANChannel,Message,Bearing,BeaconDuration)
+self:F({TACANChannel,Message,Bearing,BeaconDuration})
+local IsValid=true
+if not self.Positionable:IsAir()then
+self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting",self.Positionable})
+IsValid=false
+end
+local Frequency=self:_TACANToFrequency(TACANChannel,"Y")
+if not Frequency then
+self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
+IsValid=false
+end
+local System
+if Bearing then
+System=5
+else
+System=14
+end
+if IsValid then
+self:T2({"AA TACAN BEACON started !"})
+self.Positionable:SetCommand({
+id="ActivateBeacon",
+params={
+type=4,
+system=System,
+callsign=Message,
+frequency=Frequency,
+}
+})
+if BeaconDuration then
+SCHEDULER:New(nil,
+function()
+self:StopAATACAN()
+end,{},BeaconDuration)
+end
+end
+return self
+end
+function BEACON:StopAATACAN()
+self:F()
+if not self.Positionable then
+self:E({"Start the beacon first before stoping it !"})
+else
+self.Positionable:SetCommand({
+id='DeactivateBeacon',
+params={
+}
+})
+end
+end
+function BEACON:RadioBeacon(FileName,Frequency,Modulation,Power,BeaconDuration)
+self:F({FileName,Frequency,Modulation,Power,BeaconDuration})
+local IsValid=false
+if type(FileName)=="string"then
+if FileName:find(".ogg")or FileName:find(".wav")then
+if not FileName:find("l10n/DEFAULT/")then
+FileName="l10n/DEFAULT/"..FileName
+end
+IsValid=true
+end
+end
+if not IsValid then
+self:E({"File name invalid. Maybe something wrong with the extension ? ",FileName})
+end
+if type(Frequency)~="number"and IsValid then
+self:E({"Frequency invalid. ",Frequency})
+IsValid=false
+end
+Frequency=Frequency*1000000
+if Modulation~=radio.modulation.AM and Modulation~=radio.modulation.FM and IsValid then
+self:E({"Modulation is invalid. Use DCS's enum radio.modulation.",Modulation})
+IsValid=false
+end
+if type(Power)~="number"and IsValid then
+self:E({"Power is invalid. ",Power})
+IsValid=false
+end
+Power=math.floor(math.abs(Power))
+if IsValid then
+self:T2({"Activating Beacon on ",Frequency,Modulation})
+trigger.action.radioTransmission(FileName,self.Positionable:GetPositionVec3(),Modulation,true,Frequency,Power,tostring(self.ID))
+if BeaconDuration then
+SCHEDULER:New(nil,
+function()
+self:StopRadioBeacon()
+end,{},BeaconDuration)
+end
+end
+end
+function BEACON:StopRadioBeacon()
+self:F()
+trigger.action.stopRadioTransmission(tostring(self.ID))
+end
+SPAWNSTATIC={
+ClassName="SPAWNSTATIC",
+}
+function SPAWNSTATIC:NewFromStatic(SpawnTemplatePrefix,CountryID)
+local self=BASE:Inherit(self,BASE:New())
+self:F({SpawnTemplatePrefix})
+local TemplateStatic=StaticObject.getByName(SpawnTemplatePrefix)
+if TemplateStatic then
+self.SpawnTemplatePrefix=SpawnTemplatePrefix
+self.CountryID=CountryID
+self.SpawnIndex=0
+else
+error("SPAWNSTATIC:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'")
+end
+self:SetEventPriority(5)
+return self
+end
+function SPAWNSTATIC:NewFromType(SpawnTypeName,SpawnShapeName,SpawnCategory,CountryID)
+local self=BASE:Inherit(self,BASE:New())
+self:F({SpawnTypeName})
+self.SpawnTypeName=SpawnTypeName
+self.CountryID=CountryID
+self.SpawnIndex=0
+self:SetEventPriority(5)
+return self
+end
+function SPAWNSTATIC:SpawnFromPointVec2(PointVec2,Heading,NewName)
+self:F({PointVec2,Heading,NewName})
+local CountryName=_DATABASE.COUNTRY_NAME[self.CountryID]
+local StaticTemplate=_DATABASE:GetStaticUnitTemplate(self.SpawnTemplatePrefix)
+StaticTemplate.x=PointVec2:GetLat()
+StaticTemplate.y=PointVec2:GetLon()
+StaticTemplate.name=NewName or string.format("%s#%05d",self.SpawnTemplatePrefix,self.SpawnIndex)
+StaticTemplate.heading=(Heading/180)*math.pi
+StaticTemplate.CountryID=nil
+StaticTemplate.CoalitionID=nil
+StaticTemplate.CategoryID=nil
+local Static=coalition.addStaticObject(self.CountryID,StaticTemplate)
+self.SpawnIndex=self.SpawnIndex+1
+return Static
+end
+function SPAWNSTATIC:SpawnFromZone(Zone,Heading,NewName)
+self:F({Zone,Heading,NewName})
+local Static=self:SpawnFromPointVec2(Zone:GetPointVec2(),Heading,NewName)
+return Static
+end
+do
+GOAL={
+ClassName="GOAL",
+}
+GOAL.Players={}
+GOAL.TotalContributions=0
+function GOAL:New()
+local self=BASE:Inherit(self,FSM:New())
+self:F({})
+self:SetStartState("Pending")
+self:AddTransition("*","Achieved","Achieved")
+self:SetEventPriority(5)
+return self
+end
+function GOAL:AddPlayerContribution(PlayerName)
+self.Players[PlayerName]=self.Players[PlayerName]or 0
+self.Players[PlayerName]=self.Players[PlayerName]+1
+self.TotalContributions=self.TotalContributions+1
+end
+function GOAL:GetPlayerContribution(PlayerName)
+return self.Players[PlayerName]or 0
+end
+function GOAL:GetPlayerContributions()
+return self.Players or{}
+end
+function GOAL:GetTotalContributions()
+return self.TotalContributions or 0
+end
+function GOAL:IsAchieved()
+return self:Is("Achieved")
+end
+end
+CARGOS={}
+do
+CARGO={
+ClassName="CARGO",
+Type=nil,
+Name=nil,
+Weight=nil,
+CargoObject=nil,
+CargoCarrier=nil,
+Representable=false,
+Slingloadable=false,
+Moveable=false,
+Containable=false,
+}
+function CARGO:New(Type,Name,Weight)
+local self=BASE:Inherit(self,FSM:New())
+self:F({Type,Name,Weight})
+self:SetStartState("UnLoaded")
+self:AddTransition({"UnLoaded","Boarding"},"Board","Boarding")
+self:AddTransition("Boarding","Boarding","Boarding")
+self:AddTransition("Boarding","CancelBoarding","UnLoaded")
+self:AddTransition("Boarding","Load","Loaded")
+self:AddTransition("UnLoaded","Load","Loaded")
+self:AddTransition("Loaded","UnBoard","UnBoarding")
+self:AddTransition("UnBoarding","UnBoarding","UnBoarding")
+self:AddTransition("UnBoarding","UnLoad","UnLoaded")
+self:AddTransition("Loaded","UnLoad","UnLoaded")
+self:AddTransition("*","Damaged","Damaged")
+self:AddTransition("*","Destroyed","Destroyed")
+self:AddTransition("*","Respawn","UnLoaded")
+self.Type=Type
+self.Name=Name
+self.Weight=Weight
+self.CargoObject=nil
+self.CargoCarrier=nil
+self.Representable=false
+self.Slingloadable=false
+self.Moveable=false
+self.Containable=false
+self:SetDeployed(false)
+self.CargoScheduler=SCHEDULER:New()
+CARGOS[self.Name]=self
+return self
+end
+function CARGO:Destroy()
+if self.CargoObject then
+self.CargoObject:Destroy()
+end
+self:Destroyed()
+end
+function CARGO:GetName()
+return self.Name
+end
+function CARGO:GetObjectName()
+if self:IsLoaded()then
+return self.CargoCarrier:GetName()
+else
+return self.CargoObject:GetName()
+end
+end
+function CARGO:GetType()
+return self.Type
+end
+function CARGO:GetCoordinate()
+return self.CargoObject:GetCoordinate()
+end
+function CARGO:IsDestroyed()
+return self:Is("Destroyed")
+end
+function CARGO:IsLoaded()
+return self:Is("Loaded")
+end
+function CARGO:IsUnLoaded()
+return self:Is("UnLoaded")
+end
+function CARGO:IsAlive()
+if self:IsLoaded()then
+return self.CargoCarrier:IsAlive()
+else
+return self.CargoObject:IsAlive()
+end
+end
+function CARGO:SetDeployed(Deployed)
+self.Deployed=Deployed
+end
+function CARGO:IsDeployed()
+return self.Deployed
+end
+function CARGO:Spawn(PointVec2)
+self:F()
+end
+function CARGO:Flare(FlareColor)
+if self:IsUnLoaded()then
+trigger.action.signalFlare(self.CargoObject:GetVec3(),FlareColor,0)
+end
+end
+function CARGO:FlareWhite()
+self:Flare(trigger.flareColor.White)
+end
+function CARGO:FlareYellow()
+self:Flare(trigger.flareColor.Yellow)
+end
+function CARGO:FlareGreen()
+self:Flare(trigger.flareColor.Green)
+end
+function CARGO:FlareRed()
+self:Flare(trigger.flareColor.Red)
+end
+function CARGO:Smoke(SmokeColor,Range)
+self:F2()
+if self:IsUnLoaded()then
+if Range then
+trigger.action.smoke(self.CargoObject:GetRandomVec3(Range),SmokeColor)
+else
+trigger.action.smoke(self.CargoObject:GetVec3(),SmokeColor)
+end
+end
+end
+function CARGO:SmokeGreen()
+self:Smoke(trigger.smokeColor.Green,Range)
+end
+function CARGO:SmokeRed()
+self:Smoke(trigger.smokeColor.Red,Range)
+end
+function CARGO:SmokeWhite()
+self:Smoke(trigger.smokeColor.White,Range)
+end
+function CARGO:SmokeOrange()
+self:Smoke(trigger.smokeColor.Orange,Range)
+end
+function CARGO:SmokeBlue()
+self:Smoke(trigger.smokeColor.Blue,Range)
+end
+function CARGO:IsInZone(Zone)
+self:F({Zone})
+if self:IsLoaded()then
+return Zone:IsPointVec2InZone(self.CargoCarrier:GetPointVec2())
+else
+self:F({Size=self.CargoObject:GetSize(),Units=self.CargoObject:GetUnits()})
+if self.CargoObject:GetSize()~=0 then
+return Zone:IsPointVec2InZone(self.CargoObject:GetPointVec2())
+else
+return false
+end
+end
+return nil
+end
+function CARGO:IsNear(PointVec2,NearRadius)
+self:F({PointVec2,NearRadius})
+local Distance=PointVec2:DistanceFromPointVec2(self.CargoObject:GetPointVec2())
+self:T(Distance)
+if Distance<=NearRadius then
+return true
+else
+return false
+end
+end
+function CARGO:GetPointVec2()
+return self.CargoObject:GetPointVec2()
+end
+function CARGO:GetCoordinate()
+return self.CargoObject:GetCoordinate()
+end
+function CARGO:SetWeight(Weight)
+self.Weight=Weight
+return self
+end
+end
+do
+CARGO_REPRESENTABLE={
+ClassName="CARGO_REPRESENTABLE"
+}
+function CARGO_REPRESENTABLE:New(CargoObject,Type,Name,Weight,ReportRadius,NearRadius)
+local self=BASE:Inherit(self,CARGO:New(Type,Name,Weight,ReportRadius,NearRadius))
+self:F({Type,Name,Weight,ReportRadius,NearRadius})
+return self
+end
+function CARGO_REPRESENTABLE:RouteTo(ToPointVec2,Speed)
+self:F2(ToPointVec2)
+local Points={}
+local PointStartVec2=self.CargoObject:GetPointVec2()
+Points[#Points+1]=PointStartVec2:WaypointGround(Speed)
+Points[#Points+1]=ToPointVec2:WaypointGround(Speed)
+local TaskRoute=self.CargoObject:TaskRoute(Points)
+self.CargoObject:SetTask(TaskRoute,2)
+return self
+end
+end
+do
+CARGO_REPORTABLE={
+ClassName="CARGO_REPORTABLE"
+}
+function CARGO_REPORTABLE:New(CargoObject,Type,Name,Weight,ReportRadius)
+local self=BASE:Inherit(self,CARGO:New(Type,Name,Weight))
+self:F({Type,Name,Weight,ReportRadius})
+self.CargoSet=SET_CARGO:New()
+self.ReportRadius=ReportRadius or 1000
+self.CargoObject=CargoObject
+return self
+end
+function CARGO_REPORTABLE:IsInRadius(PointVec2)
+self:F({PointVec2})
+local Distance=0
+if self:IsLoaded()then
+Distance=PointVec2:DistanceFromPointVec2(self.CargoCarrier:GetPointVec2())
+else
+Distance=PointVec2:DistanceFromPointVec2(self.CargoObject:GetPointVec2())
+end
+self:T(Distance)
+if Distance<=self.ReportRadius then
+return true
+else
+return false
+end
+end
+function CARGO_REPORTABLE:MessageToGroup(Message,TaskGroup,Name)
+local Prefix=Name and"@ "..Name..": "or"@ "..TaskGroup:GetCallsign()..": "
+Message=Prefix..Message
+MESSAGE:New(Message,20,"Cargo: "..self:GetName()):ToGroup(TaskGroup)
+end
+function CARGO_REPORTABLE:GetBoardingRange()
+return self.ReportRadius
+end
+function CARGO_REPORTABLE:Respawn()
+self:F({"Respawning"})
+for CargoID,CargoData in pairs(self.CargoSet:GetSet())do
+local Cargo=CargoData
+Cargo:Destroy()
+Cargo:SetStartState("UnLoaded")
+end
+local CargoObject=self.CargoObject
+CargoObject:Destroy()
+local Template=CargoObject:GetTemplate()
+CargoObject:Respawn(Template)
+self:SetDeployed(false)
+local WeightGroup=0
+self:SetStartState("UnLoaded")
+end
+end
+do
+CARGO_UNIT={
+ClassName="CARGO_UNIT"
+}
+function CARGO_UNIT:New(CargoUnit,Type,Name,Weight,NearRadius)
+local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoUnit,Type,Name,Weight,NearRadius))
+self:F({Type,Name,Weight,NearRadius})
+self:T(CargoUnit)
+self.CargoObject=CargoUnit
+self:T(self.ClassName)
+self:SetEventPriority(5)
+return self
+end
+function CARGO_UNIT:Destroy()
+self:F({CargoName=self:GetName()})
+_EVENTDISPATCHER:CreateEventDeleteCargo(self)
+return self
+end
+function CARGO_UNIT:onenterUnBoarding(From,Event,To,ToPointVec2,NearRadius)
+self:F({From,Event,To,ToPointVec2,NearRadius})
+NearRadius=NearRadius or 25
+local Angle=180
+local Speed=60
+local DeployDistance=9
+local RouteDistance=60
+if From=="Loaded"then
+local CargoCarrier=self.CargoCarrier
+local CargoCarrierPointVec2=CargoCarrier:GetPointVec2()
+local CargoCarrierHeading=self.CargoCarrier:GetHeading()
+local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
+local CargoRoutePointVec2=CargoCarrierPointVec2:Translate(RouteDistance,CargoDeployHeading)
+ToPointVec2=ToPointVec2 or CargoRoutePointVec2
+local DirectionVec3=CargoCarrierPointVec2:GetDirectionVec3(ToPointVec2)
+local Angle=CargoCarrierPointVec2:GetAngleDegrees(DirectionVec3)
+local CargoDeployPointVec2=CargoCarrierPointVec2:Translate(DeployDistance,Angle)
+local FromPointVec2=CargoCarrierPointVec2
+if self.CargoObject then
+self.CargoObject:ReSpawn(CargoDeployPointVec2:GetVec3(),CargoDeployHeading)
+self:F({"CargoUnits:",self.CargoObject:GetGroup():GetName()})
+self.CargoCarrier=nil
+local Points={}
+Points[#Points+1]=CargoCarrierPointVec2:WaypointGround(Speed)
+Points[#Points+1]=ToPointVec2:WaypointGround(Speed)
+local TaskRoute=self.CargoObject:TaskRoute(Points)
+self.CargoObject:SetTask(TaskRoute,1)
+self:__UnBoarding(1,ToPointVec2,NearRadius)
+end
+end
+end
+function CARGO_UNIT:onleaveUnBoarding(From,Event,To,ToPointVec2,NearRadius)
+self:F({From,Event,To,ToPointVec2,NearRadius})
+NearRadius=NearRadius or 25
+local Angle=180
+local Speed=10
+local Distance=5
+if From=="UnBoarding"then
+if self:IsNear(ToPointVec2,NearRadius)then
+return true
+else
+self:__UnBoarding(1,ToPointVec2,NearRadius)
+end
+return false
+end
+end
+function CARGO_UNIT:onafterUnBoarding(From,Event,To,ToPointVec2,NearRadius)
+self:F({From,Event,To,ToPointVec2,NearRadius})
+NearRadius=NearRadius or 25
+self.CargoInAir=self.CargoObject:InAir()
+self:T(self.CargoInAir)
+if not self.CargoInAir then
+end
+self:__UnLoad(1,ToPointVec2,NearRadius)
+end
+function CARGO_UNIT:onenterUnLoaded(From,Event,To,ToPointVec2)
+self:F({ToPointVec2,From,Event,To})
+local Angle=180
+local Speed=10
+local Distance=5
+if From=="Loaded"then
+local StartPointVec2=self.CargoCarrier:GetPointVec2()
+local CargoCarrierHeading=self.CargoCarrier:GetHeading()
+local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
+local CargoDeployPointVec2=StartPointVec2:Translate(Distance,CargoDeployHeading)
+ToPointVec2=ToPointVec2 or POINT_VEC2:New(CargoDeployPointVec2:GetX(),CargoDeployPointVec2:GetY())
+if self.CargoObject then
+self.CargoObject:ReSpawn(ToPointVec2:GetVec3(),0)
+self.CargoCarrier=nil
+end
+end
+if self.OnUnLoadedCallBack then
+self.OnUnLoadedCallBack(self,unpack(self.OnUnLoadedParameters))
+self.OnUnLoadedCallBack=nil
+end
+end
+function CARGO_UNIT:onafterBoard(From,Event,To,CargoCarrier,NearRadius,...)
+self:F({From,Event,To,CargoCarrier,NearRadius})
+local NearRadius=NearRadius or 25
+self.CargoInAir=self.CargoObject:InAir()
+self:T(self.CargoInAir)
+if not self.CargoInAir then
+if self:IsNear(CargoCarrier:GetPointVec2(),NearRadius)then
+self:Load(CargoCarrier,NearRadius,...)
+else
+local Speed=90
+local Angle=180
+local Distance=5
+NearRadius=NearRadius or 25
+local CargoCarrierPointVec2=CargoCarrier:GetPointVec2()
+local CargoCarrierHeading=CargoCarrier:GetHeading()
+local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
+local CargoDeployPointVec2=CargoCarrierPointVec2:Translate(Distance,CargoDeployHeading)
+local Points={}
+local PointStartVec2=self.CargoObject:GetPointVec2()
+Points[#Points+1]=PointStartVec2:WaypointGround(Speed)
+Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed)
+local TaskRoute=self.CargoObject:TaskRoute(Points)
+self.CargoObject:SetTask(TaskRoute,2)
+self:__Boarding(-1,CargoCarrier,NearRadius)
+self.RunCount=0
+end
+end
+end
+function CARGO_UNIT:onafterBoarding(From,Event,To,CargoCarrier,NearRadius,...)
+self:F({From,Event,To,CargoCarrier.UnitName,NearRadius})
+if CargoCarrier and CargoCarrier:IsAlive()then
+if CargoCarrier:InAir()==false then
+if self:IsNear(CargoCarrier:GetPointVec2(),NearRadius)then
+self:__Load(1,CargoCarrier,...)
+else
+self:__Boarding(-1,CargoCarrier,NearRadius,...)
+self.RunCount=self.RunCount+1
+if self.RunCount>=20 then
+self.RunCount=0
+local Speed=90
+local Angle=180
+local Distance=5
+NearRadius=NearRadius or 25
+local CargoCarrierPointVec2=CargoCarrier:GetPointVec2()
+local CargoCarrierHeading=CargoCarrier:GetHeading()
+local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
+local CargoDeployPointVec2=CargoCarrierPointVec2:Translate(Distance,CargoDeployHeading)
+local Points={}
+local PointStartVec2=self.CargoObject:GetPointVec2()
+Points[#Points+1]=PointStartVec2:WaypointGround(Speed)
+Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed)
+local TaskRoute=self.CargoObject:TaskRoute(Points)
+self.CargoObject:SetTask(TaskRoute,0.2)
+end
+end
+else
+self.CargoObject:MessageToGroup("Cancelling Boarding... Get back on the ground!",5,CargoCarrier:GetGroup(),self:GetName())
+self:CancelBoarding(CargoCarrier,NearRadius,...)
+self.CargoObject:SetCommand(self.CargoObject:CommandStopRoute(true))
+end
+else
+self:E("Something is wrong")
+end
+end
+function CARGO_UNIT:onenterBoarding(From,Event,To,CargoCarrier,NearRadius,...)
+self:F({From,Event,To,CargoCarrier.UnitName,NearRadius})
+local Speed=90
+local Angle=180
+local Distance=5
+local NearRadius=NearRadius or 25
+if From=="UnLoaded"or From=="Boarding"then
+end
+end
+function CARGO_UNIT:onenterLoaded(From,Event,To,CargoCarrier)
+self:F({From,Event,To,CargoCarrier})
+self.CargoCarrier=CargoCarrier
+if self.CargoObject then
+self:T("Destroying")
+self.CargoObject:Destroy()
+end
+end
+end
+do
+CARGO_GROUP={
+ClassName="CARGO_GROUP",
+}
+function CARGO_GROUP:New(CargoGroup,Type,Name,ReportRadius)
+local self=BASE:Inherit(self,CARGO_REPORTABLE:New(CargoGroup,Type,Name,0,ReportRadius))
+self:F({Type,Name,ReportRadius})
+self.CargoObject=CargoGroup
+self:SetDeployed(false)
+self.CargoGroup=CargoGroup
+local WeightGroup=0
+for UnitID,UnitData in pairs(CargoGroup:GetUnits())do
+local Unit=UnitData
+local WeightUnit=Unit:GetDesc().massEmpty
+WeightGroup=WeightGroup+WeightUnit
+local CargoUnit=CARGO_UNIT:New(Unit,Type,Unit:GetName(),WeightUnit)
+self.CargoSet:Add(CargoUnit:GetName(),CargoUnit)
+end
+self:SetWeight(WeightGroup)
+self:T({"Weight Cargo",WeightGroup})
+_EVENTDISPATCHER:CreateEventNewCargo(self)
+self:HandleEvent(EVENTS.Dead,self.OnEventCargoDead)
+self:HandleEvent(EVENTS.Crash,self.OnEventCargoDead)
+self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventCargoDead)
+self:SetEventPriority(4)
+return self
+end
+function CARGO_GROUP:OnEventCargoDead(EventData)
+local Destroyed=false
+if self:IsDestroyed()or self:IsUnLoaded()then
+Destroyed=true
+for CargoID,CargoData in pairs(self.CargoSet:GetSet())do
+local Cargo=CargoData
+if Cargo:IsAlive()then
+Destroyed=false
+else
+Cargo:Destroyed()
+end
+end
+else
+local CarrierName=self.CargoCarrier:GetName()
+if CarrierName==EventData.IniDCSUnitName then
+MESSAGE:New("Cargo is lost from carrier "..CarrierName,15):ToAll()
+Destroyed=true
+self.CargoCarrier:ClearCargo()
+end
+end
+if Destroyed then
+self:Destroyed()
+self:E({"Cargo group destroyed"})
+end
+end
+function CARGO_GROUP:onenterBoarding(From,Event,To,CargoCarrier,NearRadius,...)
+self:F({CargoCarrier.UnitName,From,Event,To})
+local NearRadius=NearRadius or 25
+if From=="UnLoaded"then
+self.CargoSet:ForEach(
+function(Cargo,...)
+Cargo:__Board(1,CargoCarrier,NearRadius,...)
+end,...
+)
+self:__Boarding(1,CargoCarrier,NearRadius,...)
+end
+end
+function CARGO_GROUP:onenterLoaded(From,Event,To,CargoCarrier,...)
+self:F({From,Event,To,CargoCarrier,...})
+if From=="UnLoaded"then
+for CargoID,Cargo in pairs(self.CargoSet:GetSet())do
+Cargo:Load(CargoCarrier)
+end
+end
+self.CargoCarrier=CargoCarrier
+end
+function CARGO_GROUP:onafterBoarding(From,Event,To,CargoCarrier,NearRadius,...)
+self:F({CargoCarrier.UnitName,From,Event,To})
+local NearRadius=NearRadius or 25
+local Boarded=true
+local Cancelled=false
+local Dead=true
+self.CargoSet:Flush()
+for CargoID,Cargo in pairs(self.CargoSet:GetSet())do
+self:T({Cargo:GetName(),Cargo.current})
+if not Cargo:is("Loaded")then
+Boarded=false
+end
+if Cargo:is("UnLoaded")then
+Cancelled=true
+end
+if not Cargo:is("Destroyed")then
+Dead=false
+end
+end
+if not Dead then
+if not Cancelled then
+if not Boarded then
+self:__Boarding(1,CargoCarrier,NearRadius,...)
+else
+self:__Load(1,CargoCarrier,...)
+end
+else
+self:__CancelBoarding(1,CargoCarrier,NearRadius,...)
+end
+else
+self:__Destroyed(1,CargoCarrier,NearRadius,...)
+end
+end
+function CARGO_GROUP:GetCount()
+return self.CargoSet:Count()
+end
+function CARGO_GROUP:onenterUnBoarding(From,Event,To,ToPointVec2,NearRadius,...)
+self:F({From,Event,To,ToPointVec2,NearRadius})
+NearRadius=NearRadius or 25
+local Timer=1
+if From=="Loaded"then
+if self.CargoObject then
+self.CargoObject:Destroy()
+end
+self.CargoSet:ForEach(
+function(Cargo,NearRadius)
+Cargo:__UnBoard(Timer,ToPointVec2,NearRadius)
+Timer=Timer+10
+end,{NearRadius}
+)
+self:__UnBoarding(1,ToPointVec2,NearRadius,...)
+end
+end
+function CARGO_GROUP:onleaveUnBoarding(From,Event,To,ToPointVec2,NearRadius,...)
+self:F({From,Event,To,ToPointVec2,NearRadius})
+local Angle=180
+local Speed=10
+local Distance=5
+if From=="UnBoarding"then
+local UnBoarded=true
+for CargoID,Cargo in pairs(self.CargoSet:GetSet())do
+self:T(Cargo.current)
+if not Cargo:is("UnLoaded")then
+UnBoarded=false
+end
+end
+if UnBoarded then
+return true
+else
+self:__UnBoarding(1,ToPointVec2,NearRadius,...)
+end
+return false
+end
+end
+function CARGO_GROUP:onafterUnBoarding(From,Event,To,ToPointVec2,NearRadius,...)
+self:F({From,Event,To,ToPointVec2,NearRadius})
+self:__UnLoad(1,ToPointVec2,...)
+end
+function CARGO_GROUP:onenterUnLoaded(From,Event,To,ToPointVec2,...)
+self:F({From,Event,To,ToPointVec2})
+if From=="Loaded"then
+self.CargoSet:ForEach(
+function(Cargo)
+Cargo:UnLoad(ToPointVec2)
+end
+)
+end
+end
+function CARGO_GROUP:RespawnOnDestroyed(RespawnDestroyed)
+self:F({"In function RespawnOnDestroyed"})
+if RespawnDestroyed then
+self.onenterDestroyed=function(self)
+self:F("IN FUNCTION")
+self:Respawn()
+end
+else
+self.onenterDestroyed=nil
+end
+end
+end
+do
+CARGO_PACKAGE={
+ClassName="CARGO_PACKAGE"
+}
+function CARGO_PACKAGE:New(CargoCarrier,Type,Name,Weight,ReportRadius,NearRadius)
+local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoCarrier,Type,Name,Weight,ReportRadius,NearRadius))
+self:F({Type,Name,Weight,ReportRadius,NearRadius})
+self:T(CargoCarrier)
+self.CargoCarrier=CargoCarrier
+return self
+end
+function CARGO_PACKAGE:onafterOnBoard(From,Event,To,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle)
+self:F()
+self.CargoInAir=self.CargoCarrier:InAir()
+self:T(self.CargoInAir)
+if not self.CargoInAir then
+local Points={}
+local StartPointVec2=self.CargoCarrier:GetPointVec2()
+local CargoCarrierHeading=CargoCarrier:GetHeading()
+local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
+self:T({CargoCarrierHeading,CargoDeployHeading})
+local CargoDeployPointVec2=CargoCarrier:GetPointVec2():Translate(BoardDistance,CargoDeployHeading)
+Points[#Points+1]=StartPointVec2:WaypointGround(Speed)
+Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed)
+local TaskRoute=self.CargoCarrier:TaskRoute(Points)
+self.CargoCarrier:SetTask(TaskRoute,1)
+end
+self:Boarded(CargoCarrier,Speed,BoardDistance,LoadDistance,Angle)
+end
+function CARGO_PACKAGE:IsNear(CargoCarrier)
+self:F()
+local CargoCarrierPoint=CargoCarrier:GetPointVec2()
+local Distance=CargoCarrierPoint:DistanceFromPointVec2(self.CargoCarrier:GetPointVec2())
+self:T(Distance)
+if Distance<=self.NearRadius then
+return true
+else
+return false
+end
+end
+function CARGO_PACKAGE:onafterOnBoarded(From,Event,To,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle)
+self:F()
+if self:IsNear(CargoCarrier)then
+self:__Load(1,CargoCarrier,Speed,LoadDistance,Angle)
+else
+self:__Boarded(1,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle)
+end
+end
+function CARGO_PACKAGE:onafterUnBoard(From,Event,To,CargoCarrier,Speed,UnLoadDistance,UnBoardDistance,Radius,Angle)
+self:F()
+self.CargoInAir=self.CargoCarrier:InAir()
+self:T(self.CargoInAir)
+if not self.CargoInAir then
+self:_Next(self.FsmP.UnLoad,UnLoadDistance,Angle)
+local Points={}
+local StartPointVec2=CargoCarrier:GetPointVec2()
+local CargoCarrierHeading=self.CargoCarrier:GetHeading()
+local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
+self:T({CargoCarrierHeading,CargoDeployHeading})
+local CargoDeployPointVec2=StartPointVec2:Translate(UnBoardDistance,CargoDeployHeading)
+Points[#Points+1]=StartPointVec2:WaypointGround(Speed)
+Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed)
+local TaskRoute=CargoCarrier:TaskRoute(Points)
+CargoCarrier:SetTask(TaskRoute,1)
+end
+self:__UnBoarded(1,CargoCarrier,Speed)
+end
+function CARGO_PACKAGE:onafterUnBoarded(From,Event,To,CargoCarrier,Speed)
+self:F()
+if self:IsNear(CargoCarrier)then
+self:__UnLoad(1,CargoCarrier,Speed)
+else
+self:__UnBoarded(1,CargoCarrier,Speed)
+end
+end
+function CARGO_PACKAGE:onafterLoad(From,Event,To,CargoCarrier,Speed,LoadDistance,Angle)
+self:F()
+self.CargoCarrier=CargoCarrier
+local StartPointVec2=self.CargoCarrier:GetPointVec2()
+local CargoCarrierHeading=self.CargoCarrier:GetHeading()
+local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
+local CargoDeployPointVec2=StartPointVec2:Translate(LoadDistance,CargoDeployHeading)
+local Points={}
+Points[#Points+1]=StartPointVec2:WaypointGround(Speed)
+Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed)
+local TaskRoute=self.CargoCarrier:TaskRoute(Points)
+self.CargoCarrier:SetTask(TaskRoute,1)
+end
+function CARGO_PACKAGE:onafterUnLoad(From,Event,To,CargoCarrier,Speed,Distance,Angle)
+self:F()
+local StartPointVec2=self.CargoCarrier:GetPointVec2()
+local CargoCarrierHeading=self.CargoCarrier:GetHeading()
+local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
+local CargoDeployPointVec2=StartPointVec2:Translate(Distance,CargoDeployHeading)
+self.CargoCarrier=CargoCarrier
+local Points={}
+Points[#Points+1]=StartPointVec2:WaypointGround(Speed)
+Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed)
+local TaskRoute=self.CargoCarrier:TaskRoute(Points)
+self.CargoCarrier:SetTask(TaskRoute,1)
+end
+end
+do
+SPOT={
+ClassName="SPOT",
+}
+function SPOT:New(Recce)
+local self=BASE:Inherit(self,FSM:New())
+self:F({})
+self:SetStartState("Off")
+self:AddTransition("Off","LaseOn","On")
+self:AddTransition("On","Lasing","On")
+self:AddTransition({"On","Destroyed"},"LaseOff","Off")
+self:AddTransition("*","Destroyed","Destroyed")
+self.Recce=Recce
+self.LaseScheduler=SCHEDULER:New(self)
+self:SetEventPriority(5)
+self.Lasing=false
+return self
+end
+function SPOT:onafterLaseOn(From,Event,To,Target,LaserCode,Duration)
+self:E({"LaseOn",Target,LaserCode,Duration})
+local function StopLase(self)
+self:LaseOff()
+end
+self.Target=Target
+self.LaserCode=LaserCode
+self.Lasing=true
+local RecceDcsUnit=self.Recce:GetDCSObject()
+self.SpotIR=Spot.createInfraRed(RecceDcsUnit,{x=0,y=2,z=0},Target:GetPointVec3():AddY(1):GetVec3())
+self.SpotLaser=Spot.createLaser(RecceDcsUnit,{x=0,y=2,z=0},Target:GetPointVec3():AddY(1):GetVec3(),LaserCode)
+if Duration then
+self.ScheduleID=self.LaseScheduler:Schedule(self,StopLase,{self},Duration)
+end
+self:HandleEvent(EVENTS.Dead)
+self:__Lasing(-1)
+end
+function SPOT:OnEventDead(EventData)
+self:E({Dead=EventData.IniDCSUnitName,Target=self.Target})
+if self.Target then
+if EventData.IniDCSUnitName==self.Target:GetName()then
+self:E({"Target dead ",self.Target:GetName()})
+self:Destroyed()
+self:LaseOff()
+end
+end
+end
+function SPOT:onafterLasing(From,Event,To)
+if self.Target:IsAlive()then
+self.SpotIR:setPoint(self.Target:GetPointVec3():AddY(1):AddY(math.random(-100,100)/100):AddX(math.random(-100,100)/100):GetVec3())
+self.SpotLaser:setPoint(self.Target:GetPointVec3():AddY(1):GetVec3())
+self:__Lasing(-0.2)
+else
+self:E({"Target is not alive",self.Target:IsAlive()})
+end
+end
+function SPOT:onafterLaseOff(From,Event,To)
+self:E({"Stopped lasing for ",self.Target:GetName(),SpotIR=self.SportIR,SpotLaser=self.SpotLaser})
+self.Lasing=false
+self.SpotIR:destroy()
+self.SpotLaser:destroy()
+self.SpotIR=nil
+self.SpotLaser=nil
+if self.ScheduleID then
+self.LaseScheduler:Stop(self.ScheduleID)
+end
+self.ScheduleID=nil
+self.Target=nil
+return self
+end
+function SPOT:IsLasing()
+return self.Lasing
+end
+end
+OBJECT={
+ClassName="OBJECT",
+ObjectName="",
+}
+function OBJECT:New(ObjectName,Test)
+local self=BASE:Inherit(self,BASE:New())
+self:F2(ObjectName)
+self.ObjectName=ObjectName
+return self
+end
+function OBJECT:GetID()
+self:F2(self.ObjectName)
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+local ObjectID=DCSObject:getID()
+return ObjectID
+end
+return nil
+end
+function OBJECT:Destroy()
+self:F2(self.ObjectName)
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+DCSObject:destroy()
+end
+return nil
+end
+IDENTIFIABLE={
+ClassName="IDENTIFIABLE",
+IdentifiableName="",
+}
+local _CategoryName={
+[Unit.Category.AIRPLANE]="Airplane",
+[Unit.Category.HELICOPTER]="Helicoper",
+[Unit.Category.GROUND_UNIT]="Ground Identifiable",
+[Unit.Category.SHIP]="Ship",
+[Unit.Category.STRUCTURE]="Structure",
+}
+function IDENTIFIABLE:New(IdentifiableName)
+local self=BASE:Inherit(self,OBJECT:New(IdentifiableName))
+self:F2(IdentifiableName)
+self.IdentifiableName=IdentifiableName
+return self
+end
+function IDENTIFIABLE:IsAlive()
+self:F3(self.IdentifiableName)
+local DCSIdentifiable=self:GetDCSObject()
+if DCSIdentifiable then
+local IdentifiableIsAlive=DCSIdentifiable:isExist()
+return IdentifiableIsAlive
+end
+return false
+end
+function IDENTIFIABLE:GetName()
+self:F2(self.IdentifiableName)
+local IdentifiableName=self.IdentifiableName
+return IdentifiableName
+end
+function IDENTIFIABLE:GetTypeName()
+self:F2(self.IdentifiableName)
+local DCSIdentifiable=self:GetDCSObject()
+if DCSIdentifiable then
+local IdentifiableTypeName=DCSIdentifiable:getTypeName()
+self:T3(IdentifiableTypeName)
+return IdentifiableTypeName
+end
+self:E(self.ClassName.." "..self.IdentifiableName.." not found!")
+return nil
+end
+function IDENTIFIABLE:GetCategory()
+self:F2(self.ObjectName)
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+local ObjectCategory=DCSObject:getCategory()
+self:T3(ObjectCategory)
+return ObjectCategory
+end
+return nil
+end
+function IDENTIFIABLE:GetCategoryName()
+local DCSIdentifiable=self:GetDCSObject()
+if DCSIdentifiable then
+local IdentifiableCategoryName=_CategoryName[self:GetDesc().category]
+return IdentifiableCategoryName
+end
+self:E(self.ClassName.." "..self.IdentifiableName.." not found!")
+return nil
+end
+function IDENTIFIABLE:GetCoalition()
+self:F2(self.IdentifiableName)
+local DCSIdentifiable=self:GetDCSObject()
+if DCSIdentifiable then
+local IdentifiableCoalition=DCSIdentifiable:getCoalition()
+self:T3(IdentifiableCoalition)
+return IdentifiableCoalition
+end
+self:E(self.ClassName.." "..self.IdentifiableName.." not found!")
+return nil
+end
+function IDENTIFIABLE:GetCoalitionName()
+self:F2(self.IdentifiableName)
+local DCSIdentifiable=self:GetDCSObject()
+if DCSIdentifiable then
+local IdentifiableCoalition=DCSIdentifiable:getCoalition()
+self:T3(IdentifiableCoalition)
+if IdentifiableCoalition==coalition.side.BLUE then
+return"Blue"
+end
+if IdentifiableCoalition==coalition.side.RED then
+return"Red"
+end
+if IdentifiableCoalition==coalition.side.NEUTRAL then
+return"Neutral"
+end
+end
+self:E(self.ClassName.." "..self.IdentifiableName.." not found!")
+return nil
+end
+function IDENTIFIABLE:GetCountry()
+self:F2(self.IdentifiableName)
+local DCSIdentifiable=self:GetDCSObject()
+if DCSIdentifiable then
+local IdentifiableCountry=DCSIdentifiable:getCountry()
+self:T3(IdentifiableCountry)
+return IdentifiableCountry
+end
+self:E(self.ClassName.." "..self.IdentifiableName.." not found!")
+return nil
+end
+function IDENTIFIABLE:GetDesc()
+self:F2(self.IdentifiableName)
+local DCSIdentifiable=self:GetDCSObject()
+if DCSIdentifiable then
+local IdentifiableDesc=DCSIdentifiable:getDesc()
+self:T2(IdentifiableDesc)
+return IdentifiableDesc
+end
+self:E(self.ClassName.." "..self.IdentifiableName.." not found!")
+return nil
+end
+function IDENTIFIABLE:GetCallsign()
+return''
+end
+function IDENTIFIABLE:GetThreatLevel()
+return 0,"Scenery"
+end
+POSITIONABLE={
+ClassName="POSITIONABLE",
+PositionableName="",
+}
+POSITIONABLE.__={}
+POSITIONABLE.__.Cargo={}
+function POSITIONABLE:New(PositionableName)
+local self=BASE:Inherit(self,IDENTIFIABLE:New(PositionableName))
+self.PositionableName=PositionableName
+return self
+end
+function POSITIONABLE:GetPositionVec3()
+self:F2(self.PositionableName)
+local DCSPositionable=self:GetDCSObject()
+if DCSPositionable then
+local PositionablePosition=DCSPositionable:getPosition().p
+self:T3(PositionablePosition)
+return PositionablePosition
+end
+return nil
+end
+function POSITIONABLE:GetVec2()
+self:F2(self.PositionableName)
+local DCSPositionable=self:GetDCSObject()
+if DCSPositionable then
+local PositionableVec3=DCSPositionable:getPosition().p
+local PositionableVec2={}
+PositionableVec2.x=PositionableVec3.x
+PositionableVec2.y=PositionableVec3.z
+self:T2(PositionableVec2)
+return PositionableVec2
+end
+return nil
+end
+function POSITIONABLE:GetPointVec2()
+self:F2(self.PositionableName)
+local DCSPositionable=self:GetDCSObject()
+if DCSPositionable then
+local PositionableVec3=DCSPositionable:getPosition().p
+local PositionablePointVec2=POINT_VEC2:NewFromVec3(PositionableVec3)
+self:T2(PositionablePointVec2)
+return PositionablePointVec2
+end
+return nil
+end
+function POSITIONABLE:GetPointVec3()
+self:F2(self.PositionableName)
+local DCSPositionable=self:GetDCSObject()
+if DCSPositionable then
+local PositionableVec3=self:GetPositionVec3()
+local PositionablePointVec3=POINT_VEC3:NewFromVec3(PositionableVec3)
+self:T2(PositionablePointVec3)
+return PositionablePointVec3
+end
+return nil
+end
+function POSITIONABLE:GetCoordinate()
+self:F2(self.PositionableName)
+local DCSPositionable=self:GetDCSObject()
+if DCSPositionable then
+local PositionableVec3=self:GetPositionVec3()
+local PositionableCoordinate=COORDINATE:NewFromVec3(PositionableVec3)
+PositionableCoordinate:SetHeading(self:GetHeading())
+PositionableCoordinate:SetVelocity(self:GetVelocityMPS())
+self:T2(PositionableCoordinate)
+return PositionableCoordinate
+end
+return nil
+end
+function POSITIONABLE:GetRandomVec3(Radius)
+self:F2(self.PositionableName)
+local DCSPositionable=self:GetDCSObject()
+if DCSPositionable then
+local PositionablePointVec3=DCSPositionable:getPosition().p
+if Radius then
+local PositionableRandomVec3={}
+local angle=math.random()*math.pi*2;
+PositionableRandomVec3.x=PositionablePointVec3.x+math.cos(angle)*math.random()*Radius;
+PositionableRandomVec3.y=PositionablePointVec3.y
+PositionableRandomVec3.z=PositionablePointVec3.z+math.sin(angle)*math.random()*Radius;
+self:T3(PositionableRandomVec3)
+return PositionableRandomVec3
+else
+self:E("Radius is nil, returning the PointVec3 of the POSITIONABLE",PositionablePointVec3)
+return PositionablePointVec3
+end
+end
+return nil
+end
+function POSITIONABLE:GetVec3()
+self:F2(self.PositionableName)
+local DCSPositionable=self:GetDCSObject()
+if DCSPositionable then
+local PositionableVec3=DCSPositionable:getPosition().p
+self:T3(PositionableVec3)
+return PositionableVec3
+end
+return nil
+end
+function POSITIONABLE:GetBoundingBox()
+self:F2()
+local DCSPositionable=self:GetDCSObject()
+if DCSPositionable then
+local PositionableDesc=DCSPositionable:getDesc()
+if PositionableDesc then
+local PositionableBox=PositionableDesc.box
+return PositionableBox
+end
+end
+return nil
+end
+function POSITIONABLE:GetAltitude()
+self:F2()
+local DCSPositionable=self:GetDCSObject()
+if DCSPositionable then
+local PositionablePointVec3=DCSPositionable:getPoint()
+return PositionablePointVec3.y
+end
+return nil
+end
+function POSITIONABLE:IsAboveRunway()
+self:F2(self.PositionableName)
+local DCSPositionable=self:GetDCSObject()
+if DCSPositionable then
+local Vec2=self:GetVec2()
+local SurfaceType=land.getSurfaceType(Vec2)
+local IsAboveRunway=SurfaceType==land.SurfaceType.RUNWAY
+self:T2(IsAboveRunway)
+return IsAboveRunway
+end
+return nil
+end
+function POSITIONABLE:GetHeading()
+local DCSPositionable=self:GetDCSObject()
+if DCSPositionable then
+local PositionablePosition=DCSPositionable:getPosition()
+if PositionablePosition then
+local PositionableHeading=math.atan2(PositionablePosition.x.z,PositionablePosition.x.x)
+if PositionableHeading<0 then
+PositionableHeading=PositionableHeading+2*math.pi
+end
+PositionableHeading=PositionableHeading*180/math.pi
+self:T2(PositionableHeading)
+return PositionableHeading
+end
+end
+return nil
+end
+function POSITIONABLE:InAir()
+self:F2(self.PositionableName)
+return nil
+end
+function POSITIONABLE:GetVelocity()
+self:F2(self.PositionableName)
+local DCSPositionable=self:GetDCSObject()
+if DCSPositionable then
+local PositionableVelocityVec3=DCSPositionable:getVelocity()
+self:T3(PositionableVelocityVec3)
+return PositionableVelocityVec3
+end
+return nil
+end
+function POSITIONABLE:GetHeight()
+self:F2(self.PositionableName)
+local DCSPositionable=self:GetDCSObject()
+if DCSPositionable then
+local PositionablePosition=DCSPositionable:getPosition()
+if PositionablePosition then
+local PositionableHeight=PositionablePosition.p.y
+self:T2(PositionableHeight)
+return PositionableHeight
+end
+end
+return nil
+end
+function POSITIONABLE:GetVelocityKMH()
+self:F2(self.PositionableName)
+local DCSPositionable=self:GetDCSObject()
+if DCSPositionable then
+local VelocityVec3=self:GetVelocity()
+local Velocity=(VelocityVec3.x^2+VelocityVec3.y^2+VelocityVec3.z^2)^0.5
+local Velocity=Velocity*3.6
+self:T3(Velocity)
+return Velocity
+end
+return 0
+end
+function POSITIONABLE:GetVelocityMPS()
+self:F2(self.PositionableName)
+local DCSPositionable=self:GetDCSObject()
+if DCSPositionable then
+local VelocityVec3=self:GetVelocity()
+local Velocity=(VelocityVec3.x^2+VelocityVec3.y^2+VelocityVec3.z^2)^0.5
+self:T3(Velocity)
+return Velocity
+end
+return 0
+end
+function POSITIONABLE:GetMessageText(Message,Name)
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+Name=Name and(" => "..Name)or""
+local Callsign=string.format("%s",self:GetCallsign()~=""and self:GetCallsign()or self:GetName())
+local MessageText=string.format("[%s%s]: %s",Callsign,Name,Message)
+return MessageText
+end
+return nil
+end
+function POSITIONABLE:GetMessage(Message,Duration,Name)
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+local MessageText=self:GetMessageText(Message,Name)
+return MESSAGE:New(MessageText,Duration)
+end
+return nil
+end
+function POSITIONABLE:GetMessageType(Message,MessageType,Name)
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+local MessageText=self:GetMessageText(Message,Name)
+return MESSAGE:NewType(MessageText,MessageType)
+end
+return nil
+end
+function POSITIONABLE:MessageToAll(Message,Duration,Name)
+self:F2({Message,Duration})
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+self:GetMessage(Message,Duration,Name):ToAll()
+end
+return nil
+end
+function POSITIONABLE:MessageToCoalition(Message,Duration,MessageCoalition)
+self:F2({Message,Duration})
+local Name=""
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+if MessageCoalition==coalition.side.BLUE then
+Name="Blue coalition"
+end
+if MessageCoalition==coalition.side.RED then
+Name="Red coalition"
+end
+self:GetMessage(Message,Duration,Name):ToCoalition(MessageCoalition)
+end
+return nil
+end
+function POSITIONABLE:MessageTypeToCoalition(Message,MessageType,MessageCoalition)
+self:F2({Message,MessageType})
+local Name=""
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+if MessageCoalition==coalition.side.BLUE then
+Name="Blue coalition"
+end
+if MessageCoalition==coalition.side.RED then
+Name="Red coalition"
+end
+self:GetMessageType(Message,MessageType,Name):ToCoalition(MessageCoalition)
+end
+return nil
+end
+function POSITIONABLE:MessageToRed(Message,Duration,Name)
+self:F2({Message,Duration})
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+self:GetMessage(Message,Duration,Name):ToRed()
+end
+return nil
+end
+function POSITIONABLE:MessageToBlue(Message,Duration,Name)
+self:F2({Message,Duration})
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+self:GetMessage(Message,Duration,Name):ToBlue()
+end
+return nil
+end
+function POSITIONABLE:MessageToClient(Message,Duration,Client,Name)
+self:F2({Message,Duration})
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+self:GetMessage(Message,Duration,Name):ToClient(Client)
+end
+return nil
+end
+function POSITIONABLE:MessageToGroup(Message,Duration,MessageGroup,Name)
+self:F2({Message,Duration})
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+if DCSObject:isExist()then
+self:GetMessage(Message,Duration,Name):ToGroup(MessageGroup)
+end
+end
+return nil
+end
+function POSITIONABLE:MessageTypeToGroup(Message,MessageType,MessageGroup,Name)
+self:F2({Message,MessageType})
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+if DCSObject:isExist()then
+self:GetMessageType(Message,MessageType,Name):ToGroup(MessageGroup)
+end
+end
+return nil
+end
+function POSITIONABLE:MessageToSetGroup(Message,Duration,MessageSetGroup,Name)
+self:F2({Message,Duration})
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+if DCSObject:isExist()then
+MessageSetGroup:ForEachGroup(
+function(MessageGroup)
+self:GetMessage(Message,Duration,Name):ToGroup(MessageGroup)
+end
+)
+end
+end
+return nil
+end
+function POSITIONABLE:Message(Message,Duration,Name)
+self:F2({Message,Duration})
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+self:GetMessage(Message,Duration,Name):ToGroup(self)
+end
+return nil
+end
+function POSITIONABLE:GetRadio()
+self:F2(self)
+return RADIO:New(self)
+end
+function POSITIONABLE:GetBeacon()
+self:F2(self)
+return BEACON:New(self)
+end
+function POSITIONABLE:LaseUnit(Target,LaserCode,Duration)
+self:F2()
+LaserCode=LaserCode or math.random(1000,9999)
+local RecceDcsUnit=self:GetDCSObject()
+local TargetVec3=Target:GetVec3()
+self:E("bulding spot")
+self.Spot=SPOT:New(self)
+self.Spot:LaseOn(Target,LaserCode,Duration)
+self.LaserCode=LaserCode
+return self.Spot
+end
+function POSITIONABLE:LaseOff()
+self:F2()
+if self.Spot then
+self.Spot:LaseOff()
+self.Spot=nil
+end
+return self
+end
+function POSITIONABLE:IsLasing()
+self:F2()
+local Lasing=false
+if self.Spot then
+Lasing=self.Spot:IsLasing()
+end
+return Lasing
+end
+function POSITIONABLE:GetSpot()
+return self.Spot
+end
+function POSITIONABLE:GetLaserCode()
+return self.LaserCode
+end
+function POSITIONABLE:AddCargo(Cargo)
+self.__.Cargo[Cargo]=Cargo
+return self
+end
+function POSITIONABLE:RemoveCargo(Cargo)
+self.__.Cargo[Cargo]=nil
+return self
+end
+function POSITIONABLE:HasCargo(Cargo)
+return self.__.Cargo[Cargo]
+end
+function POSITIONABLE:ClearCargo()
+self.__.Cargo={}
+end
+function POSITIONABLE:CargoItemCount()
+local ItemCount=0
+for CargoName,Cargo in pairs(self.__.Cargo)do
+ItemCount=ItemCount+Cargo:GetCount()
+end
+return ItemCount
+end
+function POSITIONABLE:Flare(FlareColor)
+self:F2()
+trigger.action.signalFlare(self:GetVec3(),FlareColor,0)
+end
+function POSITIONABLE:FlareWhite()
+self:F2()
+trigger.action.signalFlare(self:GetVec3(),trigger.flareColor.White,0)
+end
+function POSITIONABLE:FlareYellow()
+self:F2()
+trigger.action.signalFlare(self:GetVec3(),trigger.flareColor.Yellow,0)
+end
+function POSITIONABLE:FlareGreen()
+self:F2()
+trigger.action.signalFlare(self:GetVec3(),trigger.flareColor.Green,0)
+end
+function POSITIONABLE:FlareRed()
+self:F2()
+local Vec3=self:GetVec3()
+if Vec3 then
+trigger.action.signalFlare(Vec3,trigger.flareColor.Red,0)
+end
+end
+function POSITIONABLE:Smoke(SmokeColor,Range,AddHeight)
+self:F2()
+if Range then
+local Vec3=self:GetRandomVec3(Range)
+Vec3.y=Vec3.y+AddHeight or 0
+trigger.action.smoke(Vec3,SmokeColor)
+else
+local Vec3=self:GetVec3()
+Vec3.y=Vec3.y+AddHeight or 0
+trigger.action.smoke(self:GetVec3(),SmokeColor)
+end
+end
+function POSITIONABLE:SmokeGreen()
+self:F2()
+trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Green)
+end
+function POSITIONABLE:SmokeRed()
+self:F2()
+trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Red)
+end
+function POSITIONABLE:SmokeWhite()
+self:F2()
+trigger.action.smoke(self:GetVec3(),trigger.smokeColor.White)
+end
+function POSITIONABLE:SmokeOrange()
+self:F2()
+trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Orange)
+end
+function POSITIONABLE:SmokeBlue()
+self:F2()
+trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Blue)
+end
+CONTROLLABLE={
+ClassName="CONTROLLABLE",
+ControllableName="",
+WayPointFunctions={},
+}
+function CONTROLLABLE:New(ControllableName)
+local self=BASE:Inherit(self,POSITIONABLE:New(ControllableName))
+self:F2(ControllableName)
+self.ControllableName=ControllableName
+self.TaskScheduler=SCHEDULER:New(self)
+return self
+end
+function CONTROLLABLE:_GetController()
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local ControllableController=DCSControllable:getController()
+return ControllableController
+end
+return nil
+end
+function CONTROLLABLE:GetUnits()
+self:F2({self.ControllableName})
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local DCSUnits=DCSControllable:getUnits()
+local Units={}
+for Index,UnitData in pairs(DCSUnits)do
+Units[#Units+1]=UNIT:Find(UnitData)
+end
+self:T3(Units)
+return Units
+end
+return nil
+end
+function CONTROLLABLE:GetLife()
+self:F2(self.ControllableName)
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local UnitLife=0
+local Units=self:GetUnits()
+if#Units==1 then
+local Unit=Units[1]
+UnitLife=Unit:GetLife()
+else
+local UnitLifeTotal=0
+for UnitID,Unit in pairs(Units)do
+local Unit=Unit
+UnitLifeTotal=UnitLifeTotal+Unit:GetLife()
+end
+UnitLife=UnitLifeTotal/#Units
+end
+return UnitLife
+end
+return nil
+end
+function CONTROLLABLE:GetLife0()
+self:F2(self.ControllableName)
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local UnitLife=0
+local Units=self:GetUnits()
+if#Units==1 then
+local Unit=Units[1]
+UnitLife=Unit:GetLife0()
+else
+local UnitLifeTotal=0
+for UnitID,Unit in pairs(Units)do
+local Unit=Unit
+UnitLifeTotal=UnitLifeTotal+Unit:GetLife0()
+end
+UnitLife=UnitLifeTotal/#Units
+end
+return UnitLife
+end
+return nil
+end
+function CONTROLLABLE:GetFuel()
+self:F(self.ControllableName)
+return nil
+end
+function CONTROLLABLE:ClearTasks()
+self:F2()
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local Controller=self:_GetController()
+Controller:resetTask()
+return self
+end
+return nil
+end
+function CONTROLLABLE:PopCurrentTask()
+self:F2()
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local Controller=self:_GetController()
+Controller:popTask()
+return self
+end
+return nil
+end
+function CONTROLLABLE:PushTask(DCSTask,WaitTime)
+self:F2()
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local Controller=self:_GetController()
+if WaitTime then
+self.TaskScheduler:Schedule(Controller,Controller.pushTask,{DCSTask},WaitTime)
+else
+Controller:pushTask(DCSTask)
+end
+return self
+end
+return nil
+end
+function CONTROLLABLE:SetTask(DCSTask,WaitTime)
+self:F2({DCSTask=DCSTask})
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local DCSControllableName=self:GetName()
+local function SetTask(Controller,DCSTask)
+if self and self:IsAlive()then
+local Controller=self:_GetController()
+Controller:setTask(DCSTask)
+else
+BASE:E(DCSControllableName.." is not alive anymore. Cannot set DCSTask "..DCSTask)
+end
+end
+if not WaitTime or WaitTime==0 then
+SetTask(self,DCSTask)
+else
+self.TaskScheduler:Schedule(self,SetTask,{DCSTask},WaitTime)
+end
+return self
+end
+return nil
+end
+function CONTROLLABLE:HasTask()
+local HasTaskResult=false
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local Controller=self:_GetController()
+HasTaskResult=Controller:hasTask()
+end
+return HasTaskResult
+end
+function CONTROLLABLE:TaskCondition(time,userFlag,userFlagValue,condition,duration,lastWayPoint)
+self:F2({time,userFlag,userFlagValue,condition,duration,lastWayPoint})
+local DCSStopCondition={}
+DCSStopCondition.time=time
+DCSStopCondition.userFlag=userFlag
+DCSStopCondition.userFlagValue=userFlagValue
+DCSStopCondition.condition=condition
+DCSStopCondition.duration=duration
+DCSStopCondition.lastWayPoint=lastWayPoint
+self:T3({DCSStopCondition})
+return DCSStopCondition
+end
+function CONTROLLABLE:TaskControlled(DCSTask,DCSStopCondition)
+self:F2({DCSTask,DCSStopCondition})
+local DCSTaskControlled
+DCSTaskControlled={
+id='ControlledTask',
+params={
+task=DCSTask,
+stopCondition=DCSStopCondition
+}
+}
+self:T3({DCSTaskControlled})
+return DCSTaskControlled
+end
+function CONTROLLABLE:TaskCombo(DCSTasks)
+self:F2({DCSTasks})
+local DCSTaskCombo
+DCSTaskCombo={
+id='ComboTask',
+params={
+tasks=DCSTasks
+}
+}
+for TaskID,Task in ipairs(DCSTasks)do
+self:T(Task)
+end
+self:T3({DCSTaskCombo})
+return DCSTaskCombo
+end
+function CONTROLLABLE:TaskWrappedAction(DCSCommand,Index)
+self:F2({DCSCommand})
+local DCSTaskWrappedAction
+DCSTaskWrappedAction={
+id="WrappedAction",
+enabled=true,
+number=Index or 1,
+auto=false,
+params={
+action=DCSCommand,
+},
+}
+self:T3({DCSTaskWrappedAction})
+return DCSTaskWrappedAction
+end
+function CONTROLLABLE:SetTaskWaypoint(Waypoint,Task)
+Waypoint.task=self:TaskCombo({Task})
+self:T3({Waypoint.task})
+return Waypoint.task
+end
+function CONTROLLABLE:SetCommand(DCSCommand)
+self:F2(DCSCommand)
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local Controller=self:_GetController()
+Controller:setCommand(DCSCommand)
+return self
+end
+return nil
+end
+function CONTROLLABLE:CommandSwitchWayPoint(FromWayPoint,ToWayPoint)
+self:F2({FromWayPoint,ToWayPoint})
+local CommandSwitchWayPoint={
+id='SwitchWaypoint',
+params={
+fromWaypointIndex=FromWayPoint,
+goToWaypointIndex=ToWayPoint,
+},
+}
+self:T3({CommandSwitchWayPoint})
+return CommandSwitchWayPoint
+end
+function CONTROLLABLE:CommandStopRoute(StopRoute)
+self:F2({StopRoute})
+local CommandStopRoute={
+id='StopRoute',
+params={
+value=StopRoute,
+},
+}
+self:T3({CommandStopRoute})
+return CommandStopRoute
+end
+function CONTROLLABLE:TaskAttackGroup(AttackGroup,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit)
+self:F2({self.ControllableName,AttackGroup,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit})
+local DirectionEnabled=nil
+if Direction then
+DirectionEnabled=true
+end
+local AltitudeEnabled=nil
+if Altitude then
+AltitudeEnabled=true
+end
+local DCSTask
+DCSTask={id='AttackGroup',
+params={
+groupId=AttackGroup:GetID(),
+weaponType=WeaponType,
+expend=WeaponExpend,
+attackQty=AttackQty,
+directionEnabled=DirectionEnabled,
+direction=Direction,
+altitudeEnabled=AltitudeEnabled,
+altitude=Altitude,
+attackQtyLimit=AttackQtyLimit,
+},
+},
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:TaskAttackUnit(AttackUnit,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,Visible,WeaponType)
+self:F2({self.ControllableName,AttackUnit,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,Visible,WeaponType})
+local DCSTask
+DCSTask={
+id='AttackUnit',
+params={
+unitId=AttackUnit:GetID(),
+groupAttack=GroupAttack or false,
+visible=Visible or false,
+expend=WeaponExpend or"Auto",
+directionEnabled=Direction and true or false,
+direction=Direction,
+altitudeEnabled=Altitude and true or false,
+altitude=Altitude or 30,
+attackQtyLimit=AttackQty and true or false,
+attackQty=AttackQty,
+weaponType=WeaponType
+}
+}
+self:T3(DCSTask)
+return DCSTask
+end
+function CONTROLLABLE:TaskBombing(Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType)
+self:F2({self.ControllableName,Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType})
+local DCSTask
+DCSTask={
+id='Bombing',
+params={
+point=Vec2,
+groupAttack=GroupAttack or false,
+expend=WeaponExpend or"Auto",
+attackQtyLimit=AttackQty and true or false,
+attackQty=AttackQty,
+directionEnabled=Direction and true or false,
+direction=Direction,
+altitudeEnabled=Altitude and true or false,
+altitude=Altitude or 30,
+weaponType=WeaponType,
+},
+},
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:TaskAttackMapObject(Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType)
+self:F2({self.ControllableName,Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType})
+local DCSTask
+DCSTask={
+id='AttackMapObject',
+params={
+point=Vec2,
+groupAttack=GroupAttack or false,
+expend=WeaponExpend or"Auto",
+attackQtyLimit=AttackQty and true or false,
+attackQty=AttackQty,
+directionEnabled=Direction and true or false,
+direction=Direction,
+altitudeEnabled=Altitude and true or false,
+altitude=Altitude or 30,
+weaponType=WeaponType,
+},
+},
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:TaskOrbitCircleAtVec2(Point,Altitude,Speed)
+self:F2({self.ControllableName,Point,Altitude,Speed})
+local LandHeight=land.getHeight(Point)
+self:T3({LandHeight})
+local DCSTask={id='Orbit',
+params={pattern=AI.Task.OrbitPattern.CIRCLE,
+point=Point,
+speed=Speed,
+altitude=Altitude+LandHeight
+}
+}
+return DCSTask
+end
+function CONTROLLABLE:TaskOrbitCircle(Altitude,Speed)
+self:F2({self.ControllableName,Altitude,Speed})
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local ControllablePoint=self:GetVec2()
+return self:TaskOrbitCircleAtVec2(ControllablePoint,Altitude,Speed)
+end
+return nil
+end
+function CONTROLLABLE:TaskHoldPosition()
+self:F2({self.ControllableName})
+return self:TaskOrbitCircle(30,10)
+end
+function CONTROLLABLE:TaskBombingRunway(Airbase,WeaponType,WeaponExpend,AttackQty,Direction,ControllableAttack)
+self:F2({self.ControllableName,Airbase,WeaponType,WeaponExpend,AttackQty,Direction,ControllableAttack})
+local DCSTask
+DCSTask={id='BombingRunway',
+params={
+point=Airbase:GetID(),
+weaponType=WeaponType,
+expend=WeaponExpend,
+attackQty=AttackQty,
+direction=Direction,
+controllableAttack=ControllableAttack,
+},
+},
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:TaskRefueling()
+self:F2({self.ControllableName})
+local DCSTask
+DCSTask={id='Refueling',
+params={
+},
+},
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:TaskLandAtVec2(Point,Duration)
+self:F2({self.ControllableName,Point,Duration})
+local DCSTask
+if Duration and Duration>0 then
+DCSTask={id='Land',
+params={
+point=Point,
+durationFlag=true,
+duration=Duration,
+},
+}
+else
+DCSTask={id='Land',
+params={
+point=Point,
+durationFlag=false,
+},
+}
+end
+self:T3(DCSTask)
+return DCSTask
+end
+function CONTROLLABLE:TaskLandAtZone(Zone,Duration,RandomPoint)
+self:F2({self.ControllableName,Zone,Duration,RandomPoint})
+local Point
+if RandomPoint then
+Point=Zone:GetRandomVec2()
+else
+Point=Zone:GetVec2()
+end
+local DCSTask=self:TaskLandAtVec2(Point,Duration)
+self:T3(DCSTask)
+return DCSTask
+end
+function CONTROLLABLE:TaskFollow(FollowControllable,Vec3,LastWaypointIndex)
+self:F2({self.ControllableName,FollowControllable,Vec3,LastWaypointIndex})
+local LastWaypointIndexFlag=false
+if LastWaypointIndex then
+LastWaypointIndexFlag=true
+end
+local DCSTask
+DCSTask={
+id='Follow',
+params={
+groupId=FollowControllable:GetID(),
+pos=Vec3,
+lastWptIndexFlag=LastWaypointIndexFlag,
+lastWptIndex=LastWaypointIndex
+}
+}
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:TaskEscort(FollowControllable,Vec3,LastWaypointIndex,EngagementDistance,TargetTypes)
+self:F2({self.ControllableName,FollowControllable,Vec3,LastWaypointIndex,EngagementDistance,TargetTypes})
+local LastWaypointIndexFlag=false
+if LastWaypointIndex then
+LastWaypointIndexFlag=true
+end
+local DCSTask
+DCSTask={id='Escort',
+params={
+groupId=FollowControllable:GetID(),
+pos=Vec3,
+lastWptIndexFlag=LastWaypointIndexFlag,
+lastWptIndex=LastWaypointIndex,
+engagementDistMax=EngagementDistance,
+targetTypes=TargetTypes,
+},
+},
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:TaskFireAtPoint(Vec2,Radius,AmmoCount)
+self:F2({self.ControllableName,Vec2,Radius,AmmoCount})
+local DCSTask
+DCSTask={id='FireAtPoint',
+params={
+point=Vec2,
+radius=Radius,
+expendQty=100,
+expendQtyEnabled=false,
+}
+}
+if AmmoCount then
+DCSTask.params.expendQty=AmmoCount
+DCSTask.params.expendQtyEnabled=true
+end
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:TaskHold()
+self:F2({self.ControllableName})
+local DCSTask
+DCSTask={id='Hold',
+params={
+}
+}
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:TaskFAC_AttackGroup(AttackGroup,WeaponType,Designation,Datalink)
+self:F2({self.ControllableName,AttackGroup,WeaponType,Designation,Datalink})
+local DCSTask
+DCSTask={id='FAC_AttackGroup',
+params={
+groupId=AttackGroup:GetID(),
+weaponType=WeaponType,
+designation=Designation,
+datalink=Datalink,
+}
+}
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:EnRouteTaskEngageTargets(Distance,TargetTypes,Priority)
+self:F2({self.ControllableName,Distance,TargetTypes,Priority})
+local DCSTask
+DCSTask={id='EngageTargets',
+params={
+maxDist=Distance,
+targetTypes=TargetTypes,
+priority=Priority
+}
+}
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:EnRouteTaskEngageTargetsInZone(Vec2,Radius,TargetTypes,Priority)
+self:F2({self.ControllableName,Vec2,Radius,TargetTypes,Priority})
+local DCSTask
+DCSTask={id='EngageTargetsInZone',
+params={
+point=Vec2,
+zoneRadius=Radius,
+targetTypes=TargetTypes,
+priority=Priority
+}
+}
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:EnRouteTaskEngageGroup(AttackGroup,Priority,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit)
+self:F2({self.ControllableName,AttackGroup,Priority,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit})
+local DirectionEnabled=nil
+if Direction then
+DirectionEnabled=true
+end
+local AltitudeEnabled=nil
+if Altitude then
+AltitudeEnabled=true
+end
+local DCSTask
+DCSTask={id='EngageControllable',
+params={
+groupId=AttackGroup:GetID(),
+weaponType=WeaponType,
+expend=WeaponExpend,
+attackQty=AttackQty,
+directionEnabled=DirectionEnabled,
+direction=Direction,
+altitudeEnabled=AltitudeEnabled,
+altitude=Altitude,
+attackQtyLimit=AttackQtyLimit,
+priority=Priority,
+},
+},
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:EnRouteTaskEngageUnit(EngageUnit,Priority,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,Visible,ControllableAttack)
+self:F2({self.ControllableName,EngageUnit,Priority,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,Visible,ControllableAttack})
+local DCSTask
+DCSTask={id='EngageUnit',
+params={
+unitId=EngageUnit:GetID(),
+priority=Priority or 1,
+groupAttack=GroupAttack or false,
+visible=Visible or false,
+expend=WeaponExpend or"Auto",
+directionEnabled=Direction and true or false,
+direction=Direction,
+altitudeEnabled=Altitude and true or false,
+altitude=Altitude,
+attackQtyLimit=AttackQty and true or false,
+attackQty=AttackQty,
+controllableAttack=ControllableAttack,
+},
+},
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:EnRouteTaskAWACS()
+self:F2({self.ControllableName})
+local DCSTask
+DCSTask={id='AWACS',
+params={
+}
+}
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:EnRouteTaskTanker()
+self:F2({self.ControllableName})
+local DCSTask
+DCSTask={id='Tanker',
+params={
+}
+}
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:EnRouteTaskEWR()
+self:F2({self.ControllableName})
+local DCSTask
+DCSTask={id='EWR',
+params={
+}
+}
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:EnRouteTaskFAC_EngageGroup(AttackGroup,Priority,WeaponType,Designation,Datalink)
+self:F2({self.ControllableName,AttackGroup,WeaponType,Priority,Designation,Datalink})
+local DCSTask
+DCSTask={id='FAC_EngageControllable',
+params={
+groupId=AttackGroup:GetID(),
+weaponType=WeaponType,
+designation=Designation,
+datalink=Datalink,
+priority=Priority,
+}
+}
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:EnRouteTaskFAC(Radius,Priority)
+self:F2({self.ControllableName,Radius,Priority})
+local DCSTask
+DCSTask={id='FAC',
+params={
+radius=Radius,
+priority=Priority
+}
+}
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:TaskEmbarking(Point,Duration,EmbarkingControllable)
+self:F2({self.ControllableName,Point,Duration,EmbarkingControllable.DCSControllable})
+local DCSTask
+DCSTask={id='Embarking',
+params={x=Point.x,
+y=Point.y,
+duration=Duration,
+controllablesForEmbarking={EmbarkingControllable.ControllableID},
+durationFlag=true,
+distributionFlag=false,
+distribution={},
+}
+}
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:TaskEmbarkToTransport(Point,Radius)
+self:F2({self.ControllableName,Point,Radius})
+local DCSTask
+DCSTask={id='EmbarkToTransport',
+params={x=Point.x,
+y=Point.y,
+zoneRadius=Radius,
+}
+}
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:TaskFunction(FunctionString,...)
+self:F2({FunctionString,arg})
+local DCSTask
+local DCSScript={}
+DCSScript[#DCSScript+1]="local MissionControllable = GROUP:Find( ... ) "
+if arg and arg.n>0 then
+local ArgumentKey='_'..tostring(arg):match("table: (.*)")
+self:SetState(self,ArgumentKey,arg)
+DCSScript[#DCSScript+1]="local Arguments = MissionControllable:GetState( MissionControllable, '"..ArgumentKey.."' ) "
+DCSScript[#DCSScript+1]=FunctionString.."( MissionControllable, unpack( Arguments ) )"
+else
+DCSScript[#DCSScript+1]=FunctionString.."( MissionControllable )"
+end
+DCSTask=self:TaskWrappedAction(
+self:CommandDoScript(
+table.concat(DCSScript)
+)
+)
+self:T(DCSTask)
+return DCSTask
+end
+function CONTROLLABLE:TaskMission(TaskMission)
+self:F2(Points)
+local DCSTask
+DCSTask={id='Mission',params={TaskMission,},}
+self:T3({DCSTask})
+return DCSTask
+end
+do
+function CONTROLLABLE:PatrolRoute()
+local PatrolGroup=self
+if not self:IsInstanceOf("GROUP")then
+PatrolGroup=self:GetGroup()
+end
+self:E({PatrolGroup=PatrolGroup:GetName()})
+if PatrolGroup:IsGround()or PatrolGroup:IsShip()then
+local Waypoints=PatrolGroup:GetTemplateRoutePoints()
+local FromCoord=PatrolGroup:GetCoordinate()
+local From=FromCoord:WaypointGround(120)
+table.insert(Waypoints,1,From)
+local TaskRoute=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolRoute")
+self:E({Waypoints=Waypoints})
+local Waypoint=Waypoints[#Waypoints]
+PatrolGroup:SetTaskWaypoint(Waypoint,TaskRoute)
+PatrolGroup:Route(Waypoints)
+end
+end
+function CONTROLLABLE:PatrolRouteRandom(Speed,Formation,ToWaypoint)
+local PatrolGroup=self
+if not self:IsInstanceOf("GROUP")then
+PatrolGroup=self:GetGroup()
+end
+self:E({PatrolGroup=PatrolGroup:GetName()})
+if PatrolGroup:IsGround()or PatrolGroup:IsShip()then
+local Waypoints=PatrolGroup:GetTemplateRoutePoints()
+local FromCoord=PatrolGroup:GetCoordinate()
+local FromWaypoint=1
+if ToWaypoint then
+FromWaypoint=ToWaypoint
+end
+local ToWaypoint
+repeat
+ToWaypoint=math.random(1,#Waypoints)
+until(ToWaypoint~=FromWaypoint)
+self:E({FromWaypoint=FromWaypoint,ToWaypoint=ToWaypoint})
+local Waypoint=Waypoints[ToWaypoint]
+local ToCoord=COORDINATE:NewFromVec2({x=Waypoint.x,y=Waypoint.y})
+local Route={}
+Route[#Route+1]=FromCoord:WaypointGround(0)
+Route[#Route+1]=ToCoord:WaypointGround(Speed,Formation)
+local TaskRouteToZone=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolRouteRandom",Speed,Formation,ToWaypoint)
+PatrolGroup:SetTaskWaypoint(Route[#Route],TaskRouteToZone)
+PatrolGroup:Route(Route,1)
+end
+end
+function CONTROLLABLE:PatrolZones(ZoneList,Speed,Formation)
+if not type(ZoneList)=="table"then
+ZoneList={ZoneList}
+end
+local PatrolGroup=self
+if not self:IsInstanceOf("GROUP")then
+PatrolGroup=self:GetGroup()
+end
+self:E({PatrolGroup=PatrolGroup:GetName()})
+if PatrolGroup:IsGround()or PatrolGroup:IsShip()then
+local Waypoints=PatrolGroup:GetTemplateRoutePoints()
+local Waypoint=Waypoints[math.random(1,#Waypoints)]
+local FromCoord=PatrolGroup:GetCoordinate()
+local RandomZone=ZoneList[math.random(1,#ZoneList)]
+local ToCoord=RandomZone:GetRandomCoordinate(10)
+local Route={}
+Route[#Route+1]=FromCoord:WaypointGround(120)
+Route[#Route+1]=ToCoord:WaypointGround(Speed,Formation)
+local TaskRouteToZone=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolZones",ZoneList,Speed,Formation)
+PatrolGroup:SetTaskWaypoint(Route[#Route],TaskRouteToZone)
+PatrolGroup:Route(Route,1)
+end
+end
+end
+function CONTROLLABLE:TaskRoute(Points)
+self:F2(Points)
+local DCSTask
+DCSTask={id='Mission',params={route={points=Points,},},}
+self:T3({DCSTask})
+return DCSTask
+end
+function CONTROLLABLE:RouteToVec2(Point,Speed)
+self:F2({Point,Speed})
+local ControllablePoint=self:GetUnit(1):GetVec2()
+local PointFrom={}
+PointFrom.x=ControllablePoint.x
+PointFrom.y=ControllablePoint.y
+PointFrom.type="Turning Point"
+PointFrom.action="Turning Point"
+PointFrom.speed=Speed
+PointFrom.speed_locked=true
+PointFrom.properties={
+["vnav"]=1,
+["scale"]=0,
+["angle"]=0,
+["vangle"]=0,
+["steer"]=2,
+}
+local PointTo={}
+PointTo.x=Point.x
+PointTo.y=Point.y
+PointTo.type="Turning Point"
+PointTo.action="Fly Over Point"
+PointTo.speed=Speed
+PointTo.speed_locked=true
+PointTo.properties={
+["vnav"]=1,
+["scale"]=0,
+["angle"]=0,
+["vangle"]=0,
+["steer"]=2,
+}
+local Points={PointFrom,PointTo}
+self:T3(Points)
+self:Route(Points)
+return self
+end
+function CONTROLLABLE:RouteToVec3(Point,Speed)
+self:F2({Point,Speed})
+local ControllableVec3=self:GetUnit(1):GetVec3()
+local PointFrom={}
+PointFrom.x=ControllableVec3.x
+PointFrom.y=ControllableVec3.z
+PointFrom.alt=ControllableVec3.y
+PointFrom.alt_type="BARO"
+PointFrom.type="Turning Point"
+PointFrom.action="Turning Point"
+PointFrom.speed=Speed
+PointFrom.speed_locked=true
+PointFrom.properties={
+["vnav"]=1,
+["scale"]=0,
+["angle"]=0,
+["vangle"]=0,
+["steer"]=2,
+}
+local PointTo={}
+PointTo.x=Point.x
+PointTo.y=Point.z
+PointTo.alt=Point.y
+PointTo.alt_type="BARO"
+PointTo.type="Turning Point"
+PointTo.action="Fly Over Point"
+PointTo.speed=Speed
+PointTo.speed_locked=true
+PointTo.properties={
+["vnav"]=1,
+["scale"]=0,
+["angle"]=0,
+["vangle"]=0,
+["steer"]=2,
+}
+local Points={PointFrom,PointTo}
+self:T3(Points)
+self:Route(Points)
+return self
+end
+function CONTROLLABLE:Route(Route,DelaySeconds)
+self:F2(Route)
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local RouteTask=self:TaskRoute(Route)
+self:SetTask(RouteTask,DelaySeconds or 1)
+return self
+end
+return nil
+end
+function CONTROLLABLE:RouteGroundTo(ToCoordinate,Speed,Formation,DelaySeconds)
+local FromCoordinate=self:GetCoordinate()
+local FromWP=FromCoordinate:WaypointGround()
+local ToWP=ToCoordinate:WaypointGround(Speed,Formation)
+self:Route({FromWP,ToWP},DelaySeconds)
+return self
+end
+function CONTROLLABLE:RouteAirTo(ToCoordinate,AltType,Type,Action,Speed,DelaySeconds)
+local FromCoordinate=self:GetCoordinate()
+local FromWP=FromCoordinate:WaypointAir()
+local ToWP=ToCoordinate:WaypointAir(AltType,Type,Action,Speed)
+self:Route({FromWP,ToWP},DelaySeconds)
+return self
+end
+function CONTROLLABLE:TaskRouteToZone(Zone,Randomize,Speed,Formation)
+self:F2(Zone)
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local ControllablePoint=self:GetVec2()
+local PointFrom={}
+PointFrom.x=ControllablePoint.x
+PointFrom.y=ControllablePoint.y
+PointFrom.type="Turning Point"
+PointFrom.action=Formation or"Cone"
+PointFrom.speed=20/1.6
+local PointTo={}
+local ZonePoint
+if Randomize then
+ZonePoint=Zone:GetRandomVec2()
+else
+ZonePoint=Zone:GetVec2()
+end
+PointTo.x=ZonePoint.x
+PointTo.y=ZonePoint.y
+PointTo.type="Turning Point"
+if Formation then
+PointTo.action=Formation
+else
+PointTo.action="Cone"
+end
+if Speed then
+PointTo.speed=Speed
+else
+PointTo.speed=20/1.6
+end
+local Points={PointFrom,PointTo}
+self:T3(Points)
+self:Route(Points)
+return self
+end
+return nil
+end
+function CONTROLLABLE:TaskRouteToVec2(Vec2,Speed,Formation)
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local ControllablePoint=self:GetVec2()
+local PointFrom={}
+PointFrom.x=ControllablePoint.x
+PointFrom.y=ControllablePoint.y
+PointFrom.type="Turning Point"
+PointFrom.action=Formation or"Cone"
+PointFrom.speed=20/1.6
+local PointTo={}
+PointTo.x=Vec2.x
+PointTo.y=Vec2.y
+PointTo.type="Turning Point"
+if Formation then
+PointTo.action=Formation
+else
+PointTo.action="Cone"
+end
+if Speed then
+PointTo.speed=Speed
+else
+PointTo.speed=60/3.6
+end
+local Points={PointFrom,PointTo}
+self:T3(Points)
+self:Route(Points)
+return self
+end
+return nil
+end
+function CONTROLLABLE:CommandDoScript(DoScript)
+local DCSDoScript={
+id="Script",
+params={
+command=DoScript,
+},
+}
+self:T3(DCSDoScript)
+return DCSDoScript
+end
+function CONTROLLABLE:GetTaskMission()
+self:F2(self.ControllableName)
+return routines.utils.deepCopy(_DATABASE.Templates.Controllables[self.ControllableName].Template)
+end
+function CONTROLLABLE:GetTaskRoute()
+self:F2(self.ControllableName)
+return routines.utils.deepCopy(_DATABASE.Templates.Controllables[self.ControllableName].Template.route.points)
+end
+function CONTROLLABLE:CopyRoute(Begin,End,Randomize,Radius)
+self:F2({Begin,End})
+local Points={}
+local ControllableName=string.match(self:GetName(),".*#")
+if ControllableName then
+ControllableName=ControllableName:sub(1,-2)
+else
+ControllableName=self:GetName()
+end
+self:T3({ControllableName})
+local Template=_DATABASE.Templates.Controllables[ControllableName].Template
+if Template then
+if not Begin then
+Begin=0
+end
+if not End then
+End=0
+end
+for TPointID=Begin+1,#Template.route.points-End do
+if Template.route.points[TPointID]then
+Points[#Points+1]=routines.utils.deepCopy(Template.route.points[TPointID])
+if Randomize then
+if not Radius then
+Radius=500
+end
+Points[#Points].x=Points[#Points].x+math.random(Radius*-1,Radius)
+Points[#Points].y=Points[#Points].y+math.random(Radius*-1,Radius)
+end
+end
+end
+return Points
+else
+error("Template not found for Controllable : "..ControllableName)
+end
+return nil
+end
+function CONTROLLABLE:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
+self:F2(self.ControllableName)
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local DetectionVisual=(DetectVisual and DetectVisual==true)and Controller.Detection.VISUAL or nil
+local DetectionOptical=(DetectOptical and DetectOptical==true)and Controller.Detection.OPTICAL or nil
+local DetectionRadar=(DetectRadar and DetectRadar==true)and Controller.Detection.RADAR or nil
+local DetectionIRST=(DetectIRST and DetectIRST==true)and Controller.Detection.IRST or nil
+local DetectionRWR=(DetectRWR and DetectRWR==true)and Controller.Detection.RWR or nil
+local DetectionDLINK=(DetectDLINK and DetectDLINK==true)and Controller.Detection.DLINK or nil
+self:T({DetectionVisual,DetectionOptical,DetectionRadar,DetectionIRST,DetectionRWR,DetectionDLINK})
+return self:_GetController():getDetectedTargets(DetectionVisual,DetectionOptical,DetectionRadar,DetectionIRST,DetectionRWR,DetectionDLINK)
+end
+return nil
+end
+function CONTROLLABLE:IsTargetDetected(DCSObject,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
+self:F2(self.ControllableName)
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local DetectionVisual=(DetectVisual and DetectVisual==true)and Controller.Detection.VISUAL or nil
+local DetectionOptical=(DetectOptical and DetectOptical==true)and Controller.Detection.OPTICAL or nil
+local DetectionRadar=(DetectRadar and DetectRadar==true)and Controller.Detection.RADAR or nil
+local DetectionIRST=(DetectIRST and DetectIRST==true)and Controller.Detection.IRST or nil
+local DetectionRWR=(DetectRWR and DetectRWR==true)and Controller.Detection.RWR or nil
+local DetectionDLINK=(DetectDLINK and DetectDLINK==true)and Controller.Detection.DLINK or nil
+local Controller=self:_GetController()
+local TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity
+=Controller:isTargetDetected(DCSObject,DetectionVisual,DetectionOptical,DetectionRadar,DetectionIRST,DetectionRWR,DetectionDLINK)
+return TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity
+end
+return nil
+end
+function CONTROLLABLE:OptionROEHoldFirePossible()
+self:F2({self.ControllableName})
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+if self:IsAir()or self:IsGround()or self:IsShip()then
+return true
+end
+return false
+end
+return nil
+end
+function CONTROLLABLE:OptionROEHoldFire()
+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.ROE,AI.Option.Air.val.ROE.WEAPON_HOLD)
+elseif self:IsGround()then
+Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.WEAPON_HOLD)
+elseif self:IsShip()then
+Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.WEAPON_HOLD)
+end
+return self
+end
+return nil
+end
+function CONTROLLABLE:OptionROEReturnFirePossible()
+self:F2({self.ControllableName})
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+if self:IsAir()or self:IsGround()or self:IsShip()then
+return true
+end
+return false
+end
+return nil
+end
+function CONTROLLABLE:OptionROEReturnFire()
+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.ROE,AI.Option.Air.val.ROE.RETURN_FIRE)
+elseif self:IsGround()then
+Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.RETURN_FIRE)
+elseif self:IsShip()then
+Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.RETURN_FIRE)
+end
+return self
+end
+return nil
+end
+function CONTROLLABLE:OptionROEOpenFirePossible()
+self:F2({self.ControllableName})
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+if self:IsAir()or self:IsGround()or self:IsShip()then
+return true
+end
+return false
+end
+return nil
+end
+function CONTROLLABLE:OptionROEOpenFire()
+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.ROE,AI.Option.Air.val.ROE.OPEN_FIRE)
+elseif self:IsGround()then
+Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.OPEN_FIRE)
+elseif self:IsShip()then
+Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.OPEN_FIRE)
+end
+return self
+end
+return nil
+end
+function CONTROLLABLE:OptionROEWeaponFreePossible()
+self:F2({self.ControllableName})
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+if self:IsAir()then
+return true
+end
+return false
+end
+return nil
+end
+function CONTROLLABLE:OptionROEWeaponFree()
+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.ROE,AI.Option.Air.val.ROE.WEAPON_FREE)
+end
+return self
+end
+return nil
+end
+function CONTROLLABLE:OptionROTNoReactionPossible()
+self:F2({self.ControllableName})
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+if self:IsAir()then
+return true
+end
+return false
+end
+return nil
+end
+function CONTROLLABLE:OptionROTNoReaction()
+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.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION)
+end
+return self
+end
+return nil
+end
+function CONTROLLABLE:OptionROTPassiveDefensePossible()
+self:F2({self.ControllableName})
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+if self:IsAir()then
+return true
+end
+return false
+end
+return nil
+end
+function CONTROLLABLE:OptionROTPassiveDefense()
+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.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE)
+end
+return self
+end
+return nil
+end
+function CONTROLLABLE:OptionROTEvadeFirePossible()
+self:F2({self.ControllableName})
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+if self:IsAir()then
+return true
+end
+return false
+end
+return nil
+end
+function CONTROLLABLE:OptionROTEvadeFire()
+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.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
+end
+return self
+end
+return nil
+end
+function CONTROLLABLE:OptionROTVerticalPossible()
+self:F2({self.ControllableName})
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+if self:IsAir()then
+return true
+end
+return false
+end
+return nil
+end
+function CONTROLLABLE:OptionROTVertical()
+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.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE)
+end
+return self
+end
+return nil
+end
+function CONTROLLABLE:OptionRTBBingoFuel(RTB)
+self:F2({self.ControllableName})
+RTB=RTB or true
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local Controller=self:_GetController()
+if self:IsAir()then
+Controller:setOption(AI.Option.Air.id.RTB_ON_BINGO,RTB)
+end
+return self
+end
+return nil
+end
+function CONTROLLABLE:OptionRTBAmmo(WeaponsFlag)
+self:F2({self.ControllableName})
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local Controller=self:_GetController()
+if self:IsAir()then
+Controller:setOption(AI.Option.GROUND.id.RTB_ON_OUT_OF_AMMO,WeaponsFlag)
+end
+return self
+end
+return nil
+end
+function CONTROLLABLE:WayPointInitialize(WayPoints)
+self:F({WayPoints})
+if WayPoints then
+self.WayPoints=WayPoints
+else
+self.WayPoints=self:GetTaskRoute()
+end
+return self
+end
+function CONTROLLABLE:GetWayPoints()
+self:F()
+if self.WayPoints then
+return self.WayPoints
+end
+return nil
+end
+function CONTROLLABLE:WayPointFunction(WayPoint,WayPointIndex,WayPointFunction,...)
+self:F2({WayPoint,WayPointIndex,WayPointFunction})
+table.insert(self.WayPoints[WayPoint].task.params.tasks,WayPointIndex)
+self.WayPoints[WayPoint].task.params.tasks[WayPointIndex]=self:TaskFunction(WayPointFunction,arg)
+return self
+end
+function CONTROLLABLE:WayPointExecute(WayPoint,WaitTime)
+self:F({WayPoint,WaitTime})
+if not WayPoint then
+WayPoint=1
+end
+for TaskPointID=1,WayPoint-1 do
+table.remove(self.WayPoints,1)
+end
+self:T3(self.WayPoints)
+self:SetTask(self:TaskRoute(self.WayPoints),WaitTime)
+return self
+end
+function CONTROLLABLE:IsAirPlane()
+self:F2()
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+local Category=DCSObject:getDesc().category
+return Category==Unit.Category.AIRPLANE
+end
+return nil
+end
+function CONTROLLABLE:GetSize()
+local DCSObject=self:GetDCSObject()
+if DCSObject then
+return 1
+else
+return 0
+end
+end
+GROUP={
+ClassName="GROUP",
+}
+GROUP.Takeoff={
+Air=1,
+Runway=2,
+Hot=3,
+Cold=4,
+}
+GROUPTEMPLATE={}
+GROUPTEMPLATE.Takeoff={
+[GROUP.Takeoff.Air]={"Turning Point","Turning Point"},
+[GROUP.Takeoff.Runway]={"TakeOff","From Runway"},
+[GROUP.Takeoff.Hot]={"TakeOffParkingHot","From Parking Area Hot"},
+[GROUP.Takeoff.Cold]={"TakeOffParking","From Parking Area"}
+}
+function GROUP:Register(GroupName)
+self=BASE:Inherit(self,CONTROLLABLE:New(GroupName))
+self:F2(GroupName)
+self.GroupName=GroupName
+self:SetEventPriority(4)
+return self
+end
+function GROUP:Find(DCSGroup)
+local GroupName=DCSGroup:getName()
+local GroupFound=_DATABASE:FindGroup(GroupName)
+return GroupFound
+end
+function GROUP:FindByName(GroupName)
+local GroupFound=_DATABASE:FindGroup(GroupName)
+return GroupFound
+end
+function GROUP:GetDCSObject()
+local DCSGroup=Group.getByName(self.GroupName)
+if DCSGroup then
+return DCSGroup
+end
+return nil
+end
+function GROUP:GetPositionVec3()
+self:F2(self.PositionableName)
+local DCSPositionable=self:GetDCSObject()
+if DCSPositionable then
+local PositionablePosition=DCSPositionable:getUnits()[1]:getPosition().p
+self:T3(PositionablePosition)
+return PositionablePosition
+end
+return nil
+end
+function GROUP:IsAlive()
+self:F2(self.GroupName)
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+if DCSGroup:isExist()then
+local DCSUnit=DCSGroup:getUnit(1)
+if DCSUnit then
+local GroupIsAlive=DCSUnit:isActive()
+self:T3(GroupIsAlive)
+return GroupIsAlive
+end
+end
+end
+return nil
+end
+function GROUP:Destroy()
+self:F2(self.GroupName)
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+for Index,UnitData in pairs(DCSGroup:getUnits())do
+self:CreateEventCrash(timer.getTime(),UnitData)
+end
+DCSGroup:destroy()
+DCSGroup=nil
+end
+return nil
+end
+function GROUP:GetCategory()
+self:F2(self.GroupName)
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local GroupCategory=DCSGroup:getCategory()
+self:T3(GroupCategory)
+return GroupCategory
+end
+return nil
+end
+function GROUP:GetCategoryName()
+self:F2(self.GroupName)
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local CategoryNames={
+[Group.Category.AIRPLANE]="Airplane",
+[Group.Category.HELICOPTER]="Helicopter",
+[Group.Category.GROUND]="Ground Unit",
+[Group.Category.SHIP]="Ship",
+}
+local GroupCategory=DCSGroup:getCategory()
+self:T3(GroupCategory)
+return CategoryNames[GroupCategory]
+end
+return nil
+end
+function GROUP:GetCoalition()
+self:F2(self.GroupName)
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local GroupCoalition=DCSGroup:getCoalition()
+self:T3(GroupCoalition)
+return GroupCoalition
+end
+return nil
+end
+function GROUP:GetCountry()
+self:F2(self.GroupName)
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local GroupCountry=DCSGroup:getUnit(1):getCountry()
+self:T3(GroupCountry)
+return GroupCountry
+end
+return nil
+end
+function GROUP:GetUnit(UnitNumber)
+self:F2({self.GroupName,UnitNumber})
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local DCSUnit=DCSGroup:getUnit(UnitNumber)
+local UnitFound=UNIT:Find(DCSGroup:getUnit(UnitNumber))
+self:T2(UnitFound)
+return UnitFound
+end
+return nil
+end
+function GROUP:GetDCSUnit(UnitNumber)
+self:F2({self.GroupName,UnitNumber})
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local DCSUnitFound=DCSGroup:getUnit(UnitNumber)
+self:T3(DCSUnitFound)
+return DCSUnitFound
+end
+return nil
+end
+function GROUP:GetSize()
+self:F2({self.GroupName})
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local GroupSize=DCSGroup:getSize()
+if GroupSize then
+self:T3(GroupSize)
+return GroupSize
+else
+return 0
+end
+end
+return nil
+end
+function GROUP:GetInitialSize()
+self:F2({self.GroupName})
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local GroupInitialSize=DCSGroup:getInitialSize()
+self:T3(GroupInitialSize)
+return GroupInitialSize
+end
+return nil
+end
+function GROUP:GetDCSUnits()
+self:F2({self.GroupName})
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local DCSUnits=DCSGroup:getUnits()
+self:T3(DCSUnits)
+return DCSUnits
+end
+return nil
+end
+function GROUP:Activate()
+self:F2({self.GroupName})
+trigger.action.activateGroup(self:GetDCSObject())
+return self:GetDCSObject()
+end
+function GROUP:GetTypeName()
+self:F2(self.GroupName)
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local GroupTypeName=DCSGroup:getUnit(1):getTypeName()
+self:T3(GroupTypeName)
+return(GroupTypeName)
+end
+return nil
+end
+function GROUP:GetPlayerName()
+self:F2(self.GroupName)
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local PlayerName=DCSGroup:getUnit(1):getPlayerName()
+self:T3(PlayerName)
+return(PlayerName)
+end
+return nil
+end
+function GROUP:GetCallsign()
+self:F2(self.GroupName)
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local GroupCallSign=DCSGroup:getUnit(1):getCallsign()
+self:T3(GroupCallSign)
+return GroupCallSign
+end
+return nil
+end
+function GROUP:GetVec2()
+self:F2(self.GroupName)
+local UnitPoint=self:GetUnit(1)
+UnitPoint:GetVec2()
+local GroupPointVec2=UnitPoint:GetVec2()
+self:T3(GroupPointVec2)
+return GroupPointVec2
+end
+function GROUP:GetVec3()
+self:F2(self.GroupName)
+local GroupVec3=self:GetUnit(1):GetVec3()
+self:T3(GroupVec3)
+return GroupVec3
+end
+function GROUP:GetPointVec2()
+self:F2(self.GroupName)
+local FirstUnit=self:GetUnit(1)
+if FirstUnit then
+local FirstUnitPointVec2=FirstUnit:GetPointVec2()
+self:T3(FirstUnitPointVec2)
+return FirstUnitPointVec2
+end
+return nil
+end
+function GROUP:GetCoordinate()
+self:F2(self.PositionableName)
+local FirstUnit=self:GetUnit(1)
+if FirstUnit then
+local FirstUnitCoordinate=FirstUnit:GetCoordinate()
+self:T3(FirstUnitCoordinate)
+return FirstUnitCoordinate
+end
+return nil
+end
+function GROUP:GetRandomVec3(Radius)
+self:F2(self.GroupName)
+local FirstUnit=self:GetUnit(1)
+if FirstUnit then
+local FirstUnitRandomPointVec3=FirstUnit:GetRandomVec3(Radius)
+self:T3(FirstUnitRandomPointVec3)
+return FirstUnitRandomPointVec3
+end
+return nil
+end
+function GROUP:GetHeading()
+self:F2(self.GroupName)
+local GroupSize=self:GetSize()
+local HeadingAccumulator=0
+if GroupSize then
+for i=1,GroupSize do
+HeadingAccumulator=HeadingAccumulator+self:GetUnit(i):GetHeading()
+end
+return math.floor(HeadingAccumulator/GroupSize)
+end
+return nil
+end
+function GROUP:GetFuel()
+self:F(self.ControllableName)
+local DCSControllable=self:GetDCSObject()
+if DCSControllable then
+local GroupSize=self:GetSize()
+local TotalFuel=0
+for UnitID,UnitData in pairs(self:GetUnits())do
+local Unit=UnitData
+local UnitFuel=Unit:GetFuel()
+self:F({Fuel=UnitFuel})
+TotalFuel=TotalFuel+UnitFuel
+end
+local GroupFuel=TotalFuel/GroupSize
+return GroupFuel
+end
+return 0
+end
+do
+function GROUP:IsCompletelyInZone(Zone)
+self:F2({self.GroupName,Zone})
+if not self:IsAlive()then return false end
+for UnitID,UnitData in pairs(self:GetUnits())do
+local Unit=UnitData
+if Zone:IsVec3InZone(Unit:GetVec3())then
+else
+return false
+end
+end
+return true
+end
+function GROUP:IsPartlyInZone(Zone)
+self:F2({self.GroupName,Zone})
+local IsOneUnitInZone=false
+local IsOneUnitOutsideZone=false
+if not self:IsAlive()then return false end
+for UnitID,UnitData in pairs(self:GetUnits())do
+local Unit=UnitData
+if Zone:IsVec3InZone(Unit:GetVec3())then
+IsOneUnitInZone=true
+else
+IsOneUnitOutsideZone=true
+end
+end
+if IsOneUnitInZone and IsOneUnitOutsideZone then
+return true
+else
+return false
+end
+end
+function GROUP:IsNotInZone(Zone)
+self:F2({self.GroupName,Zone})
+if not self:IsAlive()then return true end
+for UnitID,UnitData in pairs(self:GetUnits())do
+local Unit=UnitData
+if Zone:IsVec3InZone(Unit:GetVec3())then
+return false
+end
+end
+return true
+end
+function GROUP:CountInZone(Zone)
+self:F2({self.GroupName,Zone})
+local Count=0
+if not self:IsAlive()then return Count end
+for UnitID,UnitData in pairs(self:GetUnits())do
+local Unit=UnitData
+if Zone:IsVec3InZone(Unit:GetVec3())then
+Count=Count+1
+end
+end
+return Count
+end
+function GROUP:IsAir()
+self:F2(self.GroupName)
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local IsAirResult=DCSGroup:getCategory()==Group.Category.AIRPLANE or DCSGroup:getCategory()==Group.Category.HELICOPTER
+self:T3(IsAirResult)
+return IsAirResult
+end
+return nil
+end
+function GROUP:IsHelicopter()
+self:F2(self.GroupName)
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local GroupCategory=DCSGroup:getCategory()
+self:T2(GroupCategory)
+return GroupCategory==Group.Category.HELICOPTER
+end
+return nil
+end
+function GROUP:IsAirPlane()
+self:F2()
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local GroupCategory=DCSGroup:getCategory()
+self:T2(GroupCategory)
+return GroupCategory==Group.Category.AIRPLANE
+end
+return nil
+end
+function GROUP:IsGround()
+self:F2()
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local GroupCategory=DCSGroup:getCategory()
+self:T2(GroupCategory)
+return GroupCategory==Group.Category.GROUND
+end
+return nil
+end
+function GROUP:IsShip()
+self:F2()
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local GroupCategory=DCSGroup:getCategory()
+self:T2(GroupCategory)
+return GroupCategory==Group.Category.SHIP
+end
+return nil
+end
+function GROUP:AllOnGround()
+self:F2()
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local AllOnGroundResult=true
+for Index,UnitData in pairs(DCSGroup:getUnits())do
+if UnitData:inAir()then
+AllOnGroundResult=false
+end
+end
+self:T3(AllOnGroundResult)
+return AllOnGroundResult
+end
+return nil
+end
+end
+do
+function GROUP:SetAIOnOff(AIOnOff)
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local DCSController=DCSGroup:getController()
+if DCSController then
+DCSController:setOnOff(AIOnOff)
+return self
+end
+end
+return nil
+end
+function GROUP:SetAIOn()
+return self:SetAIOnOff(true)
+end
+function GROUP:SetAIOff()
+return self:SetAIOnOff(false)
+end
+end
+function GROUP:GetMaxVelocity()
+self:F2()
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local GroupVelocityMax=0
+for Index,UnitData in pairs(DCSGroup:getUnits())do
+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
+return GroupVelocityMax
+end
+return nil
+end
+function GROUP:GetMinHeight()
+self:F2()
+end
+function GROUP:GetMaxHeight()
+self:F2()
+end
+function GROUP:Respawn(Template)
+if self:IsAlive()then
+local Vec3=self:GetVec3()
+Template.x=Vec3.x
+Template.y=Vec3.z
+self:E(#Template.units)
+for UnitID,UnitData in pairs(self:GetUnits())do
+local GroupUnit=UnitData
+self:E(GroupUnit:GetName())
+if GroupUnit:IsAlive()then
+local GroupUnitVec3=GroupUnit:GetVec3()
+local GroupUnitHeading=GroupUnit:GetHeading()
+Template.units[UnitID].alt=GroupUnitVec3.y
+Template.units[UnitID].x=GroupUnitVec3.x
+Template.units[UnitID].y=GroupUnitVec3.z
+Template.units[UnitID].heading=GroupUnitHeading
+self:E({UnitID,Template.units[UnitID],Template.units[UnitID]})
+end
+end
+end
+self:Destroy()
+_DATABASE:Spawn(Template)
+self:ResetEvents()
+end
+function GROUP:GetTemplate()
+local GroupName=self:GetName()
+return UTILS.DeepCopy(_DATABASE:GetGroupTemplate(GroupName))
+end
+function GROUP:GetTemplateRoutePoints()
+local GroupName=self:GetName()
+return UTILS.DeepCopy(_DATABASE:GetGroupTemplate(GroupName).route.points)
+end
+function GROUP:SetTemplateControlled(Template,Controlled)
+Template.uncontrolled=not Controlled
+return Template
+end
+function GROUP:SetTemplateCountry(Template,CountryID)
+Template.CountryID=CountryID
+return Template
+end
+function GROUP:SetTemplateCoalition(Template,CoalitionID)
+Template.CoalitionID=CoalitionID
+return Template
+end
+function GROUP:GetTaskMission()
+self:F2(self.GroupName)
+return routines.utils.deepCopy(_DATABASE.Templates.Groups[self.GroupName].Template)
+end
+function GROUP:GetTaskRoute()
+self:F2(self.GroupName)
+return routines.utils.deepCopy(_DATABASE.Templates.Groups[self.GroupName].Template.route.points)
+end
+function GROUP:CopyRoute(Begin,End,Randomize,Radius)
+self:F2({Begin,End})
+local Points={}
+local GroupName=string.match(self:GetName(),".*#")
+if GroupName then
+GroupName=GroupName:sub(1,-2)
+else
+GroupName=self:GetName()
+end
+self:T3({GroupName})
+local Template=_DATABASE.Templates.Groups[GroupName].Template
+if Template then
+if not Begin then
+Begin=0
+end
+if not End then
+End=0
+end
+for TPointID=Begin+1,#Template.route.points-End do
+if Template.route.points[TPointID]then
+Points[#Points+1]=routines.utils.deepCopy(Template.route.points[TPointID])
+if Randomize then
+if not Radius then
+Radius=500
+end
+Points[#Points].x=Points[#Points].x+math.random(Radius*-1,Radius)
+Points[#Points].y=Points[#Points].y+math.random(Radius*-1,Radius)
+end
+end
+end
+return Points
+else
+error("Template not found for Group : "..GroupName)
+end
+return nil
+end
+function GROUP:CalculateThreatLevelA2G()
+local MaxThreatLevelA2G=0
+for UnitName,UnitData in pairs(self:GetUnits())do
+local ThreatUnit=UnitData
+local ThreatLevelA2G=ThreatUnit:GetThreatLevel()
+if ThreatLevelA2G>MaxThreatLevelA2G then
+MaxThreatLevelA2G=ThreatLevelA2G
+end
+end
+self:T3(MaxThreatLevelA2G)
+return MaxThreatLevelA2G
+end
+function GROUP:InAir()
+self:F2(self.GroupName)
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+local DCSUnit=DCSGroup:getUnit(1)
+if DCSUnit then
+local GroupInAir=DCSGroup:getUnit(1):inAir()
+self:T3(GroupInAir)
+return GroupInAir
+end
+end
+return nil
+end
+do
+function GROUP:RouteRTB(RTBAirbase,Speed)
+self:F2({RTBAirbase,Speed})
+local DCSGroup=self:GetDCSObject()
+if DCSGroup then
+if RTBAirbase then
+local GroupPoint=self:GetVec2()
+local GroupVelocity=self:GetUnit(1):GetDesc().speedMax
+local PointFrom={}
+PointFrom.x=GroupPoint.x
+PointFrom.y=GroupPoint.y
+PointFrom.type="Turning Point"
+PointFrom.action="Turning Point"
+PointFrom.speed=GroupVelocity
+local PointTo={}
+local AirbasePointVec2=RTBAirbase:GetPointVec2()
+local AirbaseAirPoint=AirbasePointVec2:WaypointAir(
+POINT_VEC3.RoutePointAltType.BARO,
+"Land",
+"Landing",
+Speed or self:GetUnit(1):GetDesc().speedMax
+)
+AirbaseAirPoint["airdromeId"]=RTBAirbase:GetID()
+AirbaseAirPoint["speed_locked"]=true,
+self:E(AirbaseAirPoint)
+local Points={PointFrom,AirbaseAirPoint}
+self:T3(Points)
+local Template=self:GetTemplate()
+Template.route.points=Points
+self:Respawn(Template)
+self:Route(Points)
+self:Respawn(Template)
+else
+self:ClearTasks()
+end
+end
+return self
+end
+end
+function GROUP:OnReSpawn(ReSpawnFunction)
+self.ReSpawnFunction=ReSpawnFunction
+end
+do
+function GROUP:HandleEvent(Event,EventFunction,...)
+self:EventDispatcher():OnEventForGroup(self:GetName(),EventFunction,self,Event,...)
+return self
+end
+function GROUP:UnHandleEvent(Event)
+self:EventDispatcher():RemoveEvent(self,Event)
+return self
+end
+function GROUP:ResetEvents()
+self:EventDispatcher():Reset(self)
+for UnitID,UnitData in pairs(self:GetUnits())do
+UnitData:ResetEvents()
+end
+return self
+end
+end
+do
+function GROUP:GetPlayerNames()
+local PlayerNames={}
+local Units=self:GetUnits()
+for UnitID,UnitData in pairs(Units)do
+local Unit=UnitData
+local PlayerName=Unit:GetPlayerName()
+if PlayerName and PlayerName~=""then
+PlayerNames=PlayerNames or{}
+table.insert(PlayerNames,PlayerName)
+end
+end
+self:F2(PlayerNames)
+return PlayerNames
+end
+end
+UNIT={
+ClassName="UNIT",
+}
+function UNIT:Register(UnitName)
+local self=BASE:Inherit(self,CONTROLLABLE:New(UnitName))
+self.UnitName=UnitName
+self:SetEventPriority(3)
+return self
+end
+function UNIT:Find(DCSUnit)
+local UnitName=DCSUnit:getName()
+local UnitFound=_DATABASE:FindUnit(UnitName)
+return UnitFound
+end
+function UNIT:FindByName(UnitName)
+local UnitFound=_DATABASE:FindUnit(UnitName)
+return UnitFound
+end
+function UNIT:Name()
+return self.UnitName
+end
+function UNIT:GetDCSObject()
+local DCSUnit=Unit.getByName(self.UnitName)
+if DCSUnit then
+return DCSUnit
+end
+return nil
+end
+function UNIT:ReSpawn(SpawnVec3,Heading)
+local SpawnGroupTemplate=UTILS.DeepCopy(_DATABASE:GetGroupTemplateFromUnitName(self:Name()))
+self:T(SpawnGroupTemplate)
+local SpawnGroup=self:GetGroup()
+if SpawnGroup then
+local Vec3=SpawnGroup:GetVec3()
+SpawnGroupTemplate.x=SpawnVec3.x
+SpawnGroupTemplate.y=SpawnVec3.z
+self:E(#SpawnGroupTemplate.units)
+for UnitID,UnitData in pairs(SpawnGroup:GetUnits())do
+local GroupUnit=UnitData
+self:E(GroupUnit:GetName())
+if GroupUnit:IsAlive()then
+local GroupUnitVec3=GroupUnit:GetVec3()
+local GroupUnitHeading=GroupUnit:GetHeading()
+SpawnGroupTemplate.units[UnitID].alt=GroupUnitVec3.y
+SpawnGroupTemplate.units[UnitID].x=GroupUnitVec3.x
+SpawnGroupTemplate.units[UnitID].y=GroupUnitVec3.z
+SpawnGroupTemplate.units[UnitID].heading=GroupUnitHeading
+self:E({UnitID,SpawnGroupTemplate.units[UnitID],SpawnGroupTemplate.units[UnitID]})
+end
+end
+end
+for UnitTemplateID,UnitTemplateData in pairs(SpawnGroupTemplate.units)do
+self:T(UnitTemplateData.name)
+if UnitTemplateData.name==self:Name()then
+self:T("Adjusting")
+SpawnGroupTemplate.units[UnitTemplateID].alt=SpawnVec3.y
+SpawnGroupTemplate.units[UnitTemplateID].x=SpawnVec3.x
+SpawnGroupTemplate.units[UnitTemplateID].y=SpawnVec3.z
+SpawnGroupTemplate.units[UnitTemplateID].heading=Heading
+self:E({UnitTemplateID,SpawnGroupTemplate.units[UnitTemplateID],SpawnGroupTemplate.units[UnitTemplateID]})
+else
+self:E(SpawnGroupTemplate.units[UnitTemplateID].name)
+local GroupUnit=UNIT:FindByName(SpawnGroupTemplate.units[UnitTemplateID].name)
+if GroupUnit and GroupUnit:IsAlive()then
+local GroupUnitVec3=GroupUnit:GetVec3()
+local GroupUnitHeading=GroupUnit:GetHeading()
+UnitTemplateData.alt=GroupUnitVec3.y
+UnitTemplateData.x=GroupUnitVec3.x
+UnitTemplateData.y=GroupUnitVec3.z
+UnitTemplateData.heading=GroupUnitHeading
+else
+if SpawnGroupTemplate.units[UnitTemplateID].name~=self:Name()then
+self:T("nilling")
+SpawnGroupTemplate.units[UnitTemplateID].delete=true
+end
+end
+end
+end
+local i=1
+while i<=#SpawnGroupTemplate.units do
+local UnitTemplateData=SpawnGroupTemplate.units[i]
+self:T(UnitTemplateData.name)
+if UnitTemplateData.delete then
+table.remove(SpawnGroupTemplate.units,i)
+else
+i=i+1
+end
+end
+_DATABASE:Spawn(SpawnGroupTemplate)
+end
+function UNIT:IsActive()
+self:F2(self.UnitName)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitIsActive=DCSUnit:isActive()
+return UnitIsActive
+end
+return nil
+end
+function UNIT:IsAlive()
+self:F3(self.UnitName)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitIsAlive=DCSUnit:isExist()and DCSUnit:isActive()
+return UnitIsAlive
+end
+return nil
+end
+function UNIT:GetCallsign()
+self:F2(self.UnitName)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitCallSign=DCSUnit:getCallsign()
+return UnitCallSign
+end
+self:E(self.ClassName.." "..self.UnitName.." not found!")
+return nil
+end
+function UNIT:GetPlayerName()
+self:F2(self.UnitName)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local PlayerName=DCSUnit:getPlayerName()
+if PlayerName==nil then
+PlayerName=""
+end
+return PlayerName
+end
+return nil
+end
+function UNIT:GetNumber()
+self:F2(self.UnitName)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitNumber=DCSUnit:getNumber()
+return UnitNumber
+end
+return nil
+end
+function UNIT:GetGroup()
+self:F2(self.UnitName)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitGroup=GROUP:Find(DCSUnit:getGroup())
+return UnitGroup
+end
+return nil
+end
+function UNIT:GetPrefix()
+self:F2(self.UnitName)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitPrefix=string.match(self.UnitName,".*#"):sub(1,-2)
+self:T3(UnitPrefix)
+return UnitPrefix
+end
+return nil
+end
+function UNIT:GetAmmo()
+self:F2(self.UnitName)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitAmmo=DCSUnit:getAmmo()
+return UnitAmmo
+end
+return nil
+end
+function UNIT:GetSensors()
+self:F2(self.UnitName)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitSensors=DCSUnit:getSensors()
+return UnitSensors
+end
+return nil
+end
+function UNIT:HasSensors(...)
+self:F2(arg)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local HasSensors=DCSUnit:hasSensors(unpack(arg))
+return HasSensors
+end
+return nil
+end
+function UNIT:HasSEAD()
+self:F2()
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitSEADAttributes=DCSUnit:getDesc().attributes
+local HasSEAD=false
+if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"]and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"]==true or
+UnitSEADAttributes["RADAR_BAND2_FOR_ARM"]and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"]==true then
+HasSEAD=true
+end
+return HasSEAD
+end
+return nil
+end
+function UNIT:GetRadar()
+self:F2(self.UnitName)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitRadarOn,UnitRadarObject=DCSUnit:getRadar()
+return UnitRadarOn,UnitRadarObject
+end
+return nil,nil
+end
+function UNIT:GetFuel()
+self:F(self.UnitName)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitFuel=DCSUnit:getFuel()
+return UnitFuel
+end
+return nil
+end
+function UNIT:GetUnits()
+self:F2({self.UnitName})
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local DCSUnits=DCSUnit:getUnits()
+local Units={}
+Units[1]=UNIT:Find(DCSUnit)
+self:T3(Units)
+return Units
+end
+return nil
+end
+function UNIT:GetLife()
+self:F2(self.UnitName)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitLife=DCSUnit:getLife()
+return UnitLife
+end
+return-1
+end
+function UNIT:GetLife0()
+self:F2(self.UnitName)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitLife0=DCSUnit:getLife0()
+return UnitLife0
+end
+return 0
+end
+function UNIT:GetCategoryName()
+self:F3(self.UnitName)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local CategoryNames={
+[Unit.Category.AIRPLANE]="Airplane",
+[Unit.Category.HELICOPTER]="Helicopter",
+[Unit.Category.GROUND_UNIT]="Ground Unit",
+[Unit.Category.SHIP]="Ship",
+[Unit.Category.STRUCTURE]="Structure",
+}
+local UnitCategory=DCSUnit:getDesc().category
+self:T3(UnitCategory)
+return CategoryNames[UnitCategory]
+end
+return nil
+end
+function UNIT:GetThreatLevel()
+local ThreatLevel=0
+local ThreatText=""
+local Descriptor=self:GetDesc()
+if Descriptor then
+local Attributes=Descriptor.attributes
+self:T(Attributes)
+if self:IsGround()then
+self:T("Ground")
+local ThreatLevels={
+"Unarmed",
+"Infantry",
+"Old Tanks & APCs",
+"Tanks & IFVs without ATGM",
+"Tanks & IFV with ATGM",
+"Modern Tanks",
+"AAA",
+"IR Guided SAMs",
+"SR SAMs",
+"MR SAMs",
+"LR SAMs"
+}
+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
+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
+elseif(Attributes["Tanks"]or Attributes["IFV"])and
+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"]then ThreatLevel=1
+end
+ThreatText=ThreatLevels[ThreatLevel+1]
+end
+if self:IsAir()then
+self:T("Air")
+local ThreatLevels={
+"Unarmed",
+"Tanker",
+"AWACS",
+"Transport Helicopter",
+"UAV",
+"Bomber",
+"Strategic Bomber",
+"Attack Helicopter",
+"Battleplane",
+"Multirole Fighter",
+"Fighter"
+}
+if Attributes["Fighters"]then ThreatLevel=10
+elseif Attributes["Multirole fighters"]then ThreatLevel=9
+elseif Attributes["Battleplanes"]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
+if self:IsShip()then
+self:T("Ship")
+local ThreatLevels={
+"Unarmed ship",
+"Light armed ships",
+"Corvettes",
+"",
+"Frigates",
+"",
+"Cruiser",
+"",
+"Destroyer",
+"",
+"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
+end
+ThreatText=ThreatLevels[ThreatLevel+1]
+end
+end
+self:T2(ThreatLevel)
+return ThreatLevel,ThreatText
+end
+function UNIT:IsInZone(Zone)
+self:F2({self.UnitName,Zone})
+if self:IsAlive()then
+local IsInZone=Zone:IsVec3InZone(self:GetVec3())
+self:T2({IsInZone})
+return IsInZone
+end
+return false
+end
+function UNIT:IsNotInZone(Zone)
+self:F2({self.UnitName,Zone})
+if self:IsAlive()then
+local IsInZone=not Zone:IsVec3InZone(self:GetVec3())
+self:T({IsInZone})
+return IsInZone
+else
+return false
+end
+end
+function UNIT:OtherUnitInRadius(AwaitUnit,Radius)
+self:F2({self.UnitName,AwaitUnit.UnitName,Radius})
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitVec3=self:GetVec3()
+local AwaitUnitVec3=AwaitUnit:GetVec3()
+if(((UnitVec3.x-AwaitUnitVec3.x)^2+(UnitVec3.z-AwaitUnitVec3.z)^2)^0.5<=Radius)then
+self:T3("true")
+return true
+else
+self:T3("false")
+return false
+end
+end
+return nil
+end
+function UNIT:IsAir()
+self:F2()
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitDescriptor=DCSUnit:getDesc()
+self:T3({UnitDescriptor.category,Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
+local IsAirResult=(UnitDescriptor.category==Unit.Category.AIRPLANE)or(UnitDescriptor.category==Unit.Category.HELICOPTER)
+self:T3(IsAirResult)
+return IsAirResult
+end
+return nil
+end
+function UNIT:IsGround()
+self:F2()
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitDescriptor=DCSUnit:getDesc()
+self:T3({UnitDescriptor.category,Unit.Category.GROUND_UNIT})
+local IsGroundResult=(UnitDescriptor.category==Unit.Category.GROUND_UNIT)
+self:T3(IsGroundResult)
+return IsGroundResult
+end
+return nil
+end
+function UNIT:IsFriendly(FriendlyCoalition)
+self:F2()
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitCoalition=DCSUnit:getCoalition()
+self:T3({UnitCoalition,FriendlyCoalition})
+local IsFriendlyResult=(UnitCoalition==FriendlyCoalition)
+self:E(IsFriendlyResult)
+return IsFriendlyResult
+end
+return nil
+end
+function UNIT:IsShip()
+self:F2()
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitDescriptor=DCSUnit:getDesc()
+self:T3({UnitDescriptor.category,Unit.Category.SHIP})
+local IsShipResult=(UnitDescriptor.category==Unit.Category.SHIP)
+self:T3(IsShipResult)
+return IsShipResult
+end
+return nil
+end
+function UNIT:InAir()
+self:F2(self.UnitName)
+local DCSUnit=self:GetDCSObject()
+if DCSUnit then
+local UnitInAir=DCSUnit:inAir()
+self:T3(UnitInAir)
+return UnitInAir
+end
+return nil
+end
+do
+function UNIT:HandleEvent(Event,EventFunction)
+self:EventDispatcher():OnEventForUnit(self:GetName(),EventFunction,self,Event)
+return self
+end
+function UNIT:UnHandleEvent(Event)
+self:EventDispatcher():RemoveForUnit(self:GetName(),self,Event)
+return self
+end
+function UNIT:ResetEvents()
+self:EventDispatcher():Reset(self)
+return self
+end
+end
+do
+function UNIT:IsDetected(TargetUnit)
+local TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity=self:IsTargetDetected(TargetUnit:GetDCSObject())
+return TargetIsDetected
+end
+function UNIT:IsLOS(TargetUnit)
+local IsLOS=self:GetPointVec3():IsLOS(TargetUnit:GetPointVec3())
+return IsLOS
+end
+end
+CLIENT={
+ONBOARDSIDE={
+NONE=0,
+LEFT=1,
+RIGHT=2,
+BACK=3,
+FRONT=4
+},
+ClassName="CLIENT",
+ClientName=nil,
+ClientAlive=false,
+ClientTransport=false,
+ClientBriefingShown=false,
+_Menus={},
+_Tasks={},
+Messages={
+}
+}
+function CLIENT:Find(DCSUnit,Error)
+local ClientName=DCSUnit:getName()
+local ClientFound=_DATABASE:FindClient(ClientName)
+if ClientFound then
+ClientFound:F(ClientName)
+return ClientFound
+end
+if not Error then
+error("CLIENT not found for: "..ClientName)
+end
+end
+function CLIENT:FindByName(ClientName,ClientBriefing,Error)
+local ClientFound=_DATABASE:FindClient(ClientName)
+if ClientFound then
+ClientFound:F({ClientName,ClientBriefing})
+ClientFound:AddBriefing(ClientBriefing)
+ClientFound.MessageSwitch=true
+return ClientFound
+end
+if not Error then
+error("CLIENT not found for: "..ClientName)
+end
+end
+function CLIENT:Register(ClientName)
+local self=BASE:Inherit(self,UNIT:Register(ClientName))
+self:F(ClientName)
+self.ClientName=ClientName
+self.MessageSwitch=true
+self.ClientAlive2=false
+self.AliveCheckScheduler=SCHEDULER:New(self,self._AliveCheckScheduler,{"Client Alive "..ClientName},1,5)
+self:E(self)
+return self
+end
+function CLIENT:Transport()
+self:F()
+self.ClientTransport=true
+return self
+end
+function CLIENT:AddBriefing(ClientBriefing)
+self:F(ClientBriefing)
+self.ClientBriefing=ClientBriefing
+self.ClientBriefingShown=false
+return self
+end
+function CLIENT:ShowBriefing()
+self:F({self.ClientName,self.ClientBriefingShown})
+if not self.ClientBriefingShown then
+self.ClientBriefingShown=true
+local Briefing=""
+if self.ClientBriefing then
+Briefing=Briefing..self.ClientBriefing
+end
+Briefing=Briefing.." Press [LEFT ALT]+[B] to view the complete mission briefing."
+self:Message(Briefing,60,"Briefing")
+end
+return self
+end
+function CLIENT:ShowMissionBriefing(MissionBriefing)
+self:F({self.ClientName})
+if MissionBriefing then
+self:Message(MissionBriefing,60,"Mission Briefing")
+end
+return self
+end
+function CLIENT:Reset(ClientName)
+self:F()
+self._Menus={}
+end
+function CLIENT:IsMultiSeated()
+self:F(self.ClientName)
+local ClientMultiSeatedTypes={
+["Mi-8MT"]="Mi-8MT",
+["UH-1H"]="UH-1H",
+["P-51B"]="P-51B"
+}
+if self:IsAlive()then
+local ClientTypeName=self:GetClientGroupUnit():GetTypeName()
+if ClientMultiSeatedTypes[ClientTypeName]then
+return true
+end
+end
+return false
+end
+function CLIENT:Alive(CallBackFunction,...)
+self:F()
+self.ClientCallBack=CallBackFunction
+self.ClientParameters=arg
+return self
+end
+function CLIENT:_AliveCheckScheduler(SchedulerName)
+self:F3({SchedulerName,self.ClientName,self.ClientAlive2,self.ClientBriefingShown,self.ClientCallBack})
+if self:IsAlive()then
+if self.ClientAlive2==false then
+self:ShowBriefing()
+if self.ClientCallBack then
+self:T("Calling Callback function")
+self.ClientCallBack(self,unpack(self.ClientParameters))
+end
+self.ClientAlive2=true
+end
+else
+if self.ClientAlive2==true then
+self.ClientAlive2=false
+end
+end
+return true
+end
+function CLIENT:GetDCSGroup()
+self:F3()
+local ClientUnit=Unit.getByName(self.ClientName)
+local CoalitionsData={AlivePlayersRed=coalition.getPlayers(coalition.side.RED),AlivePlayersBlue=coalition.getPlayers(coalition.side.BLUE)}
+for CoalitionId,CoalitionData in pairs(CoalitionsData)do
+self:T3({"CoalitionData:",CoalitionData})
+for UnitId,UnitData in pairs(CoalitionData)do
+self:T3({"UnitData:",UnitData})
+if UnitData and UnitData:isExist()then
+if ClientUnit then
+local ClientGroup=ClientUnit:getGroup()
+if ClientGroup then
+self:T3("ClientGroup = "..self.ClientName)
+if ClientGroup:isExist()and UnitData:getGroup():isExist()then
+if ClientGroup:getID()==UnitData:getGroup():getID()then
+self:T3("Normal logic")
+self:T3(self.ClientName.." : group found!")
+self.ClientGroupID=ClientGroup:getID()
+self.ClientGroupName=ClientGroup:getName()
+return ClientGroup
+end
+else
+self:T3("Bug 1.5 logic")
+local ClientGroupTemplate=_DATABASE.Templates.Units[self.ClientName].GroupTemplate
+self.ClientGroupID=ClientGroupTemplate.groupId
+self.ClientGroupName=_DATABASE.Templates.Units[self.ClientName].GroupName
+self:T3(self.ClientName.." : group found in bug 1.5 resolvement logic!")
+return ClientGroup
+end
+end
+else
+end
+end
+end
+end
+if ClientUnit then
+local ClientGroup=ClientUnit:getGroup()
+if ClientGroup then
+self:T3("ClientGroup = "..self.ClientName)
+if ClientGroup:isExist()then
+self:T3("Normal logic")
+self:T3(self.ClientName.." : group found!")
+return ClientGroup
+end
+end
+end
+self.ClientGroupID=nil
+self.ClientGroupUnit=nil
+return nil
+end
+function CLIENT:GetClientGroupID()
+local ClientGroup=self:GetDCSGroup()
+return self.ClientGroupID
+end
+function CLIENT:GetClientGroupName()
+local ClientGroup=self:GetDCSGroup()
+self:T(self.ClientGroupName)
+return self.ClientGroupName
+end
+function CLIENT:GetClientGroupUnit()
+self:F2()
+local ClientDCSUnit=Unit.getByName(self.ClientName)
+self:T(self.ClientDCSUnit)
+if ClientDCSUnit and ClientDCSUnit:isExist()then
+local ClientUnit=_DATABASE:FindUnit(self.ClientName)
+self:T2(ClientUnit)
+return ClientUnit
+end
+end
+function CLIENT:GetClientGroupDCSUnit()
+self:F2()
+local ClientDCSUnit=Unit.getByName(self.ClientName)
+if ClientDCSUnit and ClientDCSUnit:isExist()then
+self:T2(ClientDCSUnit)
+return ClientDCSUnit
+end
+end
+function CLIENT:IsTransport()
+self:F()
+return self.ClientTransport
+end
+function CLIENT:ShowCargo()
+self:F()
+local CargoMsg=""
+for CargoName,Cargo in pairs(CARGOS)do
+if self==Cargo:IsLoadedInClient()then
+CargoMsg=CargoMsg..Cargo.CargoName.." Type:"..Cargo.CargoType.." Weight: "..Cargo.CargoWeight.."\n"
+end
+end
+if CargoMsg==""then
+CargoMsg="empty"
+end
+self:Message(CargoMsg,15,"Co-Pilot: Cargo Status",30)
+end
+function CLIENT.SwitchMessages(PrmTable)
+PrmTable[1].MessageSwitch=PrmTable[2]
+end
+function CLIENT:Message(Message,MessageDuration,MessageCategory,MessageInterval,MessageID)
+self:F({Message,MessageDuration,MessageCategory,MessageInterval})
+if self.MessageSwitch==true then
+if MessageCategory==nil then
+MessageCategory="Messages"
+end
+if MessageID~=nil then
+if self.Messages[MessageID]==nil then
+self.Messages[MessageID]={}
+self.Messages[MessageID].MessageId=MessageID
+self.Messages[MessageID].MessageTime=timer.getTime()
+self.Messages[MessageID].MessageDuration=MessageDuration
+if MessageInterval==nil then
+self.Messages[MessageID].MessageInterval=600
+else
+self.Messages[MessageID].MessageInterval=MessageInterval
+end
+MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self)
+else
+if self:GetClientGroupDCSUnit()and not self:GetClientGroupDCSUnit():inAir()then
+if timer.getTime()-self.Messages[MessageID].MessageTime>=self.Messages[MessageID].MessageDuration+10 then
+MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self)
+self.Messages[MessageID].MessageTime=timer.getTime()
+end
+else
+if timer.getTime()-self.Messages[MessageID].MessageTime>=self.Messages[MessageID].MessageDuration+self.Messages[MessageID].MessageInterval then
+MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self)
+self.Messages[MessageID].MessageTime=timer.getTime()
+end
+end
+end
+else
+MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self)
+end
+end
+end
+STATIC={
+ClassName="STATIC",
+}
+function STATIC:FindByName(StaticName,RaiseError)
+local StaticFound=_DATABASE:FindStatic(StaticName)
+self.StaticName=StaticName
+if StaticFound then
+StaticFound:F3({StaticName})
+return StaticFound
+end
+if RaiseError==nil or RaiseError==true then
+error("STATIC not found for: "..StaticName)
+end
+return nil
+end
+function STATIC:Register(StaticName)
+local self=BASE:Inherit(self,POSITIONABLE:New(StaticName))
+self.StaticName=StaticName
+return self
+end
+function STATIC:GetDCSObject()
+local DCSStatic=StaticObject.getByName(self.StaticName)
+if DCSStatic then
+return DCSStatic
+end
+return nil
+end
+function STATIC:GetThreatLevel()
+return 1,"Static"
+end
+AIRBASE={
+ClassName="AIRBASE",
+CategoryName={
+[Airbase.Category.AIRDROME]="Airdrome",
+[Airbase.Category.HELIPAD]="Helipad",
+[Airbase.Category.SHIP]="Ship",
+},
+}
+AIRBASE.Caucasus={
+["Gelendzhik"]="Gelendzhik",
+["Krasnodar_Pashkovsky"]="Krasnodar-Pashkovsky",
+["Sukhumi_Babushara"]="Sukhumi-Babushara",
+["Gudauta"]="Gudauta",
+["Batumi"]="Batumi",
+["Senaki_Kolkhi"]="Senaki-Kolkhi",
+["Kobuleti"]="Kobuleti",
+["Kutaisi"]="Kutaisi",
+["Tbilisi_Lochini"]="Tbilisi-Lochini",
+["Soganlug"]="Soganlug",
+["Vaziani"]="Vaziani",
+["Anapa_Vityazevo"]="Anapa-Vityazevo",
+["Krasnodar_Center"]="Krasnodar-Center",
+["Novorossiysk"]="Novorossiysk",
+["Krymsk"]="Krymsk",
+["Maykop_Khanskaya"]="Maykop-Khanskaya",
+["Sochi_Adler"]="Sochi-Adler",
+["Mineralnye_Vody"]="Mineralnye Vody",
+["Nalchik"]="Nalchik",
+["Mozdok"]="Mozdok",
+["Beslan"]="Beslan",
+}
+AIRBASE.Nevada={
+["Creech_AFB"]="Creech AFB",
+["Groom_Lake_AFB"]="Groom Lake AFB",
+["McCarran_International_Airport"]="McCarran International Airport",
+["Nellis_AFB"]="Nellis AFB",
+["Beatty_Airport"]="Beatty Airport",
+["Boulder_City_Airport"]="Boulder City Airport",
+["Echo_Bay"]="Echo Bay",
+["Henderson_Executive_Airport"]="Henderson Executive Airport",
+["Jean_Airport"]="Jean Airport",
+["Laughlin_Airport"]="Laughlin Airport",
+["Lincoln_County"]="Lincoln County",
+["Mellan_Airstrip"]="Mellan Airstrip",
+["Mesquite"]="Mesquite",
+["Mina_Airport_3Q0"]="Mina Airport 3Q0",
+["North_Las_Vegas"]="North Las Vegas",
+["Pahute_Mesa_Airstrip"]="Pahute Mesa Airstrip",
+["Tonopah_Airport"]="Tonopah Airport",
+["Tonopah_Test_Range_Airfield"]="Tonopah Test Range Airfield",
+}
+AIRBASE.Normandy={
+["Saint_Pierre_du_Mont"]="Saint Pierre du Mont",
+["Lignerolles"]="Lignerolles",
+["Cretteville"]="Cretteville",
+["Maupertus"]="Maupertus",
+["Brucheville"]="Brucheville",
+["Meautis"]="Meautis",
+["Cricqueville_en_Bessin"]="Cricqueville-en-Bessin",
+["Lessay"]="Lessay",
+["Sainte_Laurent_sur_Mer"]="Sainte-Laurent-sur-Mer",
+["Biniville"]="Biniville",
+["Cardonville"]="Cardonville",
+["Deux_Jumeaux"]="Deux Jumeaux",
+["Chippelle"]="Chippelle",
+["Beuzeville"]="Beuzeville",
+["Azeville"]="Azeville",
+["Picauville"]="Picauville",
+["Le_Molay"]="Le Molay",
+["Longues_sur_Mer"]="Longues-sur-Mer",
+["Carpiquet"]="Carpiquet",
+["Bazenville"]="Bazenville",
+["Sainte_Croix_sur_Mer"]="Sainte-Croix-sur-Mer",
+["Beny_sur_Mer"]="Beny-sur-Mer",
+["Rucqueville"]="Rucqueville",
+["Sommervieu"]="Sommervieu",
+["Lantheuil"]="Lantheuil",
+["Evreux"]="Evreux",
+["Chailey"]="Chailey",
+["Needs_Oar_Point"]="Needs Oar Point",
+["Funtington"]="Funtington",
+["Tangmere"]="Tangmere",
+["Ford"]="Ford",
+}
+function AIRBASE:Register(AirbaseName)
+local self=BASE:Inherit(self,POSITIONABLE:New(AirbaseName))
+self.AirbaseName=AirbaseName
+self.AirbaseZone=ZONE_RADIUS:New(AirbaseName,self:GetVec2(),8000)
+return self
+end
+function AIRBASE:Find(DCSAirbase)
+local AirbaseName=DCSAirbase:getName()
+local AirbaseFound=_DATABASE:FindAirbase(AirbaseName)
+return AirbaseFound
+end
+function AIRBASE:FindByName(AirbaseName)
+local AirbaseFound=_DATABASE:FindAirbase(AirbaseName)
+return AirbaseFound
+end
+function AIRBASE:GetDCSObject()
+local DCSAirbase=Airbase.getByName(self.AirbaseName)
+if DCSAirbase then
+return DCSAirbase
+end
+return nil
+end
+function AIRBASE:GetZone()
+return self.AirbaseZone
+end
+SCENERY={
+ClassName="SCENERY",
+}
+function SCENERY:Register(SceneryName,SceneryObject)
+local self=BASE:Inherit(self,POSITIONABLE:New(SceneryName))
+self.SceneryName=SceneryName
+self.SceneryObject=SceneryObject
+return self
+end
+function SCENERY:GetDCSObject()
+return self.SceneryObject
+end
+function SCENERY:GetThreatLevel()
+return 0,"Scenery"
+end
+SCORING={
+ClassName="SCORING",
+ClassID=0,
+Players={},
+}
+local _SCORINGCoalition=
+{
+[1]="Red",
+[2]="Blue",
+}
+local _SCORINGCategory=
+{
+[Unit.Category.AIRPLANE]="Plane",
+[Unit.Category.HELICOPTER]="Helicopter",
+[Unit.Category.GROUND_UNIT]="Vehicle",
+[Unit.Category.SHIP]="Ship",
+[Unit.Category.STRUCTURE]="Structure",
+}
+function SCORING:New(GameName)
+local self=BASE:Inherit(self,BASE:New())
+if GameName then
+self.GameName=GameName
+else
+error("A game name must be given to register the scoring results")
+end
+self.ScoringObjects={}
+self.ScoringZones={}
+self:SetMessagesToAll()
+self:SetMessagesHit(true)
+self:SetMessagesDestroy(true)
+self:SetMessagesScore(true)
+self:SetMessagesZone(true)
+self:SetScaleDestroyScore(10)
+self:SetScaleDestroyPenalty(30)
+self:SetFratricide(self.ScaleDestroyPenalty*3)
+self:SetCoalitionChangePenalty(self.ScaleDestroyPenalty)
+self:SetDisplayMessagePrefix()
+self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
+self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
+self:HandleEvent(EVENTS.Hit,self._EventOnHit)
+self:HandleEvent(EVENTS.PlayerEnterUnit)
+self:HandleEvent(EVENTS.PlayerLeaveUnit)
+self:OpenCSV(GameName)
+return self
+end
+function SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix)
+self.DisplayMessagePrefix=DisplayMessagePrefix or""
+return self
+end
+function SCORING:SetScaleDestroyScore(Scale)
+self.ScaleDestroyScore=Scale
+return self
+end
+function SCORING:SetScaleDestroyPenalty(Scale)
+self.ScaleDestroyPenalty=Scale
+return self
+end
+function SCORING:AddUnitScore(ScoreUnit,Score)
+local UnitName=ScoreUnit:GetName()
+self.ScoringObjects[UnitName]=Score
+return self
+end
+function SCORING:RemoveUnitScore(ScoreUnit)
+local UnitName=ScoreUnit:GetName()
+self.ScoringObjects[UnitName]=nil
+return self
+end
+function SCORING:AddStaticScore(ScoreStatic,Score)
+local StaticName=ScoreStatic:GetName()
+self.ScoringObjects[StaticName]=Score
+return self
+end
+function SCORING:RemoveStaticScore(ScoreStatic)
+local StaticName=ScoreStatic:GetName()
+self.ScoringObjects[StaticName]=nil
+return self
+end
+function SCORING:AddScoreGroup(ScoreGroup,Score)
+local ScoreUnits=ScoreGroup:GetUnits()
+for ScoreUnitID,ScoreUnit in pairs(ScoreUnits)do
+local UnitName=ScoreUnit:GetName()
+self.ScoringObjects[UnitName]=Score
+end
+return self
+end
+function SCORING:AddZoneScore(ScoreZone,Score)
+local ZoneName=ScoreZone:GetName()
+self.ScoringZones[ZoneName]={}
+self.ScoringZones[ZoneName].ScoreZone=ScoreZone
+self.ScoringZones[ZoneName].Score=Score
+return self
+end
+function SCORING:RemoveZoneScore(ScoreZone)
+local ZoneName=ScoreZone:GetName()
+self.ScoringZones[ZoneName]=nil
+return self
+end
+function SCORING:SetMessagesHit(OnOff)
+self.MessagesHit=OnOff
+return self
+end
+function SCORING:IfMessagesHit()
+return self.MessagesHit
+end
+function SCORING:SetMessagesDestroy(OnOff)
+self.MessagesDestroy=OnOff
+return self
+end
+function SCORING:IfMessagesDestroy()
+return self.MessagesDestroy
+end
+function SCORING:SetMessagesScore(OnOff)
+self.MessagesScore=OnOff
+return self
+end
+function SCORING:IfMessagesScore()
+return self.MessagesScore
+end
+function SCORING:SetMessagesZone(OnOff)
+self.MessagesZone=OnOff
+return self
+end
+function SCORING:IfMessagesZone()
+return self.MessagesZone
+end
+function SCORING:SetMessagesToAll()
+self.MessagesAudience=1
+return self
+end
+function SCORING:IfMessagesToAll()
+return self.MessagesAudience==1
+end
+function SCORING:SetMessagesToCoalition()
+self.MessagesAudience=2
+return self
+end
+function SCORING:IfMessagesToCoalition()
+return self.MessagesAudience==2
+end
+function SCORING:SetFratricide(Fratricide)
+self.Fratricide=Fratricide
+return self
+end
+function SCORING:SetCoalitionChangePenalty(CoalitionChangePenalty)
+self.CoalitionChangePenalty=CoalitionChangePenalty
+return self
+end
+function SCORING:_AddPlayerFromUnit(UnitData)
+self:F(UnitData)
+if UnitData:IsAlive()then
+local UnitName=UnitData:GetName()
+local PlayerName=UnitData:GetPlayerName()
+local UnitDesc=UnitData:GetDesc()
+local UnitCategory=UnitDesc.category
+local UnitCoalition=UnitData:GetCoalition()
+local UnitTypeName=UnitData:GetTypeName()
+local UnitThreatLevel,UnitThreatType=UnitData:GetThreatLevel()
+self:T({PlayerName,UnitName,UnitCategory,UnitCoalition,UnitTypeName})
+if self.Players[PlayerName]==nil then
+self.Players[PlayerName]={}
+self.Players[PlayerName].Hit={}
+self.Players[PlayerName].Destroy={}
+self.Players[PlayerName].Goals={}
+self.Players[PlayerName].Mission={}
+self.Players[PlayerName].HitPlayers={}
+self.Players[PlayerName].Score=0
+self.Players[PlayerName].Penalty=0
+self.Players[PlayerName].PenaltyCoalition=0
+self.Players[PlayerName].PenaltyWarning=0
+end
+if not self.Players[PlayerName].UnitCoalition then
+self.Players[PlayerName].UnitCoalition=UnitCoalition
+else
+if self.Players[PlayerName].UnitCoalition~=UnitCoalition then
+self.Players[PlayerName].Penalty=self.Players[PlayerName].Penalty+50
+self.Players[PlayerName].PenaltyCoalition=self.Players[PlayerName].PenaltyCoalition+1
+MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' changed coalition from ".._SCORINGCoalition[self.Players[PlayerName].UnitCoalition].." to ".._SCORINGCoalition[UnitCoalition]..
+"(changed "..self.Players[PlayerName].PenaltyCoalition.." times the coalition). 50 Penalty points added.",
+MESSAGE.Type.Information
+):ToAll()
+self:ScoreCSV(PlayerName,"","COALITION_PENALTY",1,-50,self.Players[PlayerName].UnitName,_SCORINGCoalition[self.Players[PlayerName].UnitCoalition],_SCORINGCategory[self.Players[PlayerName].UnitCategory],self.Players[PlayerName].UnitType,
+UnitName,_SCORINGCoalition[UnitCoalition],_SCORINGCategory[UnitCategory],UnitData:GetTypeName())
+end
+end
+self.Players[PlayerName].UnitName=UnitName
+self.Players[PlayerName].UnitCoalition=UnitCoalition
+self.Players[PlayerName].UnitCategory=UnitCategory
+self.Players[PlayerName].UnitType=UnitTypeName
+self.Players[PlayerName].UNIT=UnitData
+self.Players[PlayerName].ThreatLevel=UnitThreatLevel
+self.Players[PlayerName].ThreatType=UnitThreatType
+if self.Players[PlayerName].Penalty>self.Fratricide*0.50 then
+if self.Players[PlayerName].PenaltyWarning<1 then
+MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than "..self.Fratricide..", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: "..self.Players[PlayerName].Penalty,
+MESSAGE.Type.Information
+):ToAll()
+self.Players[PlayerName].PenaltyWarning=self.Players[PlayerName].PenaltyWarning+1
+end
+end
+if self.Players[PlayerName].Penalty>self.Fratricide then
+MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!",
+MESSAGE.Type.Information
+):ToAll()
+UnitData:GetGroup():Destroy()
+end
+end
+end
+function SCORING:AddGoalScore(PlayerUnit,GoalTag,Text,Score)
+local PlayerName=PlayerUnit:GetPlayerName()
+self:E({PlayerUnit.UnitName,PlayerName,GoalTag,Text,Score})
+if PlayerName then
+local PlayerData=self.Players[PlayerName]
+PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0}
+PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score
+PlayerData.Score=PlayerData.Score+Score
+MESSAGE:NewType(self.DisplayMessagePrefix..Text,MESSAGE.Type.Information):ToAll()
+self:ScoreCSV(PlayerName,"","GOAL_"..string.upper(GoalTag),1,Score,PlayerUnit:GetName())
+end
+end
+function SCORING:_AddMissionTaskScore(Mission,PlayerUnit,Text,Score)
+local PlayerName=PlayerUnit:GetPlayerName()
+local MissionName=Mission:GetName()
+self:E({Mission:GetName(),PlayerUnit.UnitName,PlayerName,Text,Score})
+if PlayerName then
+local PlayerData=self.Players[PlayerName]
+if not PlayerData.Mission[MissionName]then
+PlayerData.Mission[MissionName]={}
+PlayerData.Mission[MissionName].ScoreTask=0
+PlayerData.Mission[MissionName].ScoreMission=0
+end
+self:T(PlayerName)
+self:T(PlayerData.Mission[MissionName])
+PlayerData.Score=self.Players[PlayerName].Score+Score
+PlayerData.Mission[MissionName].ScoreTask=self.Players[PlayerName].Mission[MissionName].ScoreTask+Score
+MESSAGE:NewType(self.DisplayMessagePrefix..MissionName.." : "..Text.." Score: "..Score,MESSAGE.Type.Information):ToAll()
+self:ScoreCSV(PlayerName,"","TASK_"..MissionName:gsub(' ','_'),1,Score,PlayerUnit:GetName())
+end
+end
+function SCORING:_AddMissionGoalScore(Mission,PlayerName,Text,Score)
+local MissionName=Mission:GetName()
+self:E({Mission:GetName(),PlayerName,Text,Score})
+if PlayerName then
+local PlayerData=self.Players[PlayerName]
+if not PlayerData.Mission[MissionName]then
+PlayerData.Mission[MissionName]={}
+PlayerData.Mission[MissionName].ScoreTask=0
+PlayerData.Mission[MissionName].ScoreMission=0
+end
+self:T(PlayerName)
+self:T(PlayerData.Mission[MissionName])
+PlayerData.Score=self.Players[PlayerName].Score+Score
+PlayerData.Mission[MissionName].ScoreTask=self.Players[PlayerName].Mission[MissionName].ScoreTask+Score
+MESSAGE:NewType(string.format("%s%s: %s! Player %s receives %d score!",self.DisplayMessagePrefix,MissionName,Text,PlayerName,Score),MESSAGE.Type.Information):ToAll()
+self:ScoreCSV(PlayerName,"","TASK_"..MissionName:gsub(' ','_'),1,Score)
+end
+end
+function SCORING:_AddMissionScore(Mission,Text,Score)
+local MissionName=Mission:GetName()
+self:E({Mission,Text,Score})
+self:E(self.Players)
+for PlayerName,PlayerData in pairs(self.Players)do
+self:E(PlayerData)
+if PlayerData.Mission[MissionName]then
+PlayerData.Score=PlayerData.Score+Score
+PlayerData.Mission[MissionName].ScoreMission=PlayerData.Mission[MissionName].ScoreMission+Score
+MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' has "..Text.." in Mission '"..MissionName.."'. "..
+Score.." mission score!",
+MESSAGE.Type.Information):ToAll()
+self:ScoreCSV(PlayerName,"","MISSION_"..MissionName:gsub(' ','_'),1,Score)
+end
+end
+end
+function SCORING:OnEventPlayerEnterUnit(Event)
+if Event.IniUnit then
+self:_AddPlayerFromUnit(Event.IniUnit)
+local Menu=MENU_GROUP:New(Event.IniGroup,'Scoring')
+local ReportGroupSummary=MENU_GROUP_COMMAND:New(Event.IniGroup,'Summary report players in group',Menu,SCORING.ReportScoreGroupSummary,self,Event.IniGroup)
+local ReportGroupDetailed=MENU_GROUP_COMMAND:New(Event.IniGroup,'Detailed report players in group',Menu,SCORING.ReportScoreGroupDetailed,self,Event.IniGroup)
+local ReportToAllSummary=MENU_GROUP_COMMAND:New(Event.IniGroup,'Summary report all players',Menu,SCORING.ReportScoreAllSummary,self,Event.IniGroup)
+self:SetState(Event.IniUnit,"ScoringMenu",Menu)
+end
+end
+function SCORING:OnEventPlayerLeaveUnit(Event)
+if Event.IniUnit then
+local Menu=self:GetState(Event.IniUnit,"ScoringMenu")
+if Menu then
+end
+end
+end
+function SCORING:_EventOnHit(Event)
+self:F({Event})
+local InitUnit=nil
+local InitUNIT=nil
+local InitUnitName=""
+local InitGroup=nil
+local InitGroupName=""
+local InitPlayerName=nil
+local InitCoalition=nil
+local InitCategory=nil
+local InitType=nil
+local InitUnitCoalition=nil
+local InitUnitCategory=nil
+local InitUnitType=nil
+local TargetUnit=nil
+local TargetUNIT=nil
+local TargetUnitName=""
+local TargetGroup=nil
+local TargetGroupName=""
+local TargetPlayerName=nil
+local TargetCoalition=nil
+local TargetCategory=nil
+local TargetType=nil
+local TargetUnitCoalition=nil
+local TargetUnitCategory=nil
+local TargetUnitType=nil
+if Event.IniDCSUnit then
+InitUnit=Event.IniDCSUnit
+InitUNIT=Event.IniUnit
+InitUnitName=Event.IniDCSUnitName
+InitGroup=Event.IniDCSGroup
+InitGroupName=Event.IniDCSGroupName
+InitPlayerName=Event.IniPlayerName
+InitCoalition=Event.IniCoalition
+InitCategory=Event.IniCategory
+InitType=Event.IniTypeName
+InitUnitCoalition=_SCORINGCoalition[InitCoalition]
+InitUnitCategory=_SCORINGCategory[InitCategory]
+InitUnitType=InitType
+self:T({InitUnitName,InitGroupName,InitPlayerName,InitCoalition,InitCategory,InitType,InitUnitCoalition,InitUnitCategory,InitUnitType})
+end
+if Event.TgtDCSUnit then
+TargetUnit=Event.TgtDCSUnit
+TargetUNIT=Event.TgtUnit
+TargetUnitName=Event.TgtDCSUnitName
+TargetGroup=Event.TgtDCSGroup
+TargetGroupName=Event.TgtDCSGroupName
+TargetPlayerName=Event.TgtPlayerName
+TargetCoalition=Event.TgtCoalition
+TargetCategory=Event.TgtCategory
+TargetType=Event.TgtTypeName
+TargetUnitCoalition=_SCORINGCoalition[TargetCoalition]
+TargetUnitCategory=_SCORINGCategory[TargetCategory]
+TargetUnitType=TargetType
+self:T({TargetUnitName,TargetGroupName,TargetPlayerName,TargetCoalition,TargetCategory,TargetType,TargetUnitCoalition,TargetUnitCategory,TargetUnitType})
+end
+if InitPlayerName~=nil then
+self:_AddPlayerFromUnit(InitUNIT)
+if self.Players[InitPlayerName]then
+if TargetPlayerName~=nil then
+self:_AddPlayerFromUnit(TargetUNIT)
+end
+self:T("Hitting Something")
+if TargetCategory then
+local Player=self.Players[InitPlayerName]
+Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{}
+Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{}
+local PlayerHit=Player.Hit[TargetCategory][TargetUnitName]
+PlayerHit.Score=PlayerHit.Score or 0
+PlayerHit.Penalty=PlayerHit.Penalty or 0
+PlayerHit.ScoreHit=PlayerHit.ScoreHit or 0
+PlayerHit.PenaltyHit=PlayerHit.PenaltyHit or 0
+PlayerHit.TimeStamp=PlayerHit.TimeStamp or 0
+PlayerHit.UNIT=PlayerHit.UNIT or TargetUNIT
+PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel()
+if timer.getTime()-PlayerHit.TimeStamp>1 then
+PlayerHit.TimeStamp=timer.getTime()
+if TargetPlayerName~=nil then
+Player.HitPlayers[TargetPlayerName]=true
+end
+local Score=0
+if InitCoalition then
+if InitCoalition==TargetCoalition then
+Player.Penalty=Player.Penalty+10
+PlayerHit.Penalty=PlayerHit.Penalty+10
+PlayerHit.PenaltyHit=PlayerHit.PenaltyHit+1
+if TargetPlayerName~=nil then
+MESSAGE
+:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit friendly player '"..TargetPlayerName.."' "..
+TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.PenaltyHit.." times. "..
+"Penalty: -"..PlayerHit.Penalty..". Score Total:"..Player.Score-Player.Penalty,
+MESSAGE.Type.Update
+)
+:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
+:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
+else
+MESSAGE
+:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit friendly target "..
+TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.PenaltyHit.." times. "..
+"Penalty: -"..PlayerHit.Penalty..". 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_PENALTY",1,-10,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
+else
+Player.Score=Player.Score+1
+PlayerHit.Score=PlayerHit.Score+1
+PlayerHit.ScoreHit=PlayerHit.ScoreHit+1
+if TargetPlayerName~=nil then
+MESSAGE
+:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit enemy player '"..TargetPlayerName.."' "..
+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())
+else
+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())
+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.Type.Update
+)
+:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
+:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
+self:ScoreCSV(InitPlayerName,"","HIT_SCORE",1,0,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Scenery",TargetUnitType)
+end
+end
+end
+end
+elseif InitPlayerName==nil then
+end
+if Event.WeaponPlayerName~=nil then
+self:_AddPlayerFromUnit(Event.WeaponUNIT)
+if self.Players[Event.WeaponPlayerName]then
+if TargetPlayerName~=nil then
+self:_AddPlayerFromUnit(TargetUNIT)
+end
+self:T("Hitting Scenery")
+if TargetCategory then
+local Player=self.Players[Event.WeaponPlayerName]
+Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{}
+Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{}
+local PlayerHit=Player.Hit[TargetCategory][TargetUnitName]
+PlayerHit.Score=PlayerHit.Score or 0
+PlayerHit.Penalty=PlayerHit.Penalty or 0
+PlayerHit.ScoreHit=PlayerHit.ScoreHit or 0
+PlayerHit.PenaltyHit=PlayerHit.PenaltyHit or 0
+PlayerHit.TimeStamp=PlayerHit.TimeStamp or 0
+PlayerHit.UNIT=PlayerHit.UNIT or TargetUNIT
+PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel()
+if timer.getTime()-PlayerHit.TimeStamp>1 then
+PlayerHit.TimeStamp=timer.getTime()
+local Score=0
+if InitCoalition then
+if InitCoalition==TargetCoalition then
+Player.Penalty=Player.Penalty+10
+PlayerHit.Penalty=PlayerHit.Penalty+10
+PlayerHit.PenaltyHit=PlayerHit.PenaltyHit+1
+MESSAGE
+:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit friendly target "..
+TargetUnitCategory.." ( "..TargetType.." ) "..
+"Penalty: -"..PlayerHit.Penalty.." = "..Player.Score-Player.Penalty,
+MESSAGE.Type.Update
+)
+:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
+:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
+self:ScoreCSV(Event.WeaponPlayerName,TargetPlayerName,"HIT_PENALTY",1,-10,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
+else
+Player.Score=Player.Score+1
+PlayerHit.Score=PlayerHit.Score+1
+PlayerHit.ScoreHit=PlayerHit.ScoreHit+1
+MESSAGE
+:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit enemy target "..
+TargetUnitCategory.." ( "..TargetType.." ) "..
+"Score: +"..PlayerHit.Score.." = "..Player.Score-Player.Penalty,
+MESSAGE.Type.Update
+)
+:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
+:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
+self:ScoreCSV(Event.WeaponPlayerName,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
+end
+else
+MESSAGE
+:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit scenery object.",
+MESSAGE.Type.Update
+)
+:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
+:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
+self:ScoreCSV(Event.WeaponPlayerName,"","HIT_SCORE",1,0,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,"","Scenery",TargetUnitType)
+end
+end
+end
+end
+end
+end
+function SCORING:_EventOnDeadOrCrash(Event)
+self:F({Event})
+local TargetUnit=nil
+local TargetGroup=nil
+local TargetUnitName=""
+local TargetGroupName=""
+local TargetPlayerName=""
+local TargetCoalition=nil
+local TargetCategory=nil
+local TargetType=nil
+local TargetUnitCoalition=nil
+local TargetUnitCategory=nil
+local TargetUnitType=nil
+if Event.IniDCSUnit then
+TargetUnit=Event.IniUnit
+TargetUnitName=Event.IniDCSUnitName
+TargetGroup=Event.IniDCSGroup
+TargetGroupName=Event.IniDCSGroupName
+TargetPlayerName=Event.IniPlayerName
+TargetCoalition=Event.IniCoalition
+TargetCategory=Event.IniCategory
+TargetType=Event.IniTypeName
+TargetUnitCoalition=_SCORINGCoalition[TargetCoalition]
+TargetUnitCategory=_SCORINGCategory[TargetCategory]
+TargetUnitType=TargetType
+self:T({TargetUnitName,TargetGroupName,TargetPlayerName,TargetCoalition,TargetCategory,TargetType})
+end
+for PlayerName,Player in pairs(self.Players)do
+if Player then
+self:T("Something got destroyed")
+local InitUnitName=Player.UnitName
+local InitUnitType=Player.UnitType
+local InitCoalition=Player.UnitCoalition
+local InitCategory=Player.UnitCategory
+local InitUnitCoalition=_SCORINGCoalition[InitCoalition]
+local InitUnitCategory=_SCORINGCategory[InitCategory]
+self:T({InitUnitName,InitUnitType,InitUnitCoalition,InitCoalition,InitUnitCategory,InitCategory})
+local Destroyed=false
+if Player and Player.Hit and Player.Hit[TargetCategory]and Player.Hit[TargetCategory][TargetUnitName]and Player.Hit[TargetCategory][TargetUnitName].TimeStamp~=0 then
+local TargetThreatLevel=Player.Hit[TargetCategory][TargetUnitName].ThreatLevel
+local TargetThreatType=Player.Hit[TargetCategory][TargetUnitName].ThreatType
+Player.Destroy[TargetCategory]=Player.Destroy[TargetCategory]or{}
+Player.Destroy[TargetCategory][TargetType]=Player.Destroy[TargetCategory][TargetType]or{}
+local TargetDestroy=Player.Destroy[TargetCategory][TargetType]
+TargetDestroy.Score=TargetDestroy.Score or 0
+TargetDestroy.ScoreDestroy=TargetDestroy.ScoreDestroy or 0
+TargetDestroy.Penalty=TargetDestroy.Penalty or 0
+TargetDestroy.PenaltyDestroy=TargetDestroy.PenaltyDestroy or 0
+if TargetCoalition then
+if InitCoalition==TargetCoalition then
+local ThreatLevelTarget=TargetThreatLevel
+local ThreatTypeTarget=TargetThreatType
+local ThreatLevelPlayer=Player.ThreatLevel/10+1
+local ThreatPenalty=math.ceil((ThreatLevelTarget/ThreatLevelPlayer)*self.ScaleDestroyPenalty/10)
+self:E({ThreatLevel=ThreatPenalty,ThreatLevelTarget=ThreatLevelTarget,ThreatTypeTarget=ThreatTypeTarget,ThreatLevelPlayer=ThreatLevelPlayer})
+Player.Penalty=Player.Penalty+ThreatPenalty
+TargetDestroy.Penalty=TargetDestroy.Penalty+ThreatPenalty
+TargetDestroy.PenaltyDestroy=TargetDestroy.PenaltyDestroy+1
+if Player.HitPlayers[TargetPlayerName]then
+MESSAGE
+:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed friendly player '"..TargetPlayerName.."' "..
+TargetUnitCategory.." ( "..ThreatTypeTarget.." ) "..
+"Penalty: -"..TargetDestroy.Penalty.." = "..Player.Score-Player.Penalty,
+MESSAGE.Type.Information
+)
+:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll())
+:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition())
+else
+MESSAGE
+:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed friendly target "..
+TargetUnitCategory.." ( "..ThreatTypeTarget.." ) "..
+"Penalty: -"..TargetDestroy.Penalty.." = "..Player.Score-Player.Penalty,
+MESSAGE.Type.Information
+)
+:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll())
+:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition())
+end
+Destroyed=true
+self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_PENALTY",1,ThreatPenalty,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
+else
+local ThreatLevelTarget=TargetThreatLevel
+local ThreatTypeTarget=TargetThreatType
+local ThreatLevelPlayer=Player.ThreatLevel/10+1
+local ThreatScore=math.ceil((ThreatLevelTarget/ThreatLevelPlayer)*self.ScaleDestroyScore/10)
+self:E({ThreatLevel=ThreatScore,ThreatLevelTarget=ThreatLevelTarget,ThreatTypeTarget=ThreatTypeTarget,ThreatLevelPlayer=ThreatLevelPlayer})
+Player.Score=Player.Score+ThreatScore
+TargetDestroy.Score=TargetDestroy.Score+ThreatScore
+TargetDestroy.ScoreDestroy=TargetDestroy.ScoreDestroy+1
+if Player.HitPlayers[TargetPlayerName]then
+MESSAGE
+:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed enemy player '"..TargetPlayerName.."' "..
+TargetUnitCategory.." ( "..ThreatTypeTarget.." ) "..
+"Score: +"..TargetDestroy.Score.." = "..Player.Score-Player.Penalty,
+MESSAGE.Type.Information
+)
+:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll())
+:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition())
+else
+MESSAGE
+:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed enemy "..
+TargetUnitCategory.." ( "..ThreatTypeTarget.." ) "..
+"Score: +"..TargetDestroy.Score.." = "..Player.Score-Player.Penalty,
+MESSAGE.Type.Information
+)
+:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll())
+:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition())
+end
+Destroyed=true
+self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,ThreatScore,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
+local UnitName=TargetUnit:GetName()
+local Score=self.ScoringObjects[UnitName]
+if Score then
+Player.Score=Player.Score+Score
+TargetDestroy.Score=TargetDestroy.Score+Score
+MESSAGE
+:NewType(self.DisplayMessagePrefix.."Special target '"..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".." destroyed! "..
+"Player '"..PlayerName.."' receives an extra "..Score.." points! Total: "..Player.Score-Player.Penalty,
+MESSAGE.Type.Information
+)
+:ToAllIf(self:IfMessagesScore()and self:IfMessagesToAll())
+:ToCoalitionIf(InitCoalition,self:IfMessagesScore()and self:IfMessagesToCoalition())
+self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
+Destroyed=true
+end
+for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do
+self:E({ScoringZone=ScoreZoneData})
+local ScoreZone=ScoreZoneData.ScoreZone
+local Score=ScoreZoneData.Score
+if ScoreZone:IsVec2InZone(TargetUnit:GetVec2())then
+Player.Score=Player.Score+Score
+TargetDestroy.Score=TargetDestroy.Score+Score
+MESSAGE
+:NewType(self.DisplayMessagePrefix.."Target destroyed in zone '"..ScoreZone:GetName().."'."..
+"Player '"..PlayerName.."' receives an extra "..Score.." points! "..
+"Total: "..Player.Score-Player.Penalty,
+MESSAGE.Type.Information)
+:ToAllIf(self:IfMessagesZone()and self:IfMessagesToAll())
+:ToCoalitionIf(InitCoalition,self:IfMessagesZone()and self:IfMessagesToCoalition())
+self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
+Destroyed=true
+end
+end
+end
+else
+for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do
+self:E({ScoringZone=ScoreZoneData})
+local ScoreZone=ScoreZoneData.ScoreZone
+local Score=ScoreZoneData.Score
+if ScoreZone:IsVec2InZone(TargetUnit:GetVec2())then
+Player.Score=Player.Score+Score
+TargetDestroy.Score=TargetDestroy.Score+Score
+MESSAGE
+:NewType(self.DisplayMessagePrefix.."Scenery destroyed in zone '"..ScoreZone:GetName().."'."..
+"Player '"..PlayerName.."' receives an extra "..Score.." points! "..
+"Total: "..Player.Score-Player.Penalty,
+MESSAGE.Type.Information
+)
+:ToAllIf(self:IfMessagesZone()and self:IfMessagesToAll())
+:ToCoalitionIf(InitCoalition,self:IfMessagesZone()and self:IfMessagesToCoalition())
+Destroyed=true
+self:ScoreCSV(PlayerName,"","DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Scenery",TargetUnitType)
+end
+end
+end
+if Destroyed then
+Player.Hit[TargetCategory][TargetUnitName].TimeStamp=0
+end
+end
+end
+end
+end
+function SCORING:ReportDetailedPlayerHits(PlayerName)
+local ScoreMessage=""
+local PlayerScore=0
+local PlayerPenalty=0
+local PlayerData=self.Players[PlayerName]
+if PlayerData then
+self:T("Score Player: "..PlayerName)
+local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition]
+local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory]
+local InitUnitType=PlayerData.UnitType
+local InitUnitName=PlayerData.UnitName
+local ScoreMessageHits=""
+for CategoryID,CategoryName in pairs(_SCORINGCategory)do
+self:T(CategoryName)
+if PlayerData.Hit[CategoryID]then
+self:T("Hit scores exist for player "..PlayerName)
+local Score=0
+local ScoreHit=0
+local Penalty=0
+local PenaltyHit=0
+for UnitName,UnitData in pairs(PlayerData.Hit[CategoryID])do
+Score=Score+UnitData.Score
+ScoreHit=ScoreHit+UnitData.ScoreHit
+Penalty=Penalty+UnitData.Penalty
+PenaltyHit=UnitData.PenaltyHit
+end
+local ScoreMessageHit=string.format("%s:%d ",CategoryName,Score-Penalty)
+self:T(ScoreMessageHit)
+ScoreMessageHits=ScoreMessageHits..ScoreMessageHit
+PlayerScore=PlayerScore+Score
+PlayerPenalty=PlayerPenalty+Penalty
+else
+end
+end
+if ScoreMessageHits~=""then
+ScoreMessage="Hits: "..ScoreMessageHits
+end
+end
+return ScoreMessage,PlayerScore,PlayerPenalty
+end
+function SCORING:ReportDetailedPlayerDestroys(PlayerName)
+local ScoreMessage=""
+local PlayerScore=0
+local PlayerPenalty=0
+local PlayerData=self.Players[PlayerName]
+if PlayerData then
+self:T("Score Player: "..PlayerName)
+local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition]
+local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory]
+local InitUnitType=PlayerData.UnitType
+local InitUnitName=PlayerData.UnitName
+local ScoreMessageDestroys=""
+for CategoryID,CategoryName in pairs(_SCORINGCategory)do
+if PlayerData.Destroy[CategoryID]then
+self:T("Destroy scores exist for player "..PlayerName)
+local Score=0
+local ScoreDestroy=0
+local Penalty=0
+local PenaltyDestroy=0
+for UnitName,UnitData in pairs(PlayerData.Destroy[CategoryID])do
+self:E({UnitData=UnitData})
+if UnitData~={}then
+Score=Score+UnitData.Score
+ScoreDestroy=ScoreDestroy+UnitData.ScoreDestroy
+Penalty=Penalty+UnitData.Penalty
+PenaltyDestroy=PenaltyDestroy+UnitData.PenaltyDestroy
+end
+end
+local ScoreMessageDestroy=string.format(" %s:%d ",CategoryName,Score-Penalty)
+self:T(ScoreMessageDestroy)
+ScoreMessageDestroys=ScoreMessageDestroys..ScoreMessageDestroy
+PlayerScore=PlayerScore+Score
+PlayerPenalty=PlayerPenalty+Penalty
+else
+end
+end
+if ScoreMessageDestroys~=""then
+ScoreMessage="Destroys: "..ScoreMessageDestroys
+end
+end
+return ScoreMessage,PlayerScore,PlayerPenalty
+end
+function SCORING:ReportDetailedPlayerCoalitionChanges(PlayerName)
+local ScoreMessage=""
+local PlayerScore=0
+local PlayerPenalty=0
+local PlayerData=self.Players[PlayerName]
+if PlayerData then
+self:T("Score Player: "..PlayerName)
+local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition]
+local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory]
+local InitUnitType=PlayerData.UnitType
+local InitUnitName=PlayerData.UnitName
+local ScoreMessageCoalitionChangePenalties=""
+if PlayerData.PenaltyCoalition~=0 then
+ScoreMessageCoalitionChangePenalties=ScoreMessageCoalitionChangePenalties..string.format(" -%d (%d changed)",PlayerData.Penalty,PlayerData.PenaltyCoalition)
+PlayerPenalty=PlayerPenalty+PlayerData.Penalty
+end
+if ScoreMessageCoalitionChangePenalties~=""then
+ScoreMessage=ScoreMessage.."Coalition Penalties: "..ScoreMessageCoalitionChangePenalties
+end
+end
+return ScoreMessage,PlayerScore,PlayerPenalty
+end
+function SCORING:ReportDetailedPlayerGoals(PlayerName)
+local ScoreMessage=""
+local PlayerScore=0
+local PlayerPenalty=0
+local PlayerData=self.Players[PlayerName]
+if PlayerData then
+self:T("Score Player: "..PlayerName)
+local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition]
+local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory]
+local InitUnitType=PlayerData.UnitType
+local InitUnitName=PlayerData.UnitName
+local ScoreMessageGoal=""
+local ScoreGoal=0
+local ScoreTask=0
+for GoalName,GoalData in pairs(PlayerData.Goals)do
+ScoreGoal=ScoreGoal+GoalData.Score
+ScoreMessageGoal=ScoreMessageGoal.."'"..GoalName.."':"..GoalData.Score.."; "
+end
+PlayerScore=PlayerScore+ScoreGoal
+if ScoreMessageGoal~=""then
+ScoreMessage="Goals: "..ScoreMessageGoal
+end
+end
+return ScoreMessage,PlayerScore,PlayerPenalty
+end
+function SCORING:ReportDetailedPlayerMissions(PlayerName)
+local ScoreMessage=""
+local PlayerScore=0
+local PlayerPenalty=0
+local PlayerData=self.Players[PlayerName]
+if PlayerData then
+self:T("Score Player: "..PlayerName)
+local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition]
+local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory]
+local InitUnitType=PlayerData.UnitType
+local InitUnitName=PlayerData.UnitName
+local ScoreMessageMission=""
+local ScoreMission=0
+local ScoreTask=0
+for MissionName,MissionData in pairs(PlayerData.Mission)do
+ScoreMission=ScoreMission+MissionData.ScoreMission
+ScoreTask=ScoreTask+MissionData.ScoreTask
+ScoreMessageMission=ScoreMessageMission.."'"..MissionName.."'; "
+end
+PlayerScore=PlayerScore+ScoreMission+ScoreTask
+if ScoreMessageMission~=""then
+ScoreMessage="Tasks: "..ScoreTask.." Mission: "..ScoreMission.." ( "..ScoreMessageMission..")"
+end
+end
+return ScoreMessage,PlayerScore,PlayerPenalty
+end
+function SCORING:ReportScoreGroupSummary(PlayerGroup)
+local PlayerMessage=""
+self:T("Report Score Group Summary")
+local PlayerUnits=PlayerGroup:GetUnits()
+for UnitID,PlayerUnit in pairs(PlayerUnits)do
+local PlayerUnit=PlayerUnit
+local PlayerName=PlayerUnit:GetPlayerName()
+if PlayerName then
+local ReportHits,ScoreHits,PenaltyHits=self:ReportDetailedPlayerHits(PlayerName)
+ReportHits=ReportHits~=""and"\n- "..ReportHits or ReportHits
+self:E({ReportHits,ScoreHits,PenaltyHits})
+local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName)
+ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys
+self:E({ReportDestroys,ScoreDestroys,PenaltyDestroys})
+local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName)
+ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges
+self:E({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges})
+local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName)
+ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals
+self:E({ReportGoals,ScoreGoals,PenaltyGoals})
+local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName)
+ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions
+self:E({ReportMissions,ScoreMissions,PenaltyMissions})
+local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions
+local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+ScoreGoals+PenaltyMissions
+PlayerMessage=
+string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )",
+PlayerName,
+PlayerScore-PlayerPenalty,
+PlayerScore,
+PlayerPenalty
+)
+MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup)
+end
+end
+end
+function SCORING:ReportScoreGroupDetailed(PlayerGroup)
+local PlayerMessage=""
+self:T("Report Score Group Detailed")
+local PlayerUnits=PlayerGroup:GetUnits()
+for UnitID,PlayerUnit in pairs(PlayerUnits)do
+local PlayerUnit=PlayerUnit
+local PlayerName=PlayerUnit:GetPlayerName()
+if PlayerName then
+local ReportHits,ScoreHits,PenaltyHits=self:ReportDetailedPlayerHits(PlayerName)
+ReportHits=ReportHits~=""and"\n- "..ReportHits or ReportHits
+self:E({ReportHits,ScoreHits,PenaltyHits})
+local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName)
+ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys
+self:E({ReportDestroys,ScoreDestroys,PenaltyDestroys})
+local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName)
+ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges
+self:E({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges})
+local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName)
+ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals
+self:E({ReportGoals,ScoreGoals,PenaltyGoals})
+local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName)
+ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions
+self:E({ReportMissions,ScoreMissions,PenaltyMissions})
+local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions
+local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+ScoreGoals+PenaltyMissions
+PlayerMessage=
+string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )%s%s%s%s%s",
+PlayerName,
+PlayerScore-PlayerPenalty,
+PlayerScore,
+PlayerPenalty,
+ReportHits,
+ReportDestroys,
+ReportCoalitionChanges,
+ReportGoals,
+ReportMissions
+)
+MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup)
+end
+end
+end
+function SCORING:ReportScoreAllSummary(PlayerGroup)
+local PlayerMessage=""
+self:T("Report Score All Players")
+for PlayerName,PlayerData in pairs(self.Players)do
+if PlayerName then
+local ReportHits,ScoreHits,PenaltyHits=self:ReportDetailedPlayerHits(PlayerName)
+ReportHits=ReportHits~=""and"\n- "..ReportHits or ReportHits
+self:E({ReportHits,ScoreHits,PenaltyHits})
+local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName)
+ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys
+self:E({ReportDestroys,ScoreDestroys,PenaltyDestroys})
+local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName)
+ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges
+self:E({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges})
+local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName)
+ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals
+self:E({ReportGoals,ScoreGoals,PenaltyGoals})
+local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName)
+ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions
+self:E({ReportMissions,ScoreMissions,PenaltyMissions})
+local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions
+local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+ScoreGoals+PenaltyMissions
+PlayerMessage=
+string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )",
+PlayerName,
+PlayerScore-PlayerPenalty,
+PlayerScore,
+PlayerPenalty
+)
+MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Overview):ToGroup(PlayerGroup)
+end
+end
+end
+function SCORING:SecondsToClock(sSeconds)
+local nSeconds=sSeconds
+if nSeconds==0 then
+return"00:00:00";
+else
+nHours=string.format("%02.f",math.floor(nSeconds/3600));
+nMins=string.format("%02.f",math.floor(nSeconds/60-(nHours*60)));
+nSecs=string.format("%02.f",math.floor(nSeconds-nHours*3600-nMins*60));
+return nHours..":"..nMins..":"..nSecs
+end
+end
+function SCORING:OpenCSV(ScoringCSV)
+self:F(ScoringCSV)
+if lfs and io and os then
+if ScoringCSV then
+self.ScoringCSV=ScoringCSV
+local fdir=lfs.writedir()..[[Logs\]]..self.ScoringCSV.." "..os.date("%Y-%m-%d %H-%M-%S")..".csv"
+self.CSVFile,self.err=io.open(fdir,"w+")
+if not self.CSVFile then
+error("Error: Cannot open CSV file in "..lfs.writedir())
+end
+self.CSVFile:write('"GameName","RunTime","Time","PlayerName","TargetPlayerName","ScoreType","PlayerUnitCoaltion","PlayerUnitCategory","PlayerUnitType","PlayerUnitName","TargetUnitCoalition","TargetUnitCategory","TargetUnitType","TargetUnitName","Times","Score"\n')
+self.RunTime=os.date("%y-%m-%d_%H-%M-%S")
+else
+error("A string containing the CSV file name must be given.")
+end
+else
+self:E("The MissionScripting.lua file has not been changed to allow lfs, io and os modules to be used...")
+end
+return self
+end
+function SCORING:ScoreCSV(PlayerName,TargetPlayerName,ScoreType,ScoreTimes,ScoreAmount,PlayerUnitName,PlayerUnitCoalition,PlayerUnitCategory,PlayerUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
+local ScoreTime=self:SecondsToClock(timer.getTime())
+PlayerName=PlayerName:gsub('"','_')
+TargetPlayerName=TargetPlayerName or""
+TargetPlayerName=TargetPlayerName:gsub('"','_')
+if PlayerUnitName and PlayerUnitName~=''then
+local PlayerUnit=Unit.getByName(PlayerUnitName)
+if PlayerUnit then
+if not PlayerUnitCategory then
+PlayerUnitCategory=_SCORINGCategory[PlayerUnit:getDesc().category]
+end
+if not PlayerUnitCoalition then
+PlayerUnitCoalition=_SCORINGCoalition[PlayerUnit:getCoalition()]
+end
+if not PlayerUnitType then
+PlayerUnitType=PlayerUnit:getTypeName()
+end
+else
+PlayerUnitName=''
+PlayerUnitCategory=''
+PlayerUnitCoalition=''
+PlayerUnitType=''
+end
+else
+PlayerUnitName=''
+PlayerUnitCategory=''
+PlayerUnitCoalition=''
+PlayerUnitType=''
+end
+TargetUnitCoalition=TargetUnitCoalition or""
+TargetUnitCategory=TargetUnitCategory or""
+TargetUnitType=TargetUnitType or""
+TargetUnitName=TargetUnitName or""
+if lfs and io and os then
+self.CSVFile:write(
+'"'..self.GameName..'"'..','..
+'"'..self.RunTime..'"'..','..
+''..ScoreTime..''..','..
+'"'..PlayerName..'"'..','..
+'"'..TargetPlayerName..'"'..','..
+'"'..ScoreType..'"'..','..
+'"'..PlayerUnitCoalition..'"'..','..
+'"'..PlayerUnitCategory..'"'..','..
+'"'..PlayerUnitType..'"'..','..
+'"'..PlayerUnitName..'"'..','..
+'"'..TargetUnitCoalition..'"'..','..
+'"'..TargetUnitCategory..'"'..','..
+'"'..TargetUnitType..'"'..','..
+'"'..TargetUnitName..'"'..','..
+''..ScoreTimes..''..','..
+''..ScoreAmount
+)
+self.CSVFile:write("\n")
+end
+end
+function SCORING:CloseCSV()
+if lfs and io and os then
+self.CSVFile:close()
+end
+end
+CLEANUP_AIRBASE={
+ClassName="CLEANUP_AIRBASE",
+TimeInterval=0.2,
+CleanUpList={},
+}
+CLEANUP_AIRBASE.__={}
+CLEANUP_AIRBASE.__.Airbases={}
+function CLEANUP_AIRBASE:New(AirbaseNames)
+local self=BASE:Inherit(self,BASE:New())
+self:F({AirbaseNames})
+if type(AirbaseNames)=='table'then
+for AirbaseID,AirbaseName in pairs(AirbaseNames)do
+self:AddAirbase(AirbaseName)
+end
+else
+local AirbaseName=AirbaseNames
+self:AddAirbase(AirbaseName)
+end
+self:HandleEvent(EVENTS.Birth,self.__.OnEventBirth)
+self.__.CleanUpScheduler=SCHEDULER:New(self,self.__.CleanUpSchedule,{},1,self.TimeInterval)
+self:HandleEvent(EVENTS.EngineShutdown,self.__.EventAddForCleanUp)
+self:HandleEvent(EVENTS.EngineStartup,self.__.EventAddForCleanUp)
+self:HandleEvent(EVENTS.Hit,self.__.EventAddForCleanUp)
+self:HandleEvent(EVENTS.PilotDead,self.__.OnEventCrash)
+self:HandleEvent(EVENTS.Dead,self.__.OnEventCrash)
+self:HandleEvent(EVENTS.Crash,self.__.OnEventCrash)
+return self
+end
+function CLEANUP_AIRBASE:AddAirbase(AirbaseName)
+self.__.Airbases[AirbaseName]=AIRBASE:FindByName(AirbaseName)
+self:F({"Airbase:",AirbaseName,self.__.Airbases[AirbaseName]:GetDesc()})
+return self
+end
+function CLEANUP_AIRBASE:RemoveAirbase(AirbaseName)
+self.__.Airbases[AirbaseName]=nil
+return self
+end
+function CLEANUP_AIRBASE:SetCleanMissiles(CleanMissiles)
+if CleanMissiles then
+self:HandleEvent(EVENTS.Shot,self.__.OnEventShot)
+else
+self:UnHandleEvent(EVENTS.Shot)
+end
+end
+function CLEANUP_AIRBASE.__:IsInAirbase(Vec2)
+local InAirbase=false
+for AirbaseName,Airbase in pairs(self.__.Airbases)do
+local Airbase=Airbase
+if Airbase:GetZone():IsVec2InZone(Vec2)then
+InAirbase=true
+break;
+end
+end
+return InAirbase
+end
+function CLEANUP_AIRBASE.__:DestroyUnit(CleanUpUnit)
+self:F({CleanUpUnit})
+if CleanUpUnit then
+local CleanUpUnitName=CleanUpUnit:GetName()
+local CleanUpGroup=CleanUpUnit:GetGroup()
+if CleanUpGroup:IsAlive()then
+local CleanUpGroupUnits=CleanUpGroup:GetUnits()
+if#CleanUpGroupUnits==1 then
+local CleanUpGroupName=CleanUpGroup:GetName()
+CleanUpGroup:Destroy()
+else
+CleanUpUnit:Destroy()
+end
+self.CleanUpList[CleanUpUnitName]=nil
+end
+end
+end
+function CLEANUP_AIRBASE.__:DestroyMissile(MissileObject)
+self:F({MissileObject})
+if MissileObject and MissileObject:isExist()then
+MissileObject:destroy()
+self:T("MissileObject Destroyed")
+end
+end
+function CLEANUP_AIRBASE.__:OnEventBirth(EventData)
+self:F({EventData})
+self.CleanUpList[EventData.IniDCSUnitName]={}
+self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnit=EventData.IniUnit
+self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroup=EventData.IniGroup
+self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroupName=EventData.IniDCSGroupName
+self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnitName=EventData.IniDCSUnitName
+end
+function CLEANUP_AIRBASE.__:OnEventCrash(Event)
+self:F({Event})
+if Event.IniDCSUnitName and Event.IniCategory==Object.Category.UNIT then
+self.CleanUpList[Event.IniDCSUnitName]={}
+self.CleanUpList[Event.IniDCSUnitName].CleanUpUnit=Event.IniUnit
+self.CleanUpList[Event.IniDCSUnitName].CleanUpGroup=Event.IniGroup
+self.CleanUpList[Event.IniDCSUnitName].CleanUpGroupName=Event.IniDCSGroupName
+self.CleanUpList[Event.IniDCSUnitName].CleanUpUnitName=Event.IniDCSUnitName
+end
+end
+function CLEANUP_AIRBASE.__:OnEventShot(Event)
+self:F({Event})
+if self:IsInAirbase(Event.IniUnit:GetVec2())then
+self:DestroyMissile(Event.Weapon)
+end
+end
+function CLEANUP_AIRBASE.__:OnEventHit(Event)
+self:F({Event})
+if Event.IniUnit then
+if self:IsInAirbase(Event.IniUnit:GetVec2())then
+self:T({"Life: ",Event.IniDCSUnitName,' = ',Event.IniUnit:GetLife(),"/",Event.IniUnit:GetLife0()})
+if Event.IniUnit:GetLife()=SpawnWidth then
+SpawnXIndex=0
+SpawnYIndex=SpawnYIndex+1
+end
+end
+local SpawnRootX=self.SpawnGroups[SpawnGroupID].SpawnTemplate.x
+local SpawnRootY=self.SpawnGroups[SpawnGroupID].SpawnTemplate.y
+self:_TranslateRotate(SpawnGroupID,SpawnRootX,SpawnRootY,SpawnX,SpawnY,SpawnAngle)
+self.SpawnGroups[SpawnGroupID].SpawnTemplate.lateActivation=true
+self.SpawnGroups[SpawnGroupID].SpawnTemplate.visible=true
+self.SpawnGroups[SpawnGroupID].Visible=true
+self:HandleEvent(EVENTS.Birth,self._OnBirth)
+self:HandleEvent(EVENTS.Dead,self._OnDeadOrCrash)
+self:HandleEvent(EVENTS.Crash,self._OnDeadOrCrash)
+if self.Repeat then
+self:HandleEvent(EVENTS.Takeoff,self._OnTakeOff)
+self:HandleEvent(EVENTS.Land,self._OnLand)
+end
+if self.RepeatOnEngineShutDown then
+self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutDown)
+end
+self.SpawnGroups[SpawnGroupID].Group=_DATABASE:Spawn(self.SpawnGroups[SpawnGroupID].SpawnTemplate)
+SpawnX=SpawnXIndex*SpawnDeltaX
+SpawnY=SpawnYIndex*SpawnDeltaY
+end
+return self
+end
+do
+function SPAWN:InitAIOnOff(AIOnOff)
+self.AIOnOff=AIOnOff
+return self
+end
+function SPAWN:InitAIOn()
+return self:InitAIOnOff(true)
+end
+function SPAWN:InitAIOff()
+return self:InitAIOnOff(false)
+end
+end
+do
+function SPAWN:InitDelayOnOff(DelayOnOff)
+self.DelayOnOff=DelayOnOff
+return self
+end
+function SPAWN:InitDelayOn()
+return self:InitDelayOnOff(true)
+end
+function SPAWN:InitDelayOff()
+return self:InitDelayOnOff(false)
+end
+end
+function SPAWN:Spawn()
+self:F({self.SpawnTemplatePrefix,self.SpawnIndex,self.AliveUnits})
+return self:SpawnWithIndex(self.SpawnIndex+1)
+end
+function SPAWN:ReSpawn(SpawnIndex)
+self:F({self.SpawnTemplatePrefix,SpawnIndex})
+if not SpawnIndex then
+SpawnIndex=1
+end
+local SpawnGroup=self:GetGroupFromIndex(SpawnIndex)
+local WayPoints=SpawnGroup and SpawnGroup.WayPoints or nil
+if SpawnGroup then
+local SpawnDCSGroup=SpawnGroup:GetDCSObject()
+if SpawnDCSGroup then
+SpawnGroup:Destroy()
+end
+end
+local SpawnGroup=self:SpawnWithIndex(SpawnIndex)
+if SpawnGroup and WayPoints then
+SpawnGroup:WayPointInitialize(WayPoints)
+SpawnGroup:WayPointExecute(1,5)
+end
+if SpawnGroup.ReSpawnFunction then
+SpawnGroup:ReSpawnFunction()
+end
+SpawnGroup:ResetEvents()
+return SpawnGroup
+end
+function SPAWN:SpawnWithIndex(SpawnIndex)
+self:F2({SpawnTemplatePrefix=self.SpawnTemplatePrefix,SpawnIndex=SpawnIndex,AliveUnits=self.AliveUnits,SpawnMaxGroups=self.SpawnMaxGroups})
+if self:_GetSpawnIndex(SpawnIndex)then
+if self.SpawnGroups[self.SpawnIndex].Visible then
+self.SpawnGroups[self.SpawnIndex].Group:Activate()
+else
+local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate
+self:T(SpawnTemplate.name)
+if SpawnTemplate then
+local PointVec3=POINT_VEC3:New(SpawnTemplate.route.points[1].x,SpawnTemplate.route.points[1].alt,SpawnTemplate.route.points[1].y)
+self:T({"Current point of ",self.SpawnTemplatePrefix,PointVec3})
+if self.SpawnRandomizePosition then
+local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnRandomizePositionOuterRadius,self.SpawnRandomizePositionInnerRadius)
+local CurrentX=SpawnTemplate.units[1].x
+local CurrentY=SpawnTemplate.units[1].y
+SpawnTemplate.x=RandomVec2.x
+SpawnTemplate.y=RandomVec2.y
+for UnitID=1,#SpawnTemplate.units do
+SpawnTemplate.units[UnitID].x=SpawnTemplate.units[UnitID].x+(RandomVec2.x-CurrentX)
+SpawnTemplate.units[UnitID].y=SpawnTemplate.units[UnitID].y+(RandomVec2.y-CurrentY)
+self:T('SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
+end
+end
+if self.SpawnRandomizeUnits then
+for UnitID=1,#SpawnTemplate.units do
+local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius)
+SpawnTemplate.units[UnitID].x=RandomVec2.x
+SpawnTemplate.units[UnitID].y=RandomVec2.y
+self:T('SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
+end
+end
+if SpawnTemplate.CategoryID==Group.Category.HELICOPTER or SpawnTemplate.CategoryID==Group.Category.AIRPLANE then
+if SpawnTemplate.route.points[1].type=="TakeOffParking"then
+SpawnTemplate.uncontrolled=self.SpawnUnControlled
+end
+end
+end
+self:HandleEvent(EVENTS.Birth,self._OnBirth)
+self:HandleEvent(EVENTS.Dead,self._OnDeadOrCrash)
+self:HandleEvent(EVENTS.Crash,self._OnDeadOrCrash)
+if self.Repeat then
+self:HandleEvent(EVENTS.Takeoff,self._OnTakeOff)
+self:HandleEvent(EVENTS.Land,self._OnLand)
+end
+if self.RepeatOnEngineShutDown then
+self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutDown)
+end
+self.SpawnGroups[self.SpawnIndex].Group=_DATABASE:Spawn(SpawnTemplate)
+local SpawnGroup=self.SpawnGroups[self.SpawnIndex].Group
+if SpawnGroup then
+SpawnGroup:SetAIOnOff(self.AIOnOff)
+end
+self:T3(SpawnTemplate.name)
+if self.SpawnFunctionHook then
+self.SpawnHookScheduler=SCHEDULER:New()
+self.SpawnHookScheduler:Schedule(nil,self.SpawnFunctionHook,{self.SpawnGroups[self.SpawnIndex].Group,unpack(self.SpawnFunctionArguments)},0.1)
+end
+end
+self.SpawnGroups[self.SpawnIndex].Spawned=true
+return self.SpawnGroups[self.SpawnIndex].Group
+else
+end
+return nil
+end
+function SPAWN:SpawnScheduled(SpawnTime,SpawnTimeVariation)
+self:F({SpawnTime,SpawnTimeVariation})
+if SpawnTime~=nil and SpawnTimeVariation~=nil then
+local InitialDelay=0
+if self.DelayOnOff==true then
+InitialDelay=math.random(SpawnTime-SpawnTime*SpawnTimeVariation,SpawnTime+SpawnTime*SpawnTimeVariation)
+end
+self.SpawnScheduler=SCHEDULER:New(self,self._Scheduler,{},InitialDelay,SpawnTime,SpawnTimeVariation)
+end
+return self
+end
+function SPAWN:SpawnScheduleStart()
+self:F({self.SpawnTemplatePrefix})
+self.SpawnScheduler:Start()
+return self
+end
+function SPAWN:SpawnScheduleStop()
+self:F({self.SpawnTemplatePrefix})
+self.SpawnScheduler:Stop()
+return self
+end
+function SPAWN:OnSpawnGroup(SpawnCallBackFunction,...)
+self:F("OnSpawnGroup")
+self.SpawnFunctionHook=SpawnCallBackFunction
+self.SpawnFunctionArguments={}
+if arg then
+self.SpawnFunctionArguments=arg
+end
+return self
+end
+function SPAWN:SpawnAtAirbase(SpawnAirbase,Takeoff,TakeoffAltitude)
+self:E({self.SpawnTemplatePrefix,SpawnAirbase,Takeoff,TakeoffAltitude})
+local PointVec3=SpawnAirbase:GetPointVec3()
+self:T2(PointVec3)
+Takeoff=Takeoff or SPAWN.Takeoff.Hot
+if self:_GetSpawnIndex(self.SpawnIndex+1)then
+local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate
+if SpawnTemplate then
+self:T({"Current point of ",self.SpawnTemplatePrefix,SpawnAirbase})
+local SpawnPoint=SpawnTemplate.route.points[1]
+SpawnPoint.linkUnit=nil
+SpawnPoint.helipadId=nil
+SpawnPoint.airdromeId=nil
+local AirbaseID=SpawnAirbase:GetID()
+local AirbaseCategory=SpawnAirbase:GetDesc().category
+self:F({AirbaseCategory=AirbaseCategory})
+if AirbaseCategory==Airbase.Category.SHIP then
+SpawnPoint.linkUnit=AirbaseID
+SpawnPoint.helipadId=AirbaseID
+elseif AirbaseCategory==Airbase.Category.HELIPAD then
+SpawnPoint.linkUnit=AirbaseID
+SpawnPoint.helipadId=AirbaseID
+elseif AirbaseCategory==Airbase.Category.AIRDROME then
+SpawnPoint.airdromeId=AirbaseID
+end
+SpawnPoint.alt=0
+SpawnPoint.type=GROUPTEMPLATE.Takeoff[Takeoff][1]
+SpawnPoint.action=GROUPTEMPLATE.Takeoff[Takeoff][2]
+for UnitID=1,#SpawnTemplate.units do
+self:T('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
+local UnitTemplate=SpawnTemplate.units[UnitID]
+UnitTemplate.parking=nil
+UnitTemplate.parking_id=nil
+UnitTemplate.alt=0
+local SX=UnitTemplate.x
+local SY=UnitTemplate.y
+local BX=SpawnPoint.x
+local BY=SpawnPoint.y
+local TX=PointVec3.x+(SX-BX)
+local TY=PointVec3.z+(SY-BY)
+UnitTemplate.x=TX
+UnitTemplate.y=TY
+if Takeoff==GROUP.Takeoff.Air then
+UnitTemplate.alt=PointVec3.y+(TakeoffAltitude or 200)
+end
+self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..UnitTemplate.x..', SpawnTemplate.units['..UnitID..'].y = '..UnitTemplate.y)
+end
+SpawnPoint.x=PointVec3.x
+SpawnPoint.y=PointVec3.z
+if Takeoff==GROUP.Takeoff.Air then
+SpawnPoint.alt=PointVec3.y+(TakeoffAltitude or 200)
+end
+SpawnTemplate.x=PointVec3.x
+SpawnTemplate.y=PointVec3.z
+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()},1)
+end
+end
+return GroupSpawned
+end
+end
+return nil
+end
+function SPAWN:SpawnFromVec3(Vec3,SpawnIndex)
+self:F({self.SpawnTemplatePrefix,Vec3,SpawnIndex})
+local PointVec3=POINT_VEC3:NewFromVec3(Vec3)
+self:T2(PointVec3)
+if SpawnIndex then
+else
+SpawnIndex=self.SpawnIndex+1
+end
+if self:_GetSpawnIndex(SpawnIndex)then
+local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate
+if SpawnTemplate then
+self:T({"Current point of ",self.SpawnTemplatePrefix,Vec3})
+for UnitID=1,#SpawnTemplate.units do
+self:T('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
+local UnitTemplate=SpawnTemplate.units[UnitID]
+local SX=UnitTemplate.x
+local SY=UnitTemplate.y
+local BX=SpawnTemplate.route.points[1].x
+local BY=SpawnTemplate.route.points[1].y
+local TX=Vec3.x+(SX-BX)
+local TY=Vec3.z+(SY-BY)
+SpawnTemplate.units[UnitID].x=TX
+SpawnTemplate.units[UnitID].y=TY
+SpawnTemplate.units[UnitID].alt=Vec3.y
+self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
+end
+SpawnTemplate.route.points[1].x=Vec3.x
+SpawnTemplate.route.points[1].y=Vec3.z
+SpawnTemplate.route.points[1].alt=Vec3.y
+SpawnTemplate.x=Vec3.x
+SpawnTemplate.y=Vec3.z
+return self:SpawnWithIndex(self.SpawnIndex)
+end
+end
+return nil
+end
+function SPAWN:SpawnFromVec2(Vec2,SpawnIndex)
+self:F({self.SpawnTemplatePrefix,Vec2,SpawnIndex})
+local PointVec2=POINT_VEC2:NewFromVec2(Vec2)
+return self:SpawnFromVec3(PointVec2:GetVec3(),SpawnIndex)
+end
+function SPAWN:SpawnFromUnit(HostUnit,SpawnIndex)
+self:F({self.SpawnTemplatePrefix,HostUnit,SpawnIndex})
+if HostUnit and HostUnit:IsAlive()~=nil then
+return self:SpawnFromVec3(HostUnit:GetVec3(),SpawnIndex)
+end
+return nil
+end
+function SPAWN:SpawnFromStatic(HostStatic,SpawnIndex)
+self:F({self.SpawnTemplatePrefix,HostStatic,SpawnIndex})
+if HostStatic and HostStatic:IsAlive()then
+return self:SpawnFromVec3(HostStatic:GetVec3(),SpawnIndex)
+end
+return nil
+end
+function SPAWN:SpawnInZone(Zone,RandomizeGroup,SpawnIndex)
+self:F({self.SpawnTemplatePrefix,Zone,RandomizeGroup,SpawnIndex})
+if Zone then
+if RandomizeGroup then
+return self:SpawnFromVec2(Zone:GetRandomVec2(),SpawnIndex)
+else
+return self:SpawnFromVec2(Zone:GetVec2(),SpawnIndex)
+end
+end
+return nil
+end
+function SPAWN:InitUnControlled(UnControlled)
+self:F2({self.SpawnTemplatePrefix,UnControlled})
+self.SpawnUnControlled=UnControlled
+for SpawnGroupID=1,self.SpawnMaxGroups do
+self.SpawnGroups[SpawnGroupID].UnControlled=UnControlled
+end
+return self
+end
+function SPAWN:GetCoordinate()
+local LateGroup=GROUP:FindByName(self.SpawnTemplatePrefix)
+if LateGroup then
+return LateGroup:GetCoordinate()
+end
+return nil
+end
+function SPAWN:SpawnGroupName(SpawnIndex)
+self:F({self.SpawnTemplatePrefix,SpawnIndex})
+local SpawnPrefix=self.SpawnTemplatePrefix
+if self.SpawnAliasPrefix then
+SpawnPrefix=self.SpawnAliasPrefix
+end
+if SpawnIndex then
+local SpawnName=string.format('%s#%03d',SpawnPrefix,SpawnIndex)
+self:T(SpawnName)
+return SpawnName
+else
+self:T(SpawnPrefix)
+return SpawnPrefix
+end
+end
+function SPAWN:GetFirstAliveGroup()
+self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix})
+for SpawnIndex=1,self.SpawnCount do
+local SpawnGroup=self:GetGroupFromIndex(SpawnIndex)
+if SpawnGroup and SpawnGroup:IsAlive()then
+return SpawnGroup,SpawnIndex
+end
+end
+return nil,nil
+end
+function SPAWN:GetNextAliveGroup(SpawnIndexStart)
+self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnIndexStart})
+SpawnIndexStart=SpawnIndexStart+1
+for SpawnIndex=SpawnIndexStart,self.SpawnCount do
+local SpawnGroup=self:GetGroupFromIndex(SpawnIndex)
+if SpawnGroup and SpawnGroup:IsAlive()then
+return SpawnGroup,SpawnIndex
+end
+end
+return nil,nil
+end
+function SPAWN:GetLastAliveGroup()
+self:F({self.SpawnTemplatePrefixself.SpawnAliasPrefix})
+self.SpawnIndex=self:_GetLastIndex()
+for SpawnIndex=self.SpawnIndex,1,-1 do
+local SpawnGroup=self:GetGroupFromIndex(SpawnIndex)
+if SpawnGroup and SpawnGroup:IsAlive()then
+self.SpawnIndex=SpawnIndex
+return SpawnGroup
+end
+end
+self.SpawnIndex=nil
+return nil
+end
+function SPAWN:GetGroupFromIndex(SpawnIndex)
+self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnIndex})
+if not SpawnIndex then
+SpawnIndex=1
+end
+if self.SpawnGroups and self.SpawnGroups[SpawnIndex]then
+local SpawnGroup=self.SpawnGroups[SpawnIndex].Group
+return SpawnGroup
+else
+return nil
+end
+end
+function SPAWN:_GetPrefixFromGroup(SpawnGroup)
+self:F3({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnGroup})
+local GroupName=SpawnGroup:GetName()
+if GroupName then
+local SpawnPrefix=string.match(GroupName,".*#")
+if SpawnPrefix then
+SpawnPrefix=SpawnPrefix:sub(1,-2)
+end
+return SpawnPrefix
+end
+return nil
+end
+function SPAWN:GetSpawnIndexFromGroup(SpawnGroup)
+self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnGroup})
+local IndexString=string.match(SpawnGroup:GetName(),"#(%d*)$"):sub(2)
+local Index=tonumber(IndexString)
+self:T3(IndexString,Index)
+return Index
+end
+function SPAWN:_GetLastIndex()
+self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix})
+return self.SpawnMaxGroups
+end
+function SPAWN:_InitializeSpawnGroups(SpawnIndex)
+self:F3({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnIndex})
+if not self.SpawnGroups[SpawnIndex]then
+self.SpawnGroups[SpawnIndex]={}
+self.SpawnGroups[SpawnIndex].Visible=false
+self.SpawnGroups[SpawnIndex].Spawned=false
+self.SpawnGroups[SpawnIndex].UnControlled=false
+self.SpawnGroups[SpawnIndex].SpawnTime=0
+self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix=self.SpawnTemplatePrefix
+self.SpawnGroups[SpawnIndex].SpawnTemplate=self:_Prepare(self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix,SpawnIndex)
+end
+self:_RandomizeTemplate(SpawnIndex)
+self:_RandomizeRoute(SpawnIndex)
+return self.SpawnGroups[SpawnIndex]
+end
+function SPAWN:_GetGroupCategoryID(SpawnPrefix)
+local TemplateGroup=Group.getByName(SpawnPrefix)
+if TemplateGroup then
+return TemplateGroup:getCategory()
+else
+return nil
+end
+end
+function SPAWN:_GetGroupCoalitionID(SpawnPrefix)
+local TemplateGroup=Group.getByName(SpawnPrefix)
+if TemplateGroup then
+return TemplateGroup:getCoalition()
+else
+return nil
+end
+end
+function SPAWN:_GetGroupCountryID(SpawnPrefix)
+self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnPrefix})
+local TemplateGroup=Group.getByName(SpawnPrefix)
+if TemplateGroup then
+local TemplateUnits=TemplateGroup:getUnits()
+return TemplateUnits[1]:getCountry()
+else
+return nil
+end
+end
+function SPAWN:_GetTemplate(SpawnTemplatePrefix)
+self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnTemplatePrefix})
+local SpawnTemplate=nil
+SpawnTemplate=routines.utils.deepCopy(_DATABASE.Templates.Groups[SpawnTemplatePrefix].Template)
+if SpawnTemplate==nil then
+error('No Template returned for SpawnTemplatePrefix = '..SpawnTemplatePrefix)
+end
+self:T3({SpawnTemplate})
+return SpawnTemplate
+end
+function SPAWN:_Prepare(SpawnTemplatePrefix,SpawnIndex)
+self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix})
+local SpawnTemplate=self:_GetTemplate(SpawnTemplatePrefix)
+SpawnTemplate.name=self:SpawnGroupName(SpawnIndex)
+SpawnTemplate.groupId=nil
+SpawnTemplate.lateActivation=false
+if SpawnTemplate.CategoryID==Group.Category.GROUND then
+self:T3("For ground units, visible needs to be false...")
+SpawnTemplate.visible=false
+end
+if self.SpawnGrouping then
+local UnitAmount=#SpawnTemplate.units
+self:F({UnitAmount=UnitAmount,SpawnGrouping=self.SpawnGrouping})
+if UnitAmount>self.SpawnGrouping then
+for UnitID=self.SpawnGrouping+1,UnitAmount do
+SpawnTemplate.units[UnitID]=nil
+end
+else
+if UnitAmount=self.SpawnCount+1 then
+self.SpawnCount=self.SpawnCount+1
+SpawnIndex=self.SpawnCount
+end
+self.SpawnIndex=SpawnIndex
+if not self.SpawnGroups[self.SpawnIndex]then
+self:_InitializeSpawnGroups(self.SpawnIndex)
+end
+else
+return nil
+end
+else
+return nil
+end
+return self.SpawnIndex
+end
+function SPAWN:_OnBirth(EventData)
+self:F(self.SpawnTemplatePrefix)
+local SpawnGroup=EventData.IniGroup
+if SpawnGroup then
+local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
+if EventPrefix then
+self:T({"Birth Event:",EventPrefix,self.SpawnTemplatePrefix})
+if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then
+self.AliveUnits=self.AliveUnits+1
+self:T("Alive Units: "..self.AliveUnits)
+end
+end
+end
+end
+function SPAWN:_OnDeadOrCrash(EventData)
+self:F(self.SpawnTemplatePrefix)
+local SpawnGroup=EventData.IniGroup
+if SpawnGroup then
+local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
+if EventPrefix then
+self:T({"Dead event: "..EventPrefix})
+if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then
+self.AliveUnits=self.AliveUnits-1
+self:T("Alive Units: "..self.AliveUnits)
+end
+end
+end
+end
+function SPAWN:_OnTakeOff(EventData)
+self:F(self.SpawnTemplatePrefix)
+local SpawnGroup=EventData.IniGroup
+if SpawnGroup then
+local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
+if EventPrefix then
+self:T({"TakeOff event: "..EventPrefix})
+if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then
+self:T("self.Landed = false")
+SpawnGroup:SetState(SpawnGroup,"Spawn_Landed",false)
+end
+end
+end
+end
+function SPAWN:_OnLand(EventData)
+self:F(self.SpawnTemplatePrefix)
+local SpawnGroup=EventData.IniGroup
+if SpawnGroup then
+local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
+if EventPrefix then
+self:T({"Land event: "..EventPrefix})
+if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then
+SpawnGroup:SetState(SpawnGroup,"Spawn_Landed",true)
+if self.RepeatOnLanding then
+local SpawnGroupIndex=self:GetSpawnIndexFromGroup(SpawnGroup)
+self:T({"Landed:","ReSpawn:",SpawnGroup:GetName(),SpawnGroupIndex})
+self:ReSpawn(SpawnGroupIndex)
+end
+end
+end
+end
+end
+function SPAWN:_OnEngineShutDown(EventData)
+self:F(self.SpawnTemplatePrefix)
+local SpawnGroup=EventData.IniGroup
+if SpawnGroup then
+local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
+if EventPrefix then
+self:T({"EngineShutdown event: "..EventPrefix})
+if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then
+local Landed=SpawnGroup:GetState(SpawnGroup,"Spawn_Landed")
+if Landed and self.RepeatOnEngineShutDown then
+local SpawnGroupIndex=self:GetSpawnIndexFromGroup(SpawnGroup)
+self:T({"EngineShutDown: ","ReSpawn:",SpawnGroup:GetName(),SpawnGroupIndex})
+self:ReSpawn(SpawnGroupIndex)
+end
+end
+end
+end
+end
+function SPAWN:_Scheduler()
+self:F2({"_Scheduler",self.SpawnTemplatePrefix,self.SpawnAliasPrefix,self.SpawnIndex,self.SpawnMaxGroups,self.SpawnMaxUnitsAlive})
+self:Spawn()
+return true
+end
+function SPAWN:_SpawnCleanUpScheduler()
+self:F({"CleanUp Scheduler:",self.SpawnTemplatePrefix})
+local SpawnGroup,SpawnCursor=self:GetFirstAliveGroup()
+self:T({"CleanUp Scheduler:",SpawnGroup,SpawnCursor})
+while SpawnGroup do
+local SpawnUnits=SpawnGroup:GetUnits()
+for UnitID,UnitData in pairs(SpawnUnits)do
+local SpawnUnit=UnitData
+local SpawnUnitName=SpawnUnit:GetName()
+self.SpawnCleanUpTimeStamps[SpawnUnitName]=self.SpawnCleanUpTimeStamps[SpawnUnitName]or{}
+local Stamp=self.SpawnCleanUpTimeStamps[SpawnUnitName]
+self:T({SpawnUnitName,Stamp})
+if Stamp.Vec2 then
+if SpawnUnit:InAir()==false and SpawnUnit:GetVelocityKMH()<1 then
+local NewVec2=SpawnUnit:GetVec2()
+if Stamp.Vec2.x==NewVec2.x and Stamp.Vec2.y==NewVec2.y then
+if Stamp.Time+self.SpawnCleanUpInterval0 then
+local MoveProbability=(self.MoveMaximum*100)/self.AliveUnits
+self:T('Move Probability = '..MoveProbability)
+for MovementUnitName,MovementGroupName in pairs(self.MoveUnits)do
+local MovementGroup=Group.getByName(MovementGroupName)
+if MovementGroup and MovementGroup:isExist()then
+local MoveOrStop=math.random(1,100)
+self:T('MoveOrStop = '..MoveOrStop)
+if MoveOrStop<=MoveProbability then
+self:T('Group continues moving = '..MovementGroupName)
+trigger.action.groupContinueMoving(MovementGroup)
+else
+self:T('Group stops moving = '..MovementGroupName)
+trigger.action.groupStopMoving(MovementGroup)
+end
+else
+self.MoveUnits[MovementUnitName]=nil
+end
+end
+end
+return true
+end
+SEAD={
+ClassName="SEAD",
+TargetSkill={
+Average={Evade=50,DelayOff={10,25},DelayOn={10,30}},
+Good={Evade=30,DelayOff={8,20},DelayOn={20,40}},
+High={Evade=15,DelayOff={5,17},DelayOn={30,50}},
+Excellent={Evade=10,DelayOff={3,10},DelayOn={30,60}}
+},
+SEADGroupPrefixes={}
+}
+function SEAD:New(SEADGroupPrefixes)
+local self=BASE:Inherit(self,BASE:New())
+self:F(SEADGroupPrefixes)
+if type(SEADGroupPrefixes)=='table'then
+for SEADGroupPrefixID,SEADGroupPrefix in pairs(SEADGroupPrefixes)do
+self.SEADGroupPrefixes[SEADGroupPrefix]=SEADGroupPrefix
+end
+else
+self.SEADGroupNames[SEADGroupPrefixes]=SEADGroupPrefixes
+end
+self:HandleEvent(EVENTS.Shot)
+return self
+end
+function SEAD:OnEventShot(EventData)
+self:F({EventData})
+local SEADUnit=EventData.IniDCSUnit
+local SEADUnitName=EventData.IniDCSUnitName
+local SEADWeapon=EventData.Weapon
+local SEADWeaponName=EventData.WeaponName
+self:T("Missile Launched = "..SEADWeaponName)
+if SEADWeaponName=="KH-58"or SEADWeaponName=="KH-25MPU"or SEADWeaponName=="AGM-88"or SEADWeaponName=="KH-31A"or SEADWeaponName=="KH-31P"then
+local _evade=math.random(1,100)
+local _targetMim=EventData.Weapon:getTarget()
+local _targetMimname=Unit.getName(_targetMim)
+local _targetMimgroup=Unit.getGroup(Weapon.getTarget(SEADWeapon))
+local _targetMimgroupName=_targetMimgroup:getName()
+local _targetMimcont=_targetMimgroup:getController()
+local _targetskill=_DATABASE.Templates.Units[_targetMimname].Template.skill
+self:T(self.SEADGroupPrefixes)
+self:T(_targetMimgroupName)
+local SEADGroupFound=false
+for SEADGroupPrefixID,SEADGroupPrefix in pairs(self.SEADGroupPrefixes)do
+if string.find(_targetMimgroupName,SEADGroupPrefix,1,true)then
+SEADGroupFound=true
+self:T('Group Found')
+break
+end
+end
+if SEADGroupFound==true then
+if _targetskill=="Random"then
+local Skills={"Average","Good","High","Excellent"}
+_targetskill=Skills[math.random(1,4)]
+end
+self:T(_targetskill)
+if self.TargetSkill[_targetskill]then
+if(_evade>self.TargetSkill[_targetskill].Evade)then
+self:T(string.format("Evading, target skill "..string.format(_targetskill)))
+local _targetMim=Weapon.getTarget(SEADWeapon)
+local _targetMimname=Unit.getName(_targetMim)
+local _targetMimgroup=Unit.getGroup(Weapon.getTarget(SEADWeapon))
+local _targetMimcont=_targetMimgroup:getController()
+routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20)
+local SuppressedGroups1={}
+local function SuppressionEnd1(id)
+id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
+SuppressedGroups1[id.groupName]=nil
+end
+local id={
+groupName=_targetMimgroup,
+ctrl=_targetMimcont
+}
+local delay1=math.random(self.TargetSkill[_targetskill].DelayOff[1],self.TargetSkill[_targetskill].DelayOff[2])
+if SuppressedGroups1[id.groupName]==nil then
+SuppressedGroups1[id.groupName]={
+SuppressionEndTime1=timer.getTime()+delay1,
+SuppressionEndN1=SuppressionEndCounter1
+}
+Controller.setOption(_targetMimcont,AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
+timer.scheduleFunction(SuppressionEnd1,id,SuppressedGroups1[id.groupName].SuppressionEndTime1)
+end
+local SuppressedGroups={}
+local function SuppressionEnd(id)
+id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
+SuppressedGroups[id.groupName]=nil
+end
+local id={
+groupName=_targetMimgroup,
+ctrl=_targetMimcont
+}
+local delay=math.random(self.TargetSkill[_targetskill].DelayOn[1],self.TargetSkill[_targetskill].DelayOn[2])
+if SuppressedGroups[id.groupName]==nil then
+SuppressedGroups[id.groupName]={
+SuppressionEndTime=timer.getTime()+delay,
+SuppressionEndN=SuppressionEndCounter
+}
+timer.scheduleFunction(SuppressionEnd,id,SuppressedGroups[id.groupName].SuppressionEndTime)
+end
+end
+end
+end
+end
+end
+ESCORT={
+ClassName="ESCORT",
+EscortName=nil,
+EscortClient=nil,
+EscortGroup=nil,
+EscortMode=1,
+MODE={
+FOLLOW=1,
+MISSION=2,
+},
+Targets={},
+FollowScheduler=nil,
+ReportTargets=true,
+OptionROE=AI.Option.Air.val.ROE.OPEN_FIRE,
+OptionReactionOnThreat=AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION,
+SmokeDirectionVector=false,
+TaskPoints={}
+}
+function ESCORT:New(EscortClient,EscortGroup,EscortName,EscortBriefing)
+local self=BASE:Inherit(self,BASE:New())
+self:F({EscortClient,EscortGroup,EscortName})
+self.EscortClient=EscortClient
+self.EscortGroup=EscortGroup
+self.EscortName=EscortName
+self.EscortBriefing=EscortBriefing
+self.EscortSetGroup=SET_GROUP:New()
+self.EscortSetGroup:AddObject(self.EscortGroup)
+self.EscortSetGroup:Flush()
+self.Detection=DETECTION_UNITS:New(self.EscortSetGroup,15000)
+self.EscortGroup.Detection=self.Detection
+if not self.EscortClient._EscortGroups then
+self.EscortClient._EscortGroups={}
+end
+if not self.EscortClient._EscortGroups[EscortGroup:GetName()]then
+self.EscortClient._EscortGroups[EscortGroup:GetName()]={}
+self.EscortClient._EscortGroups[EscortGroup:GetName()].EscortGroup=self.EscortGroup
+self.EscortClient._EscortGroups[EscortGroup:GetName()].EscortName=self.EscortName
+self.EscortClient._EscortGroups[EscortGroup:GetName()].Detection=self.EscortGroup.Detection
+end
+self.EscortMenu=MENU_CLIENT:New(self.EscortClient,self.EscortName)
+self.EscortGroup:WayPointInitialize(1)
+self.EscortGroup:OptionROTVertical()
+self.EscortGroup:OptionROEOpenFire()
+if not EscortBriefing then
+EscortGroup:MessageToClient(EscortGroup:GetCategoryName().." '"..EscortName.."' ("..EscortGroup:GetCallsign()..") reporting! "..
+"We're escorting your flight. "..
+"Use the Radio Menu and F10 and use the options under + "..EscortName.."\n",
+60,EscortClient
+)
+else
+EscortGroup:MessageToClient(EscortGroup:GetCategoryName().." '"..EscortName.."' ("..EscortGroup:GetCallsign()..") "..EscortBriefing,
+60,EscortClient
+)
+end
+self.FollowDistance=100
+self.CT1=0
+self.GT1=0
+self.FollowScheduler,self.FollowSchedule=SCHEDULER:New(self,self._FollowScheduler,{},1,.5,.01)
+self.FollowScheduler:Stop(self.FollowSchedule)
+self.EscortMode=ESCORT.MODE.MISSION
+return self
+end
+function ESCORT:SetDetection(Detection)
+self.Detection=Detection
+self.EscortGroup.Detection=self.Detection
+self.EscortClient._EscortGroups[self.EscortGroup:GetName()].Detection=self.EscortGroup.Detection
+Detection:__Start(1)
+end
+function ESCORT:TestSmokeDirectionVector(SmokeDirection)
+self.SmokeDirectionVector=(SmokeDirection==true)and true or false
+end
+function ESCORT:Menus()
+self:F()
+self:MenuFollowAt(100)
+self:MenuFollowAt(200)
+self:MenuFollowAt(300)
+self:MenuFollowAt(400)
+self:MenuScanForTargets(100,60)
+self:MenuHoldAtEscortPosition(30)
+self:MenuHoldAtLeaderPosition(30)
+self:MenuFlare()
+self:MenuSmoke()
+self:MenuReportTargets(60)
+self:MenuAssistedAttack()
+self:MenuROE()
+self:MenuEvasion()
+self:MenuResumeMission()
+return self
+end
+function ESCORT:MenuFollowAt(Distance)
+self:F(Distance)
+if self.EscortGroup:IsAir()then
+if not self.EscortMenuReportNavigation then
+self.EscortMenuReportNavigation=MENU_CLIENT:New(self.EscortClient,"Navigation",self.EscortMenu)
+end
+if not self.EscortMenuJoinUpAndFollow then
+self.EscortMenuJoinUpAndFollow={}
+end
+self.EscortMenuJoinUpAndFollow[#self.EscortMenuJoinUpAndFollow+1]=MENU_CLIENT_COMMAND:New(self.EscortClient,"Join-Up and Follow at "..Distance,self.EscortMenuReportNavigation,ESCORT._JoinUpAndFollow,self,Distance)
+self.EscortMode=ESCORT.MODE.FOLLOW
+end
+return self
+end
+function ESCORT:MenuHoldAtEscortPosition(Height,Seconds,MenuTextFormat)
+self:F({Height,Seconds,MenuTextFormat})
+if self.EscortGroup:IsAir()then
+if not self.EscortMenuHold then
+self.EscortMenuHold=MENU_CLIENT:New(self.EscortClient,"Hold position",self.EscortMenu)
+end
+if not Height then
+Height=30
+end
+if not Seconds then
+Seconds=0
+end
+local MenuText=""
+if not MenuTextFormat then
+if Seconds==0 then
+MenuText=string.format("Hold at %d meter",Height)
+else
+MenuText=string.format("Hold at %d meter for %d seconds",Height,Seconds)
+end
+else
+if Seconds==0 then
+MenuText=string.format(MenuTextFormat,Height)
+else
+MenuText=string.format(MenuTextFormat,Height,Seconds)
+end
+end
+if not self.EscortMenuHoldPosition then
+self.EscortMenuHoldPosition={}
+end
+self.EscortMenuHoldPosition[#self.EscortMenuHoldPosition+1]=MENU_CLIENT_COMMAND
+:New(
+self.EscortClient,
+MenuText,
+self.EscortMenuHold,
+ESCORT._HoldPosition,
+self,
+self.EscortGroup,
+Height,
+Seconds
+)
+end
+return self
+end
+function ESCORT:MenuHoldAtLeaderPosition(Height,Seconds,MenuTextFormat)
+self:F({Height,Seconds,MenuTextFormat})
+if self.EscortGroup:IsAir()then
+if not self.EscortMenuHold then
+self.EscortMenuHold=MENU_CLIENT:New(self.EscortClient,"Hold position",self.EscortMenu)
+end
+if not Height then
+Height=30
+end
+if not Seconds then
+Seconds=0
+end
+local MenuText=""
+if not MenuTextFormat then
+if Seconds==0 then
+MenuText=string.format("Rejoin and hold at %d meter",Height)
+else
+MenuText=string.format("Rejoin and hold at %d meter for %d seconds",Height,Seconds)
+end
+else
+if Seconds==0 then
+MenuText=string.format(MenuTextFormat,Height)
+else
+MenuText=string.format(MenuTextFormat,Height,Seconds)
+end
+end
+if not self.EscortMenuHoldAtLeaderPosition then
+self.EscortMenuHoldAtLeaderPosition={}
+end
+self.EscortMenuHoldAtLeaderPosition[#self.EscortMenuHoldAtLeaderPosition+1]=MENU_CLIENT_COMMAND
+:New(
+self.EscortClient,
+MenuText,
+self.EscortMenuHold,
+ESCORT._HoldPosition,
+{ParamSelf=self,
+ParamOrbitGroup=self.EscortClient,
+ParamHeight=Height,
+ParamSeconds=Seconds
+}
+)
+end
+return self
+end
+function ESCORT:MenuScanForTargets(Height,Seconds,MenuTextFormat)
+self:F({Height,Seconds,MenuTextFormat})
+if self.EscortGroup:IsAir()then
+if not self.EscortMenuScan then
+self.EscortMenuScan=MENU_CLIENT:New(self.EscortClient,"Scan for targets",self.EscortMenu)
+end
+if not Height then
+Height=100
+end
+if not Seconds then
+Seconds=30
+end
+local MenuText=""
+if not MenuTextFormat then
+if Seconds==0 then
+MenuText=string.format("At %d meter",Height)
+else
+MenuText=string.format("At %d meter for %d seconds",Height,Seconds)
+end
+else
+if Seconds==0 then
+MenuText=string.format(MenuTextFormat,Height)
+else
+MenuText=string.format(MenuTextFormat,Height,Seconds)
+end
+end
+if not self.EscortMenuScanForTargets then
+self.EscortMenuScanForTargets={}
+end
+self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1]=MENU_CLIENT_COMMAND
+:New(
+self.EscortClient,
+MenuText,
+self.EscortMenuScan,
+ESCORT._ScanTargets,
+self,
+30
+)
+end
+return self
+end
+function ESCORT:MenuFlare(MenuTextFormat)
+self:F()
+if not self.EscortMenuReportNavigation then
+self.EscortMenuReportNavigation=MENU_CLIENT:New(self.EscortClient,"Navigation",self.EscortMenu)
+end
+local MenuText=""
+if not MenuTextFormat then
+MenuText="Flare"
+else
+MenuText=MenuTextFormat
+end
+if not self.EscortMenuFlare then
+self.EscortMenuFlare=MENU_CLIENT:New(self.EscortClient,MenuText,self.EscortMenuReportNavigation,ESCORT._Flare,self)
+self.EscortMenuFlareGreen=MENU_CLIENT_COMMAND:New(self.EscortClient,"Release green flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Green,"Released a green flare!")
+self.EscortMenuFlareRed=MENU_CLIENT_COMMAND:New(self.EscortClient,"Release red flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Red,"Released a red flare!")
+self.EscortMenuFlareWhite=MENU_CLIENT_COMMAND:New(self.EscortClient,"Release white flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.White,"Released a white flare!")
+self.EscortMenuFlareYellow=MENU_CLIENT_COMMAND:New(self.EscortClient,"Release yellow flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Yellow,"Released a yellow flare!")
+end
+return self
+end
+function ESCORT:MenuSmoke(MenuTextFormat)
+self:F()
+if not self.EscortGroup:IsAir()then
+if not self.EscortMenuReportNavigation then
+self.EscortMenuReportNavigation=MENU_CLIENT:New(self.EscortClient,"Navigation",self.EscortMenu)
+end
+local MenuText=""
+if not MenuTextFormat then
+MenuText="Smoke"
+else
+MenuText=MenuTextFormat
+end
+if not self.EscortMenuSmoke then
+self.EscortMenuSmoke=MENU_CLIENT:New(self.EscortClient,"Smoke",self.EscortMenuReportNavigation,ESCORT._Smoke,self)
+self.EscortMenuSmokeGreen=MENU_CLIENT_COMMAND:New(self.EscortClient,"Release green smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Green,"Releasing green smoke!")
+self.EscortMenuSmokeRed=MENU_CLIENT_COMMAND:New(self.EscortClient,"Release red smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Red,"Releasing red smoke!")
+self.EscortMenuSmokeWhite=MENU_CLIENT_COMMAND:New(self.EscortClient,"Release white smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.White,"Releasing white smoke!")
+self.EscortMenuSmokeOrange=MENU_CLIENT_COMMAND:New(self.EscortClient,"Release orange smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Orange,"Releasing orange smoke!")
+self.EscortMenuSmokeBlue=MENU_CLIENT_COMMAND:New(self.EscortClient,"Release blue smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Blue,"Releasing blue smoke!")
+end
+end
+return self
+end
+function ESCORT:MenuReportTargets(Seconds)
+self:F({Seconds})
+if not self.EscortMenuReportNearbyTargets then
+self.EscortMenuReportNearbyTargets=MENU_CLIENT:New(self.EscortClient,"Report targets",self.EscortMenu)
+end
+if not Seconds then
+Seconds=30
+end
+self.EscortMenuReportNearbyTargetsNow=MENU_CLIENT_COMMAND:New(self.EscortClient,"Report targets now!",self.EscortMenuReportNearbyTargets,ESCORT._ReportNearbyTargetsNow,self)
+self.EscortMenuReportNearbyTargetsOn=MENU_CLIENT_COMMAND:New(self.EscortClient,"Report targets on",self.EscortMenuReportNearbyTargets,ESCORT._SwitchReportNearbyTargets,self,true)
+self.EscortMenuReportNearbyTargetsOff=MENU_CLIENT_COMMAND:New(self.EscortClient,"Report targets off",self.EscortMenuReportNearbyTargets,ESCORT._SwitchReportNearbyTargets,self,false)
+self.EscortMenuAttackNearbyTargets=MENU_CLIENT:New(self.EscortClient,"Attack targets",self.EscortMenu)
+self.ReportTargetsScheduler=SCHEDULER:New(self,self._ReportTargetsScheduler,{},1,Seconds)
+return self
+end
+function ESCORT:MenuAssistedAttack()
+self:F()
+self.EscortMenuTargetAssistance=MENU_CLIENT:New(self.EscortClient,"Request assistance from",self.EscortMenu)
+return self
+end
+function ESCORT:MenuROE(MenuTextFormat)
+self:F(MenuTextFormat)
+if not self.EscortMenuROE then
+self.EscortMenuROE=MENU_CLIENT:New(self.EscortClient,"ROE",self.EscortMenu)
+if self.EscortGroup:OptionROEHoldFirePossible()then
+self.EscortMenuROEHoldFire=MENU_CLIENT_COMMAND:New(self.EscortClient,"Hold Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEHoldFire(),"Holding weapons!")
+end
+if self.EscortGroup:OptionROEReturnFirePossible()then
+self.EscortMenuROEReturnFire=MENU_CLIENT_COMMAND:New(self.EscortClient,"Return Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEReturnFire(),"Returning fire!")
+end
+if self.EscortGroup:OptionROEOpenFirePossible()then
+self.EscortMenuROEOpenFire=MENU_CLIENT_COMMAND:New(self.EscortClient,"Open Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEOpenFire(),"Opening fire on designated targets!!")
+end
+if self.EscortGroup:OptionROEWeaponFreePossible()then
+self.EscortMenuROEWeaponFree=MENU_CLIENT_COMMAND:New(self.EscortClient,"Weapon Free",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEWeaponFree(),"Opening fire on targets of opportunity!")
+end
+end
+return self
+end
+function ESCORT:MenuEvasion(MenuTextFormat)
+self:F(MenuTextFormat)
+if self.EscortGroup:IsAir()then
+if not self.EscortMenuEvasion then
+self.EscortMenuEvasion=MENU_CLIENT:New(self.EscortClient,"Evasion",self.EscortMenu)
+if self.EscortGroup:OptionROTNoReactionPossible()then
+self.EscortMenuEvasionNoReaction=MENU_CLIENT_COMMAND:New(self.EscortClient,"Fight until death",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTNoReaction(),"Fighting until death!")
+end
+if self.EscortGroup:OptionROTPassiveDefensePossible()then
+self.EscortMenuEvasionPassiveDefense=MENU_CLIENT_COMMAND:New(self.EscortClient,"Use flares, chaff and jammers",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTPassiveDefense(),"Defending using jammers, chaff and flares!")
+end
+if self.EscortGroup:OptionROTEvadeFirePossible()then
+self.EscortMenuEvasionEvadeFire=MENU_CLIENT_COMMAND:New(self.EscortClient,"Evade enemy fire",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTEvadeFire(),"Evading on enemy fire!")
+end
+if self.EscortGroup:OptionROTVerticalPossible()then
+self.EscortMenuOptionEvasionVertical=MENU_CLIENT_COMMAND:New(self.EscortClient,"Go below radar and evade fire",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTVertical(),"Evading on enemy fire with vertical manoeuvres!")
+end
+end
+end
+return self
+end
+function ESCORT:MenuResumeMission()
+self:F()
+if not self.EscortMenuResumeMission then
+self.EscortMenuResumeMission=MENU_CLIENT:New(self.EscortClient,"Resume mission from",self.EscortMenu)
+end
+return self
+end
+function ESCORT:_HoldPosition(OrbitGroup,OrbitHeight,OrbitSeconds)
+local EscortGroup=self.EscortGroup
+local EscortClient=self.EscortClient
+local OrbitUnit=OrbitGroup:GetUnit(1)
+self.FollowScheduler:Stop(self.FollowSchedule)
+local PointFrom={}
+local GroupVec3=EscortGroup:GetUnit(1):GetVec3()
+PointFrom={}
+PointFrom.x=GroupVec3.x
+PointFrom.y=GroupVec3.z
+PointFrom.speed=250
+PointFrom.type=AI.Task.WaypointType.TURNING_POINT
+PointFrom.alt=GroupVec3.y
+PointFrom.alt_type=AI.Task.AltitudeType.BARO
+local OrbitPoint=OrbitUnit:GetVec2()
+local PointTo={}
+PointTo.x=OrbitPoint.x
+PointTo.y=OrbitPoint.y
+PointTo.speed=250
+PointTo.type=AI.Task.WaypointType.TURNING_POINT
+PointTo.alt=OrbitHeight
+PointTo.alt_type=AI.Task.AltitudeType.BARO
+PointTo.task=EscortGroup:TaskOrbitCircleAtVec2(OrbitPoint,OrbitHeight,0)
+local Points={PointFrom,PointTo}
+EscortGroup:OptionROEHoldFire()
+EscortGroup:OptionROTPassiveDefense()
+EscortGroup:SetTask(EscortGroup:TaskRoute(Points))
+EscortGroup:MessageToClient("Orbiting at location.",10,EscortClient)
+end
+function ESCORT:_JoinUpAndFollow(Distance)
+local EscortGroup=self.EscortGroup
+local EscortClient=self.EscortClient
+self.Distance=Distance
+self:JoinUpAndFollow(EscortGroup,EscortClient,self.Distance)
+end
+function ESCORT:JoinUpAndFollow(EscortGroup,EscortClient,Distance)
+self:F({EscortGroup,EscortClient,Distance})
+self.FollowScheduler:Stop(self.FollowSchedule)
+EscortGroup:OptionROEHoldFire()
+EscortGroup:OptionROTPassiveDefense()
+self.EscortMode=ESCORT.MODE.FOLLOW
+self.CT1=0
+self.GT1=0
+self.FollowScheduler:Start(self.FollowSchedule)
+EscortGroup:MessageToClient("Rejoining and Following at "..Distance.."!",30,EscortClient)
+end
+function ESCORT:_Flare(Color,Message)
+local EscortGroup=self.EscortGroup
+local EscortClient=self.EscortClient
+EscortGroup:GetUnit(1):Flare(Color)
+EscortGroup:MessageToClient(Message,10,EscortClient)
+end
+function ESCORT:_Smoke(Color,Message)
+local EscortGroup=self.EscortGroup
+local EscortClient=self.EscortClient
+EscortGroup:GetUnit(1):Smoke(Color)
+EscortGroup:MessageToClient(Message,10,EscortClient)
+end
+function ESCORT:_ReportNearbyTargetsNow()
+local EscortGroup=self.EscortGroup
+local EscortClient=self.EscortClient
+self:_ReportTargetsScheduler()
+end
+function ESCORT:_SwitchReportNearbyTargets(ReportTargets)
+local EscortGroup=self.EscortGroup
+local EscortClient=self.EscortClient
+self.ReportTargets=ReportTargets
+if self.ReportTargets then
+if not self.ReportTargetsScheduler then
+self.ReportTargetsScheduler:Schedule(self,self._ReportTargetsScheduler,{},1,30)
+end
+else
+routines.removeFunction(self.ReportTargetsScheduler)
+self.ReportTargetsScheduler=nil
+end
+end
+function ESCORT:_ScanTargets(ScanDuration)
+local EscortGroup=self.EscortGroup
+local EscortClient=self.EscortClient
+self.FollowScheduler:Stop(self.FollowSchedule)
+if EscortGroup:IsHelicopter()then
+EscortGroup:PushTask(
+EscortGroup:TaskControlled(
+EscortGroup:TaskOrbitCircle(200,20),
+EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil)
+),1)
+elseif EscortGroup:IsAirPlane()then
+EscortGroup:PushTask(
+EscortGroup:TaskControlled(
+EscortGroup:TaskOrbitCircle(1000,500),
+EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil)
+),1)
+end
+EscortGroup:MessageToClient("Scanning targets for "..ScanDuration.." seconds.",ScanDuration,EscortClient)
+if self.EscortMode==ESCORT.MODE.FOLLOW then
+self.FollowScheduler:Start(self.FollowSchedule)
+end
+end
+function _Resume(EscortGroup)
+env.info('_Resume')
+local Escort=EscortGroup:GetState(EscortGroup,"Escort")
+env.info("EscortMode = "..Escort.EscortMode)
+if Escort.EscortMode==ESCORT.MODE.FOLLOW then
+Escort:JoinUpAndFollow(EscortGroup,Escort.EscortClient,Escort.Distance)
+end
+end
+function ESCORT:_AttackTarget(DetectedItemID)
+local EscortGroup=self.EscortGroup
+self:E(EscortGroup)
+local EscortClient=self.EscortClient
+self.FollowScheduler:Stop(self.FollowSchedule)
+if EscortGroup:IsAir()then
+EscortGroup:OptionROEOpenFire()
+EscortGroup:OptionROTPassiveDefense()
+EscortGroup:SetState(EscortGroup,"Escort",self)
+local DetectedSet=self.Detection:GetDetectedSet(DetectedItemID)
+local Tasks={}
+DetectedSet:ForEachUnit(
+function(DetectedUnit,Tasks)
+if DetectedUnit:IsAlive()then
+Tasks[#Tasks+1]=EscortGroup:TaskAttackUnit(DetectedUnit)
+end
+end,Tasks
+)
+Tasks[#Tasks+1]=EscortGroup:TaskFunction("_Resume",{"''"})
+EscortGroup:SetTask(
+EscortGroup:TaskCombo(
+Tasks
+),1
+)
+else
+local DetectedSet=self.Detection:GetDetectedSet(DetectedItemID)
+local Tasks={}
+DetectedSet:ForEachUnit(
+function(DetectedUnit,Tasks)
+if DetectedUnit:IsAlive()then
+Tasks[#Tasks+1]=EscortGroup:TaskFireAtPoint(DetectedUnit:GetVec2(),50)
+end
+end,Tasks
+)
+EscortGroup:SetTask(
+EscortGroup:TaskCombo(
+Tasks
+),1
+)
+end
+EscortGroup:MessageToClient("Engaging Designated Unit!",10,EscortClient)
+end
+function ESCORT:_AssistTarget(EscortGroupAttack,DetectedItemID)
+local EscortGroup=self.EscortGroup
+local EscortClient=self.EscortClient
+self.FollowScheduler:Stop(self.FollowSchedule)
+if EscortGroupAttack:IsAir()then
+EscortGroupAttack:OptionROEOpenFire()
+EscortGroupAttack:OptionROTVertical()
+local DetectedSet=self.Detection:GetDetectedSet(DetectedItemID)
+local Tasks={}
+DetectedSet:ForEachUnit(
+function(DetectedUnit,Tasks)
+if DetectedUnit:IsAlive()then
+Tasks[#Tasks+1]=EscortGroupAttack:TaskAttackUnit(DetectedUnit)
+end
+end,Tasks
+)
+Tasks[#Tasks+1]=EscortGroupAttack:TaskOrbitCircle(500,350)
+EscortGroupAttack:SetTask(
+EscortGroupAttack:TaskCombo(
+Tasks
+),1
+)
+else
+local DetectedSet=self.Detection:GetDetectedSet(DetectedItemID)
+local Tasks={}
+DetectedSet:ForEachUnit(
+function(DetectedUnit,Tasks)
+if DetectedUnit:IsAlive()then
+Tasks[#Tasks+1]=EscortGroupAttack:TaskFireAtPoint(DetectedUnit:GetVec2(),50)
+end
+end,Tasks
+)
+EscortGroupAttack:SetTask(
+EscortGroupAttack:TaskCombo(
+Tasks
+),1
+)
+end
+EscortGroupAttack:MessageToClient("Assisting with the destroying the enemy unit!",10,EscortClient)
+end
+function ESCORT:_ROE(EscortROEFunction,EscortROEMessage)
+local EscortGroup=self.EscortGroup
+local EscortClient=self.EscortClient
+pcall(function()EscortROEFunction()end)
+EscortGroup:MessageToClient(EscortROEMessage,10,EscortClient)
+end
+function ESCORT:_ROT(EscortROTFunction,EscortROTMessage)
+local EscortGroup=self.EscortGroup
+local EscortClient=self.EscortClient
+pcall(function()EscortROTFunction()end)
+EscortGroup:MessageToClient(EscortROTMessage,10,EscortClient)
+end
+function ESCORT:_ResumeMission(WayPoint)
+local EscortGroup=self.EscortGroup
+local EscortClient=self.EscortClient
+self.FollowScheduler:Stop(self.FollowSchedule)
+local WayPoints=EscortGroup:GetTaskRoute()
+self:T(WayPoint,WayPoints)
+for WayPointIgnore=1,WayPoint do
+table.remove(WayPoints,1)
+end
+SCHEDULER:New(EscortGroup,EscortGroup.SetTask,{EscortGroup:TaskRoute(WayPoints)},1)
+EscortGroup:MessageToClient("Resuming mission from waypoint "..WayPoint..".",10,EscortClient)
+end
+function ESCORT:RegisterRoute()
+self:F()
+local EscortGroup=self.EscortGroup
+local TaskPoints=EscortGroup:GetTaskRoute()
+self:T(TaskPoints)
+return TaskPoints
+end
+function ESCORT:_FollowScheduler()
+self:F({self.FollowDistance})
+self:T({self.EscortClient.UnitName,self.EscortGroup.GroupName})
+if self.EscortGroup:IsAlive()and self.EscortClient:IsAlive()then
+local ClientUnit=self.EscortClient:GetClientGroupUnit()
+local GroupUnit=self.EscortGroup:GetUnit(1)
+local FollowDistance=self.FollowDistance
+self:T({ClientUnit.UnitName,GroupUnit.UnitName})
+if self.CT1==0 and self.GT1==0 then
+self.CV1=ClientUnit:GetVec3()
+self:T({"self.CV1",self.CV1})
+self.CT1=timer.getTime()
+self.GV1=GroupUnit:GetVec3()
+self.GT1=timer.getTime()
+else
+local CT1=self.CT1
+local CT2=timer.getTime()
+local CV1=self.CV1
+local CV2=ClientUnit:GetVec3()
+self.CT1=CT2
+self.CV1=CV2
+local CD=((CV2.x-CV1.x)^2+(CV2.y-CV1.y)^2+(CV2.z-CV1.z)^2)^0.5
+local CT=CT2-CT1
+local CS=(3600/CT)*(CD/1000)
+self:T2({"Client:",CS,CD,CT,CV2,CV1,CT2,CT1})
+local GT1=self.GT1
+local GT2=timer.getTime()
+local GV1=self.GV1
+local GV2=GroupUnit:GetVec3()
+self.GT1=GT2
+self.GV1=GV2
+local GD=((GV2.x-GV1.x)^2+(GV2.y-GV1.y)^2+(GV2.z-GV1.z)^2)^0.5
+local GT=GT2-GT1
+local GS=(3600/GT)*(GD/1000)
+self:T2({"Group:",GS,GD,GT,GV2,GV1,GT2,GT1})
+local GV={x=GV2.x-CV2.x,y=GV2.y-CV2.y,z=GV2.z-CV2.z}
+local GH2={x=GV2.x,y=CV2.y,z=GV2.z}
+local alpha=math.atan2(GV.z,GV.x)
+local CVI={x=CV2.x+FollowDistance*math.cos(alpha),
+y=GH2.y,
+z=CV2.z+FollowDistance*math.sin(alpha),
+}
+local DV={x=CV2.x-CVI.x,y=CV2.y-CVI.y,z=CV2.z-CVI.z}
+local DVu={x=DV.x/FollowDistance,y=DV.y/FollowDistance,z=DV.z/FollowDistance}
+local GDV={x=DVu.x*CS*8+CVI.x,y=CVI.y,z=DVu.z*CS*8+CVI.z}
+if self.SmokeDirectionVector==true then
+trigger.action.smoke(GDV,trigger.smokeColor.Red)
+end
+self:T2({"CV2:",CV2})
+self:T2({"CVI:",CVI})
+self:T2({"GDV:",GDV})
+local CatchUpDistance=((GDV.x-GV2.x)^2+(GDV.y-GV2.y)^2+(GDV.z-GV2.z)^2)^0.5
+local Time=10
+local CatchUpSpeed=(CatchUpDistance-(CS*8.4))/Time
+local Speed=CS+CatchUpSpeed
+if Speed<0 then
+Speed=0
+end
+self:T({"Client Speed, Escort Speed, Speed, FollowDistance, Time:",CS,GS,Speed,FollowDistance,Time})
+self.EscortGroup:RouteToVec3(GDV,Speed/3.6)
+end
+return true
+end
+return false
+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
+self.EscortMenuTargetAssistance:RemoveSubMenus()
+end
+local DetectedItems=self.Detection:GetDetectedItems()
+self:E(DetectedItems)
+local DetectedTargets=false
+local DetectedMsgs={}
+for ClientEscortGroupName,EscortGroupData in pairs(self.EscortClient._EscortGroups)do
+local ClientEscortTargets=EscortGroupData.Detection
+for DetectedItemID,DetectedItem in pairs(DetectedItems)do
+self:E({DetectedItemID,DetectedItem})
+local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItemID,EscortGroupData.EscortGroup,_DATABASE:GetPlayerSettings(self.EscortClient:GetPlayerName()))
+if ClientEscortGroupName==EscortGroupName then
+local DetectedMsg=DetectedItemReportSummary:Text("\n")
+DetectedMsgs[#DetectedMsgs+1]=DetectedMsg
+self:T(DetectedMsg)
+MENU_CLIENT_COMMAND:New(self.EscortClient,
+DetectedMsg,
+self.EscortMenuAttackNearbyTargets,
+ESCORT._AttackTarget,
+self,
+DetectedItemID
+)
+else
+if self.EscortMenuTargetAssistance then
+local DetectedMsg=DetectedItemReportSummary:Text("\n")
+self:T(DetectedMsg)
+local MenuTargetAssistance=MENU_CLIENT:New(self.EscortClient,EscortGroupData.EscortName,self.EscortMenuTargetAssistance)
+MENU_CLIENT_COMMAND:New(self.EscortClient,
+DetectedMsg,
+MenuTargetAssistance,
+ESCORT._AssistTarget,
+self,
+EscortGroupData.EscortGroup,
+DetectedItemID
+)
+end
+end
+DetectedTargets=true
+end
+end
+self:E(DetectedMsgs)
+if DetectedTargets then
+self.EscortGroup:MessageToClient("Reporting detected targets:\n"..table.concat(DetectedMsgs,"\n"),20,self.EscortClient)
+else
+self.EscortGroup:MessageToClient("No targets detected.",10,self.EscortClient)
+end
+return true
+else
+end
+end
+return false
+end
+MISSILETRAINER={
+ClassName="MISSILETRAINER",
+TrackingMissiles={},
+}
+function MISSILETRAINER._Alive(Client,self)
+if self.Briefing then
+Client:Message(self.Briefing,15,"Trainer")
+end
+if self.MenusOnOff==true then
+Client:Message("Use the 'Radio Menu' -> 'Other (F10)' -> 'Missile Trainer' menu options to change the Missile Trainer settings (for all players).",15,"Trainer")
+Client.MainMenu=MENU_CLIENT:New(Client,"Missile Trainer",nil)
+Client.MenuMessages=MENU_CLIENT:New(Client,"Messages",Client.MainMenu)
+Client.MenuOn=MENU_CLIENT_COMMAND:New(Client,"Messages On",Client.MenuMessages,self._MenuMessages,{MenuSelf=self,MessagesOnOff=true})
+Client.MenuOff=MENU_CLIENT_COMMAND:New(Client,"Messages Off",Client.MenuMessages,self._MenuMessages,{MenuSelf=self,MessagesOnOff=false})
+Client.MenuTracking=MENU_CLIENT:New(Client,"Tracking",Client.MainMenu)
+Client.MenuTrackingToAll=MENU_CLIENT_COMMAND:New(Client,"To All",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingToAll=true})
+Client.MenuTrackingToTarget=MENU_CLIENT_COMMAND:New(Client,"To Target",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingToAll=false})
+Client.MenuTrackOn=MENU_CLIENT_COMMAND:New(Client,"Tracking On",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingOnOff=true})
+Client.MenuTrackOff=MENU_CLIENT_COMMAND:New(Client,"Tracking Off",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingOnOff=false})
+Client.MenuTrackIncrease=MENU_CLIENT_COMMAND:New(Client,"Frequency Increase",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingFrequency=-1})
+Client.MenuTrackDecrease=MENU_CLIENT_COMMAND:New(Client,"Frequency Decrease",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingFrequency=1})
+Client.MenuAlerts=MENU_CLIENT:New(Client,"Alerts",Client.MainMenu)
+Client.MenuAlertsToAll=MENU_CLIENT_COMMAND:New(Client,"To All",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsToAll=true})
+Client.MenuAlertsToTarget=MENU_CLIENT_COMMAND:New(Client,"To Target",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsToAll=false})
+Client.MenuHitsOn=MENU_CLIENT_COMMAND:New(Client,"Hits On",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsHitsOnOff=true})
+Client.MenuHitsOff=MENU_CLIENT_COMMAND:New(Client,"Hits Off",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsHitsOnOff=false})
+Client.MenuLaunchesOn=MENU_CLIENT_COMMAND:New(Client,"Launches On",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsLaunchesOnOff=true})
+Client.MenuLaunchesOff=MENU_CLIENT_COMMAND:New(Client,"Launches Off",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsLaunchesOnOff=false})
+Client.MenuDetails=MENU_CLIENT:New(Client,"Details",Client.MainMenu)
+Client.MenuDetailsDistanceOn=MENU_CLIENT_COMMAND:New(Client,"Range On",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsRangeOnOff=true})
+Client.MenuDetailsDistanceOff=MENU_CLIENT_COMMAND:New(Client,"Range Off",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsRangeOnOff=false})
+Client.MenuDetailsBearingOn=MENU_CLIENT_COMMAND:New(Client,"Bearing On",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsBearingOnOff=true})
+Client.MenuDetailsBearingOff=MENU_CLIENT_COMMAND:New(Client,"Bearing Off",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsBearingOnOff=false})
+Client.MenuDistance=MENU_CLIENT:New(Client,"Set distance to plane",Client.MainMenu)
+Client.MenuDistance50=MENU_CLIENT_COMMAND:New(Client,"50 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=50/1000})
+Client.MenuDistance100=MENU_CLIENT_COMMAND:New(Client,"100 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=100/1000})
+Client.MenuDistance150=MENU_CLIENT_COMMAND:New(Client,"150 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=150/1000})
+Client.MenuDistance200=MENU_CLIENT_COMMAND:New(Client,"200 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=200/1000})
+else
+if Client.MainMenu then
+Client.MainMenu:Remove()
+end
+end
+local ClientID=Client:GetID()
+self:T(ClientID)
+if not self.TrackingMissiles[ClientID]then
+self.TrackingMissiles[ClientID]={}
+end
+self.TrackingMissiles[ClientID].Client=Client
+if not self.TrackingMissiles[ClientID].MissileData then
+self.TrackingMissiles[ClientID].MissileData={}
+end
+end
+function MISSILETRAINER:New(Distance,Briefing)
+local self=BASE:Inherit(self,BASE:New())
+self:F(Distance)
+if Briefing then
+self.Briefing=Briefing
+end
+self.Schedulers={}
+self.SchedulerID=0
+self.MessageInterval=2
+self.MessageLastTime=timer.getTime()
+self.Distance=Distance/1000
+self:HandleEvent(EVENTS.Shot)
+self.DBClients=SET_CLIENT:New():FilterStart()
+self.DBClients:ForEachClient(
+function(Client)
+self:E("ForEach:"..Client.UnitName)
+Client:Alive(self._Alive,self)
+end
+)
+self.MessagesOnOff=true
+self.TrackingToAll=false
+self.TrackingOnOff=true
+self.TrackingFrequency=3
+self.AlertsToAll=true
+self.AlertsHitsOnOff=true
+self.AlertsLaunchesOnOff=true
+self.DetailsRangeOnOff=true
+self.DetailsBearingOnOff=true
+self.MenusOnOff=true
+self.TrackingMissiles={}
+self.TrackingScheduler=SCHEDULER:New(self,self._TrackMissiles,{},0.5,0.05,0)
+return self
+end
+function MISSILETRAINER:InitMessagesOnOff(MessagesOnOff)
+self:F(MessagesOnOff)
+self.MessagesOnOff=MessagesOnOff
+if self.MessagesOnOff==true then
+MESSAGE:New("Messages ON",15,"Menu"):ToAll()
+else
+MESSAGE:New("Messages OFF",15,"Menu"):ToAll()
+end
+return self
+end
+function MISSILETRAINER:InitTrackingToAll(TrackingToAll)
+self:F(TrackingToAll)
+self.TrackingToAll=TrackingToAll
+if self.TrackingToAll==true then
+MESSAGE:New("Missile tracking to all players ON",15,"Menu"):ToAll()
+else
+MESSAGE:New("Missile tracking to all players OFF",15,"Menu"):ToAll()
+end
+return self
+end
+function MISSILETRAINER:InitTrackingOnOff(TrackingOnOff)
+self:F(TrackingOnOff)
+self.TrackingOnOff=TrackingOnOff
+if self.TrackingOnOff==true then
+MESSAGE:New("Missile tracking ON",15,"Menu"):ToAll()
+else
+MESSAGE:New("Missile tracking OFF",15,"Menu"):ToAll()
+end
+return self
+end
+function MISSILETRAINER:InitTrackingFrequency(TrackingFrequency)
+self:F(TrackingFrequency)
+self.TrackingFrequency=self.TrackingFrequency+TrackingFrequency
+if self.TrackingFrequency<0.5 then
+self.TrackingFrequency=0.5
+end
+if self.TrackingFrequency then
+MESSAGE:New("Missile tracking frequency is "..self.TrackingFrequency.." seconds.",15,"Menu"):ToAll()
+end
+return self
+end
+function MISSILETRAINER:InitAlertsToAll(AlertsToAll)
+self:F(AlertsToAll)
+self.AlertsToAll=AlertsToAll
+if self.AlertsToAll==true then
+MESSAGE:New("Alerts to all players ON",15,"Menu"):ToAll()
+else
+MESSAGE:New("Alerts to all players OFF",15,"Menu"):ToAll()
+end
+return self
+end
+function MISSILETRAINER:InitAlertsHitsOnOff(AlertsHitsOnOff)
+self:F(AlertsHitsOnOff)
+self.AlertsHitsOnOff=AlertsHitsOnOff
+if self.AlertsHitsOnOff==true then
+MESSAGE:New("Alerts Hits ON",15,"Menu"):ToAll()
+else
+MESSAGE:New("Alerts Hits OFF",15,"Menu"):ToAll()
+end
+return self
+end
+function MISSILETRAINER:InitAlertsLaunchesOnOff(AlertsLaunchesOnOff)
+self:F(AlertsLaunchesOnOff)
+self.AlertsLaunchesOnOff=AlertsLaunchesOnOff
+if self.AlertsLaunchesOnOff==true then
+MESSAGE:New("Alerts Launches ON",15,"Menu"):ToAll()
+else
+MESSAGE:New("Alerts Launches OFF",15,"Menu"):ToAll()
+end
+return self
+end
+function MISSILETRAINER:InitRangeOnOff(DetailsRangeOnOff)
+self:F(DetailsRangeOnOff)
+self.DetailsRangeOnOff=DetailsRangeOnOff
+if self.DetailsRangeOnOff==true then
+MESSAGE:New("Range display ON",15,"Menu"):ToAll()
+else
+MESSAGE:New("Range display OFF",15,"Menu"):ToAll()
+end
+return self
+end
+function MISSILETRAINER:InitBearingOnOff(DetailsBearingOnOff)
+self:F(DetailsBearingOnOff)
+self.DetailsBearingOnOff=DetailsBearingOnOff
+if self.DetailsBearingOnOff==true then
+MESSAGE:New("Bearing display OFF",15,"Menu"):ToAll()
+else
+MESSAGE:New("Bearing display OFF",15,"Menu"):ToAll()
+end
+return self
+end
+function MISSILETRAINER:InitMenusOnOff(MenusOnOff)
+self:F(MenusOnOff)
+self.MenusOnOff=MenusOnOff
+if self.MenusOnOff==true then
+MESSAGE:New("Menus are ENABLED (only when a player rejoins a slot)",15,"Menu"):ToAll()
+else
+MESSAGE:New("Menus are DISABLED",15,"Menu"):ToAll()
+end
+return self
+end
+function MISSILETRAINER._MenuMessages(MenuParameters)
+local self=MenuParameters.MenuSelf
+if MenuParameters.MessagesOnOff~=nil then
+self:InitMessagesOnOff(MenuParameters.MessagesOnOff)
+end
+if MenuParameters.TrackingToAll~=nil then
+self:InitTrackingToAll(MenuParameters.TrackingToAll)
+end
+if MenuParameters.TrackingOnOff~=nil then
+self:InitTrackingOnOff(MenuParameters.TrackingOnOff)
+end
+if MenuParameters.TrackingFrequency~=nil then
+self:InitTrackingFrequency(MenuParameters.TrackingFrequency)
+end
+if MenuParameters.AlertsToAll~=nil then
+self:InitAlertsToAll(MenuParameters.AlertsToAll)
+end
+if MenuParameters.AlertsHitsOnOff~=nil then
+self:InitAlertsHitsOnOff(MenuParameters.AlertsHitsOnOff)
+end
+if MenuParameters.AlertsLaunchesOnOff~=nil then
+self:InitAlertsLaunchesOnOff(MenuParameters.AlertsLaunchesOnOff)
+end
+if MenuParameters.DetailsRangeOnOff~=nil then
+self:InitRangeOnOff(MenuParameters.DetailsRangeOnOff)
+end
+if MenuParameters.DetailsBearingOnOff~=nil then
+self:InitBearingOnOff(MenuParameters.DetailsBearingOnOff)
+end
+if MenuParameters.Distance~=nil then
+self.Distance=MenuParameters.Distance
+MESSAGE:New("Hit detection distance set to "..(self.Distance*1000).." meters",15,"Menu"):ToAll()
+end
+end
+function MISSILETRAINER:OnEventShot(EVentData)
+self:F({EVentData})
+local TrainerSourceDCSUnit=EVentData.IniDCSUnit
+local TrainerSourceDCSUnitName=EVentData.IniDCSUnitName
+local TrainerWeapon=EVentData.Weapon
+local TrainerWeaponName=EVentData.WeaponName
+self:T("Missile Launched = "..TrainerWeaponName)
+local TrainerTargetDCSUnit=TrainerWeapon:getTarget()
+if TrainerTargetDCSUnit then
+local TrainerTargetDCSUnitName=Unit.getName(TrainerTargetDCSUnit)
+local TrainerTargetSkill=_DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill
+self:T(TrainerTargetDCSUnitName)
+local Client=self.DBClients:FindClient(TrainerTargetDCSUnitName)
+if Client then
+local TrainerSourceUnit=UNIT:Find(TrainerSourceDCSUnit)
+local TrainerTargetUnit=UNIT:Find(TrainerTargetDCSUnit)
+if self.MessagesOnOff==true and self.AlertsLaunchesOnOff==true then
+local Message=MESSAGE:New(
+string.format("%s launched a %s",
+TrainerSourceUnit:GetTypeName(),
+TrainerWeaponName
+)..self:_AddRange(Client,TrainerWeapon)..self:_AddBearing(Client,TrainerWeapon),5,"Launch Alert")
+if self.AlertsToAll then
+Message:ToAll()
+else
+Message:ToClient(Client)
+end
+end
+local ClientID=Client:GetID()
+self:T(ClientID)
+local MissileData={}
+MissileData.TrainerSourceUnit=TrainerSourceUnit
+MissileData.TrainerWeapon=TrainerWeapon
+MissileData.TrainerTargetUnit=TrainerTargetUnit
+MissileData.TrainerWeaponTypeName=TrainerWeapon:getTypeName()
+MissileData.TrainerWeaponLaunched=true
+table.insert(self.TrackingMissiles[ClientID].MissileData,MissileData)
+end
+else
+if(TrainerWeapon:getTypeName()=="9M311")then
+SCHEDULER:New(TrainerWeapon,TrainerWeapon.destroy,{},1)
+else
+end
+end
+end
+function MISSILETRAINER:_AddRange(Client,TrainerWeapon)
+local RangeText=""
+if self.DetailsRangeOnOff then
+local PositionMissile=TrainerWeapon:getPoint()
+local TargetVec3=Client:GetVec3()
+local Range=((PositionMissile.x-TargetVec3.x)^2+
+(PositionMissile.y-TargetVec3.y)^2+
+(PositionMissile.z-TargetVec3.z)^2
+)^0.5/1000
+RangeText=string.format(", at %4.2fkm",Range)
+end
+return RangeText
+end
+function MISSILETRAINER:_AddBearing(Client,TrainerWeapon)
+local BearingText=""
+if self.DetailsBearingOnOff then
+local PositionMissile=TrainerWeapon:getPoint()
+local TargetVec3=Client:GetVec3()
+self:T2({TargetVec3,PositionMissile})
+local DirectionVector={x=PositionMissile.x-TargetVec3.x,y=PositionMissile.y-TargetVec3.y,z=PositionMissile.z-TargetVec3.z}
+local DirectionRadians=math.atan2(DirectionVector.z,DirectionVector.x)
+if DirectionRadians<0 then
+DirectionRadians=DirectionRadians+2*math.pi
+end
+local DirectionDegrees=DirectionRadians*180/math.pi
+BearingText=string.format(", %d degrees",DirectionDegrees)
+end
+return BearingText
+end
+function MISSILETRAINER:_TrackMissiles()
+self:F2()
+local ShowMessages=false
+if self.MessagesOnOff and self.MessageLastTime+self.TrackingFrequency<=timer.getTime()then
+self.MessageLastTime=timer.getTime()
+ShowMessages=true
+end
+for ClientDataID,ClientData in pairs(self.TrackingMissiles)do
+local Client=ClientData.Client
+if Client and Client:IsAlive()then
+for MissileDataID,MissileData in pairs(ClientData.MissileData)do
+self:T3(MissileDataID)
+local TrainerSourceUnit=MissileData.TrainerSourceUnit
+local TrainerWeapon=MissileData.TrainerWeapon
+local TrainerTargetUnit=MissileData.TrainerTargetUnit
+local TrainerWeaponTypeName=MissileData.TrainerWeaponTypeName
+local TrainerWeaponLaunched=MissileData.TrainerWeaponLaunched
+if Client and Client:IsAlive()and TrainerSourceUnit and TrainerSourceUnit:IsAlive()and TrainerWeapon and TrainerWeapon:isExist()and TrainerTargetUnit and TrainerTargetUnit:IsAlive()then
+local PositionMissile=TrainerWeapon:getPosition().p
+local TargetVec3=Client:GetVec3()
+local Distance=((PositionMissile.x-TargetVec3.x)^2+
+(PositionMissile.y-TargetVec3.y)^2+
+(PositionMissile.z-TargetVec3.z)^2
+)^0.5/1000
+if Distance<=self.Distance then
+TrainerWeapon:destroy()
+if self.MessagesOnOff==true and self.AlertsHitsOnOff==true then
+self:T("killed")
+local Message=MESSAGE:New(
+string.format("%s launched by %s killed %s",
+TrainerWeapon:getTypeName(),
+TrainerSourceUnit:GetTypeName(),
+TrainerTargetUnit:GetPlayerName()
+),15,"Hit Alert")
+if self.AlertsToAll==true then
+Message:ToAll()
+else
+Message:ToClient(Client)
+end
+MissileData=nil
+table.remove(ClientData.MissileData,MissileDataID)
+self:T(ClientData.MissileData)
+end
+end
+else
+if not(TrainerWeapon and TrainerWeapon:isExist())then
+if self.MessagesOnOff==true and self.AlertsLaunchesOnOff==true then
+local Message=MESSAGE:New(
+string.format("%s launched by %s self destructed!",
+TrainerWeaponTypeName,
+TrainerSourceUnit:GetTypeName()
+),5,"Tracking")
+if self.AlertsToAll==true then
+Message:ToAll()
+else
+Message:ToClient(Client)
+end
+end
+MissileData=nil
+table.remove(ClientData.MissileData,MissileDataID)
+self:T(ClientData.MissileData)
+end
+end
+end
+else
+self.TrackingMissiles[ClientDataID]=nil
+end
+end
+if ShowMessages==true and self.MessagesOnOff==true and self.TrackingOnOff==true then
+for ClientDataID,ClientData in pairs(self.TrackingMissiles)do
+local Client=ClientData.Client
+ClientData.MessageToClient=""
+ClientData.MessageToAll=""
+for TrackingDataID,TrackingData in pairs(self.TrackingMissiles)do
+for MissileDataID,MissileData in pairs(TrackingData.MissileData)do
+local TrainerSourceUnit=MissileData.TrainerSourceUnit
+local TrainerWeapon=MissileData.TrainerWeapon
+local TrainerTargetUnit=MissileData.TrainerTargetUnit
+local TrainerWeaponTypeName=MissileData.TrainerWeaponTypeName
+local TrainerWeaponLaunched=MissileData.TrainerWeaponLaunched
+if Client and Client:IsAlive()and TrainerSourceUnit and TrainerSourceUnit:IsAlive()and TrainerWeapon and TrainerWeapon:isExist()and TrainerTargetUnit and TrainerTargetUnit:IsAlive()then
+if ShowMessages==true then
+local TrackingTo
+TrackingTo=string.format(" -> %s",
+TrainerWeaponTypeName
+)
+if ClientDataID==TrackingDataID then
+if ClientData.MessageToClient==""then
+ClientData.MessageToClient="Missiles to You:\n"
+end
+ClientData.MessageToClient=ClientData.MessageToClient..TrackingTo..self:_AddRange(ClientData.Client,TrainerWeapon)..self:_AddBearing(ClientData.Client,TrainerWeapon).."\n"
+else
+if self.TrackingToAll==true then
+if ClientData.MessageToAll==""then
+ClientData.MessageToAll="Missiles to other Players:\n"
+end
+ClientData.MessageToAll=ClientData.MessageToAll..TrackingTo..self:_AddRange(ClientData.Client,TrainerWeapon)..self:_AddBearing(ClientData.Client,TrainerWeapon).." ( "..TrainerTargetUnit:GetPlayerName().." )\n"
+end
+end
+end
+end
+end
+end
+if ClientData.MessageToClient~=""or ClientData.MessageToAll~=""then
+local Message=MESSAGE:New(ClientData.MessageToClient..ClientData.MessageToAll,1,"Tracking"):ToClient(Client)
+end
+end
+end
+return true
+end
+AIRBASEPOLICE_BASE={
+ClassName="AIRBASEPOLICE_BASE",
+SetClient=nil,
+Airbases=nil,
+AirbaseNames=nil,
+}
+function AIRBASEPOLICE_BASE:New(SetClient,Airbases)
+local self=BASE:Inherit(self,BASE:New())
+self:E({self.ClassName,SetClient,Airbases})
+self.SetClient=SetClient
+self.Airbases=Airbases
+for AirbaseID,Airbase in pairs(self.Airbases)do
+Airbase.ZoneBoundary=ZONE_POLYGON_BASE:New("Boundary",Airbase.PointsBoundary):SmokeZone(SMOKECOLOR.White):Flush()
+for PointsRunwayID,PointsRunway in pairs(Airbase.PointsRunways)do
+Airbase.ZoneRunways[PointsRunwayID]=ZONE_POLYGON_BASE:New("Runway "..PointsRunwayID,PointsRunway):SmokeZone(SMOKECOLOR.Red):Flush()
+end
+end
+self.SetClient:ForEachClient(
+function(Client)
+Client:SetState(self,"Speeding",false)
+Client:SetState(self,"Warnings",0)
+Client:SetState(self,"Taxi",false)
+end
+)
+self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{},0,2,0.05)
+return self
+end
+function AIRBASEPOLICE_BASE:Monitor(AirbaseNames)
+if AirbaseNames then
+if type(AirbaseNames)=="table"then
+self.AirbaseNames=AirbaseNames
+else
+self.AirbaseNames={AirbaseNames}
+end
+end
+end
+function AIRBASEPOLICE_BASE:_AirbaseMonitor()
+for AirbaseID,Airbase in pairs(self.Airbases)do
+if not self.AirbaseNames or self.AirbaseNames[AirbaseID]then
+self:E(AirbaseID)
+self.SetClient:ForEachClientInZone(Airbase.ZoneBoundary,
+function(Client)
+self:E(Client.UnitName)
+if Client:IsAlive()then
+local NotInRunwayZone=true
+for ZoneRunwayID,ZoneRunway in pairs(Airbase.ZoneRunways)do
+NotInRunwayZone=(Client:IsNotInZone(ZoneRunway)==true)and NotInRunwayZone or false
+end
+if NotInRunwayZone then
+local Taxi=self:GetState(self,"Taxi")
+self:E(Taxi)
+if Taxi==false then
+Client:Message("Welcome at "..AirbaseID..". The maximum taxiing speed is "..Airbase.MaximumSpeed" km/h.",20,"ATC")
+self:SetState(self,"Taxi",true)
+end
+local VelocityVec3=Client:GetVelocity()
+local Velocity=(VelocityVec3.x^2+VelocityVec3.y^2+VelocityVec3.z^2)^0.5
+local Velocity=Velocity*3.6
+local IsAboveRunway=Client:IsAboveRunway()
+local IsOnGround=Client:InAir()==false
+self:T(IsAboveRunway,IsOnGround)
+if IsAboveRunway and IsOnGround then
+if Velocity>Airbase.MaximumSpeed then
+local IsSpeeding=Client:GetState(self,"Speeding")
+if IsSpeeding==true then
+local SpeedingWarnings=Client:GetState(self,"Warnings")
+self:T(SpeedingWarnings)
+if SpeedingWarnings<=3 then
+Client:Message("You are speeding on the taxiway! Slow down or you will be removed from this airbase! Your current velocity is "..string.format("%2.0f km/h",Velocity),5,"Warning "..SpeedingWarnings.." / 3")
+Client:SetState(self,"Warnings",SpeedingWarnings+1)
+else
+MESSAGE:New("Player "..Client:GetPlayerName().." is being damaged at the airbase, due to a speeding violation ...",10,"Airbase Police"):ToAll()
+local function DestroyUntilHeavilyDamaged(Client)
+local ClientCoord=Client:GetCoordinate()
+ClientCoord:Explosion(100)
+local Damage=Client:GetLife()
+local InitialLife=Client:GetLife0()
+MESSAGE:New("Player "..Client:GetPlayerName().." Damage ... "..Damage,5,"Airbase Police"):ToAll()
+if(Damage/InitialLife)*100<80 then
+Client:ScheduleStop(DestroyUntilHeavilyDamaged)
+end
+end
+Client:ScheduleOnce(1,DestroyUntilHeavilyDamaged,Client)
+trigger.action.setUserFlag("AIRCRAFT_"..Client:GetID(),100)
+Client:SetState(self,"Speeding",false)
+Client:SetState(self,"Warnings",0)
+end
+else
+Client:Message("You are speeding on the taxiway, slow down now! Your current velocity is "..string.format("%2.0f km/h",Velocity),5,"Attention! ")
+Client:SetState(self,"Speeding",true)
+Client:SetState(self,"Warnings",1)
+end
+else
+Client:SetState(self,"Speeding",false)
+Client:SetState(self,"Warnings",0)
+end
+end
+else
+Client:SetState(self,"Speeding",false)
+Client:SetState(self,"Warnings",0)
+local Taxi=self:GetState(self,"Taxi")
+if Taxi==true then
+Client:Message("You have progressed to the runway ... Await take-off clearance ...",20,"ATC")
+self:SetState(self,"Taxi",false)
+end
+end
+end
+end
+)
+end
+end
+return true
+end
+AIRBASEPOLICE_CAUCASUS={
+ClassName="AIRBASEPOLICE_CAUCASUS",
+Airbases={
+AnapaVityazevo={
+PointsBoundary={
+[1]={["y"]=242234.85714287,["x"]=-6616.5714285726,},
+[2]={["y"]=241060.57142858,["x"]=-5585.142857144,},
+[3]={["y"]=243806.2857143,["x"]=-3962.2857142868,},
+[4]={["y"]=245240.57142858,["x"]=-4816.5714285726,},
+[5]={["y"]=244783.42857144,["x"]=-5630.8571428583,},
+[6]={["y"]=243800.57142858,["x"]=-5065.142857144,},
+[7]={["y"]=242232.00000001,["x"]=-6622.2857142868,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=242140.57142858,["x"]=-6478.8571428583,},
+[2]={["y"]=242188.57142858,["x"]=-6522.0000000011,},
+[3]={["y"]=244124.2857143,["x"]=-4344.0000000011,},
+[4]={["y"]=244068.2857143,["x"]=-4296.5714285726,},
+[5]={["y"]=242140.57142858,["x"]=-6480.0000000011,}
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+Batumi={
+PointsBoundary={
+[1]={["y"]=617567.14285714,["x"]=-355313.14285715,},
+[2]={["y"]=616181.42857142,["x"]=-354800.28571429,},
+[3]={["y"]=616007.14285714,["x"]=-355128.85714286,},
+[4]={["y"]=618230,["x"]=-356914.57142858,},
+[5]={["y"]=618727.14285714,["x"]=-356166,},
+[6]={["y"]=617572.85714285,["x"]=-355308.85714286,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=616442.28571429,["x"]=-355090.28571429,},
+[2]={["y"]=618450.57142857,["x"]=-356522,},
+[3]={["y"]=618407.71428571,["x"]=-356584.85714286,},
+[4]={["y"]=618361.99999999,["x"]=-356554.85714286,},
+[5]={["y"]=618324.85714285,["x"]=-356599.14285715,},
+[6]={["y"]=618250.57142856,["x"]=-356543.42857143,},
+[7]={["y"]=618257.7142857,["x"]=-356496.28571429,},
+[8]={["y"]=618237.7142857,["x"]=-356459.14285715,},
+[9]={["y"]=616555.71428571,["x"]=-355258.85714286,},
+[10]={["y"]=616486.28571428,["x"]=-355280.57142858,},
+[11]={["y"]=616410.57142856,["x"]=-355227.71428572,},
+[12]={["y"]=616441.99999999,["x"]=-355179.14285715,},
+[13]={["y"]=616401.99999999,["x"]=-355147.71428572,},
+[14]={["y"]=616441.42857142,["x"]=-355092.57142858,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+Beslan={
+PointsBoundary={
+[1]={["y"]=842082.57142857,["x"]=-148445.14285715,},
+[2]={["y"]=845237.71428572,["x"]=-148639.71428572,},
+[3]={["y"]=845232,["x"]=-148765.42857143,},
+[4]={["y"]=844220.57142857,["x"]=-149168.28571429,},
+[5]={["y"]=843274.85714286,["x"]=-149125.42857143,},
+[6]={["y"]=842077.71428572,["x"]=-148554,},
+[7]={["y"]=842083.42857143,["x"]=-148445.42857143,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=842104.57142857,["x"]=-148460.57142857,},
+[2]={["y"]=845225.71428572,["x"]=-148656,},
+[3]={["y"]=845220.57142858,["x"]=-148750,},
+[4]={["y"]=842098.85714286,["x"]=-148556.28571429,},
+[5]={["y"]=842104,["x"]=-148460.28571429,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+Gelendzhik={
+PointsBoundary={
+[1]={["y"]=297856.00000001,["x"]=-51151.428571429,},
+[2]={["y"]=299044.57142858,["x"]=-49720.000000001,},
+[3]={["y"]=298861.71428572,["x"]=-49580.000000001,},
+[4]={["y"]=298198.85714286,["x"]=-49842.857142858,},
+[5]={["y"]=297990.28571429,["x"]=-50151.428571429,},
+[6]={["y"]=297696.00000001,["x"]=-51054.285714286,},
+[7]={["y"]=297850.28571429,["x"]=-51160.000000001,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=297834.00000001,["x"]=-51107.428571429,},
+[2]={["y"]=297786.57142858,["x"]=-51068.857142858,},
+[3]={["y"]=298946.57142858,["x"]=-49686.000000001,},
+[4]={["y"]=298993.14285715,["x"]=-49725.714285715,},
+[5]={["y"]=297835.14285715,["x"]=-51107.714285715,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+Gudauta={
+PointsBoundary={
+[1]={["y"]=517246.57142857,["x"]=-197850.28571429,},
+[2]={["y"]=516749.42857142,["x"]=-198070.28571429,},
+[3]={["y"]=515755.14285714,["x"]=-197598.85714286,},
+[4]={["y"]=515369.42857142,["x"]=-196538.85714286,},
+[5]={["y"]=515623.71428571,["x"]=-195618.85714286,},
+[6]={["y"]=515946.57142857,["x"]=-195510.28571429,},
+[7]={["y"]=517243.71428571,["x"]=-197858.85714286,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=517096.57142857,["x"]=-197804.57142857,},
+[2]={["y"]=515880.85714285,["x"]=-195590.28571429,},
+[3]={["y"]=515812.28571428,["x"]=-195628.85714286,},
+[4]={["y"]=517036.57142857,["x"]=-197834.57142857,},
+[5]={["y"]=517097.99999999,["x"]=-197807.42857143,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+Kobuleti={
+PointsBoundary={
+[1]={["y"]=634427.71428571,["x"]=-318290.28571429,},
+[2]={["y"]=635033.42857143,["x"]=-317550.2857143,},
+[3]={["y"]=635864.85714286,["x"]=-317333.14285715,},
+[4]={["y"]=636967.71428571,["x"]=-317261.71428572,},
+[5]={["y"]=637144.85714286,["x"]=-317913.14285715,},
+[6]={["y"]=634630.57142857,["x"]=-318687.42857144,},
+[7]={["y"]=634424.85714286,["x"]=-318290.2857143,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=634509.71428571,["x"]=-318339.42857144,},
+[2]={["y"]=636767.42857143,["x"]=-317516.57142858,},
+[3]={["y"]=636790,["x"]=-317575.71428572,},
+[4]={["y"]=634531.42857143,["x"]=-318398.00000001,},
+[5]={["y"]=634510.28571429,["x"]=-318339.71428572,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+KrasnodarCenter={
+PointsBoundary={
+[1]={["y"]=366680.28571429,["x"]=11699.142857142,},
+[2]={["y"]=366654.28571429,["x"]=11225.142857142,},
+[3]={["y"]=367497.14285715,["x"]=11082.285714285,},
+[4]={["y"]=368025.71428572,["x"]=10396.57142857,},
+[5]={["y"]=369854.28571429,["x"]=11367.999999999,},
+[6]={["y"]=369840.00000001,["x"]=11910.857142856,},
+[7]={["y"]=366682.57142858,["x"]=11697.999999999,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=369205.42857144,["x"]=11789.142857142,},
+[2]={["y"]=369209.71428572,["x"]=11714.857142856,},
+[3]={["y"]=366699.71428572,["x"]=11581.714285713,},
+[4]={["y"]=366698.28571429,["x"]=11659.142857142,},
+[5]={["y"]=369208.85714286,["x"]=11788.57142857,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+KrasnodarPashkovsky={
+PointsBoundary={
+[1]={["y"]=386754,["x"]=6476.5714285703,},
+[2]={["y"]=389182.57142858,["x"]=8722.2857142846,},
+[3]={["y"]=388832.57142858,["x"]=9086.5714285703,},
+[4]={["y"]=386961.14285715,["x"]=7707.9999999989,},
+[5]={["y"]=385404,["x"]=9179.4285714274,},
+[6]={["y"]=383239.71428572,["x"]=7386.5714285703,},
+[7]={["y"]=383954,["x"]=6486.5714285703,},
+[8]={["y"]=385775.42857143,["x"]=8097.9999999989,},
+[9]={["y"]=386804,["x"]=7319.4285714274,},
+[10]={["y"]=386375.42857143,["x"]=6797.9999999989,},
+[11]={["y"]=386746.85714286,["x"]=6472.2857142846,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=385891.14285715,["x"]=8416.5714285703,},
+[2]={["y"]=385842.28571429,["x"]=8467.9999999989,},
+[3]={["y"]=384180.85714286,["x"]=6917.1428571417,},
+[4]={["y"]=384228.57142858,["x"]=6867.7142857132,},
+[5]={["y"]=385891.14285715,["x"]=8416.5714285703,},
+},
+[2]={
+[1]={["y"]=386714.85714286,["x"]=6674.857142856,},
+[2]={["y"]=386757.71428572,["x"]=6627.7142857132,},
+[3]={["y"]=389028.57142858,["x"]=8741.4285714275,},
+[4]={["y"]=388981.71428572,["x"]=8790.5714285703,},
+[5]={["y"]=386714.57142858,["x"]=6674.5714285703,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+Krymsk={
+PointsBoundary={
+[1]={["y"]=293338.00000001,["x"]=-7575.4285714297,},
+[2]={["y"]=295199.42857144,["x"]=-5434.0000000011,},
+[3]={["y"]=295595.14285715,["x"]=-6239.7142857154,},
+[4]={["y"]=294152.2857143,["x"]=-8325.4285714297,},
+[5]={["y"]=293345.14285715,["x"]=-7596.8571428582,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=293522.00000001,["x"]=-7567.4285714297,},
+[2]={["y"]=293578.57142858,["x"]=-7616.0000000011,},
+[3]={["y"]=295246.00000001,["x"]=-5591.142857144,},
+[4]={["y"]=295187.71428573,["x"]=-5546.0000000011,},
+[5]={["y"]=293523.14285715,["x"]=-7568.2857142868,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+Kutaisi={
+PointsBoundary={
+[1]={["y"]=682087.42857143,["x"]=-284512.85714286,},
+[2]={["y"]=685387.42857143,["x"]=-283662.85714286,},
+[3]={["y"]=685294.57142857,["x"]=-284977.14285715,},
+[4]={["y"]=682744.57142857,["x"]=-286505.71428572,},
+[5]={["y"]=682094.57142857,["x"]=-284527.14285715,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=682638,["x"]=-285202.28571429,},
+[2]={["y"]=685050.28571429,["x"]=-284507.42857144,},
+[3]={["y"]=685068.85714286,["x"]=-284578.85714286,},
+[4]={["y"]=682657.42857143,["x"]=-285264.28571429,},
+[5]={["y"]=682638.28571429,["x"]=-285202.85714286,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+MaykopKhanskaya={
+PointsBoundary={
+[1]={["y"]=456876.28571429,["x"]=-27665.42857143,},
+[2]={["y"]=457800,["x"]=-28392.857142858,},
+[3]={["y"]=459368.57142857,["x"]=-26378.571428573,},
+[4]={["y"]=459425.71428572,["x"]=-25242.857142858,},
+[5]={["y"]=458961.42857143,["x"]=-24964.285714287,},
+[6]={["y"]=456878.57142857,["x"]=-27667.714285715,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=457005.42857143,["x"]=-27668.000000001,},
+[2]={["y"]=459028.85714286,["x"]=-25168.857142858,},
+[3]={["y"]=459082.57142857,["x"]=-25216.857142858,},
+[4]={["y"]=457060,["x"]=-27714.285714287,},
+[5]={["y"]=457004.57142857,["x"]=-27669.714285715,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+MineralnyeVody={
+PointsBoundary={
+[1]={["y"]=703857.14285714,["x"]=-50226.000000002,},
+[2]={["y"]=707385.71428571,["x"]=-51911.714285716,},
+[3]={["y"]=707595.71428571,["x"]=-51434.857142859,},
+[4]={["y"]=707900,["x"]=-51568.857142859,},
+[5]={["y"]=707542.85714286,["x"]=-52326.000000002,},
+[6]={["y"]=706628.57142857,["x"]=-52568.857142859,},
+[7]={["y"]=705142.85714286,["x"]=-51790.285714288,},
+[8]={["y"]=703678.57142857,["x"]=-50611.714285716,},
+[9]={["y"]=703857.42857143,["x"]=-50226.857142859,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=703904,["x"]=-50352.571428573,},
+[2]={["y"]=707596.28571429,["x"]=-52094.571428573,},
+[3]={["y"]=707560.57142858,["x"]=-52161.714285716,},
+[4]={["y"]=703871.71428572,["x"]=-50420.571428573,},
+[5]={["y"]=703902,["x"]=-50352.000000002,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+Mozdok={
+PointsBoundary={
+[1]={["y"]=832123.42857143,["x"]=-83608.571428573,},
+[2]={["y"]=835916.28571429,["x"]=-83144.285714288,},
+[3]={["y"]=835474.28571429,["x"]=-84170.571428573,},
+[4]={["y"]=832911.42857143,["x"]=-84470.571428573,},
+[5]={["y"]=832487.71428572,["x"]=-85565.714285716,},
+[6]={["y"]=831573.42857143,["x"]=-85351.42857143,},
+[7]={["y"]=832123.71428572,["x"]=-83610.285714288,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=832201.14285715,["x"]=-83699.428571431,},
+[2]={["y"]=832212.57142857,["x"]=-83780.571428574,},
+[3]={["y"]=835730.28571429,["x"]=-83335.714285717,},
+[4]={["y"]=835718.85714286,["x"]=-83246.571428574,},
+[5]={["y"]=832200.57142857,["x"]=-83700.000000002,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+Nalchik={
+PointsBoundary={
+[1]={["y"]=759370,["x"]=-125502.85714286,},
+[2]={["y"]=761384.28571429,["x"]=-124177.14285714,},
+[3]={["y"]=761472.85714286,["x"]=-124325.71428572,},
+[4]={["y"]=761092.85714286,["x"]=-125048.57142857,},
+[5]={["y"]=760295.71428572,["x"]=-125685.71428572,},
+[6]={["y"]=759444.28571429,["x"]=-125734.28571429,},
+[7]={["y"]=759375.71428572,["x"]=-125511.42857143,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=759454.28571429,["x"]=-125551.42857143,},
+[2]={["y"]=759492.85714286,["x"]=-125610.85714286,},
+[3]={["y"]=761406.28571429,["x"]=-124304.28571429,},
+[4]={["y"]=761361.14285714,["x"]=-124239.71428572,},
+[5]={["y"]=759456,["x"]=-125552.57142857,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+Novorossiysk={
+PointsBoundary={
+[1]={["y"]=278677.71428573,["x"]=-41656.571428572,},
+[2]={["y"]=278446.2857143,["x"]=-41453.714285715,},
+[3]={["y"]=278989.14285716,["x"]=-40188.000000001,},
+[4]={["y"]=279717.71428573,["x"]=-39968.000000001,},
+[5]={["y"]=280020.57142859,["x"]=-40208.000000001,},
+[6]={["y"]=278674.85714287,["x"]=-41660.857142858,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=278673.14285716,["x"]=-41615.142857144,},
+[2]={["y"]=278625.42857144,["x"]=-41570.571428572,},
+[3]={["y"]=279835.42857144,["x"]=-40226.000000001,},
+[4]={["y"]=279882.2857143,["x"]=-40270.000000001,},
+[5]={["y"]=278672.00000001,["x"]=-41614.857142858,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+SenakiKolkhi={
+PointsBoundary={
+[1]={["y"]=646036.57142857,["x"]=-281778.85714286,},
+[2]={["y"]=646045.14285714,["x"]=-281191.71428571,},
+[3]={["y"]=647032.28571429,["x"]=-280598.85714285,},
+[4]={["y"]=647669.42857143,["x"]=-281273.14285714,},
+[5]={["y"]=648323.71428571,["x"]=-281370.28571428,},
+[6]={["y"]=648520.85714286,["x"]=-281978.85714285,},
+[7]={["y"]=646039.42857143,["x"]=-281783.14285714,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=646060.85714285,["x"]=-281736,},
+[2]={["y"]=646056.57142857,["x"]=-281631.71428571,},
+[3]={["y"]=648442.28571428,["x"]=-281840.28571428,},
+[4]={["y"]=648432.28571428,["x"]=-281918.85714286,},
+[5]={["y"]=646063.71428571,["x"]=-281738.85714286,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+SochiAdler={
+PointsBoundary={
+[1]={["y"]=460642.28571428,["x"]=-164861.71428571,},
+[2]={["y"]=462820.85714285,["x"]=-163368.85714286,},
+[3]={["y"]=463649.42857142,["x"]=-163340.28571429,},
+[4]={["y"]=463835.14285714,["x"]=-164040.28571429,},
+[5]={["y"]=462535.14285714,["x"]=-165654.57142857,},
+[6]={["y"]=460678,["x"]=-165247.42857143,},
+[7]={["y"]=460635.14285714,["x"]=-164876,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=460831.42857143,["x"]=-165180,},
+[2]={["y"]=460878.57142857,["x"]=-165257.14285714,},
+[3]={["y"]=463663.71428571,["x"]=-163793.14285714,},
+[4]={["y"]=463612.28571428,["x"]=-163697.42857143,},
+[5]={["y"]=460831.42857143,["x"]=-165177.14285714,},
+},
+[2]={
+[1]={["y"]=460831.42857143,["x"]=-165180,},
+[2]={["y"]=460878.57142857,["x"]=-165257.14285714,},
+[3]={["y"]=463663.71428571,["x"]=-163793.14285714,},
+[4]={["y"]=463612.28571428,["x"]=-163697.42857143,},
+[5]={["y"]=460831.42857143,["x"]=-165177.14285714,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+Soganlug={
+PointsBoundary={
+[1]={["y"]=894530.85714286,["x"]=-316928.28571428,},
+[2]={["y"]=896422.28571428,["x"]=-318622.57142857,},
+[3]={["y"]=896090.85714286,["x"]=-318934,},
+[4]={["y"]=894019.42857143,["x"]=-317119.71428571,},
+[5]={["y"]=894533.71428571,["x"]=-316925.42857143,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=894525.71428571,["x"]=-316964,},
+[2]={["y"]=896363.14285714,["x"]=-318634.28571428,},
+[3]={["y"]=896299.14285714,["x"]=-318702.85714286,},
+[4]={["y"]=894464,["x"]=-317031.71428571,},
+[5]={["y"]=894524.57142857,["x"]=-316963.71428571,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+SukhumiBabushara={
+PointsBoundary={
+[1]={["y"]=562541.14285714,["x"]=-219852.28571429,},
+[2]={["y"]=562691.14285714,["x"]=-219395.14285714,},
+[3]={["y"]=564326.85714286,["x"]=-219523.71428571,},
+[4]={["y"]=566262.57142857,["x"]=-221166.57142857,},
+[5]={["y"]=566069.71428571,["x"]=-221580.85714286,},
+[6]={["y"]=562534,["x"]=-219873.71428571,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=562684,["x"]=-219779.71428571,},
+[2]={["y"]=562717.71428571,["x"]=-219718,},
+[3]={["y"]=566046.85714286,["x"]=-221376.57142857,},
+[4]={["y"]=566012.28571428,["x"]=-221446.57142857,},
+[5]={["y"]=562684.57142857,["x"]=-219782.57142857,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+TbilisiLochini={
+PointsBoundary={
+[1]={["y"]=895172.85714286,["x"]=-314667.42857143,},
+[2]={["y"]=895337.42857143,["x"]=-314143.14285714,},
+[3]={["y"]=895990.28571429,["x"]=-314036,},
+[4]={["y"]=897730.28571429,["x"]=-315284.57142857,},
+[5]={["y"]=897901.71428571,["x"]=-316284.57142857,},
+[6]={["y"]=897684.57142857,["x"]=-316618.85714286,},
+[7]={["y"]=895173.14285714,["x"]=-314667.42857143,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=895261.14285715,["x"]=-314652.28571428,},
+[2]={["y"]=897654.57142857,["x"]=-316523.14285714,},
+[3]={["y"]=897711.71428571,["x"]=-316450.28571429,},
+[4]={["y"]=895327.42857143,["x"]=-314568.85714286,},
+[5]={["y"]=895261.71428572,["x"]=-314656,},
+},
+[2]={
+[1]={["y"]=895605.71428572,["x"]=-314724.57142857,},
+[2]={["y"]=897639.71428572,["x"]=-316148,},
+[3]={["y"]=897683.42857143,["x"]=-316087.14285714,},
+[4]={["y"]=895650,["x"]=-314660,},
+[5]={["y"]=895606,["x"]=-314724.85714286,}
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+Vaziani={
+PointsBoundary={
+[1]={["y"]=902122,["x"]=-318163.71428572,},
+[2]={["y"]=902678.57142857,["x"]=-317594,},
+[3]={["y"]=903275.71428571,["x"]=-317405.42857143,},
+[4]={["y"]=903418.57142857,["x"]=-317891.14285714,},
+[5]={["y"]=904292.85714286,["x"]=-318748.28571429,},
+[6]={["y"]=904542,["x"]=-319740.85714286,},
+[7]={["y"]=904042,["x"]=-320166.57142857,},
+[8]={["y"]=902121.42857143,["x"]=-318164.85714286,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=902239.14285714,["x"]=-318190.85714286,},
+[2]={["y"]=904014.28571428,["x"]=-319994.57142857,},
+[3]={["y"]=904064.85714285,["x"]=-319945.14285715,},
+[4]={["y"]=902294.57142857,["x"]=-318146,},
+[5]={["y"]=902247.71428571,["x"]=-318190.85714286,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+},
+}
+function AIRBASEPOLICE_CAUCASUS:New(SetClient)
+local self=BASE:Inherit(self,AIRBASEPOLICE_BASE:New(SetClient,self.Airbases))
+return self
+end
+AIRBASEPOLICE_NEVADA={
+ClassName="AIRBASEPOLICE_NEVADA",
+Airbases={
+Nellis={
+PointsBoundary={
+[1]={["y"]=-17814.714285714,["x"]=-399823.14285714,},
+[2]={["y"]=-16875.857142857,["x"]=-398763.14285714,},
+[3]={["y"]=-16251.571428571,["x"]=-398988.85714286,},
+[4]={["y"]=-16163,["x"]=-398693.14285714,},
+[5]={["y"]=-16328.714285714,["x"]=-398034.57142857,},
+[6]={["y"]=-15943,["x"]=-397571.71428571,},
+[7]={["y"]=-15711.571428571,["x"]=-397551.71428571,},
+[8]={["y"]=-15748.714285714,["x"]=-396806,},
+[9]={["y"]=-16288.714285714,["x"]=-396517.42857143,},
+[10]={["y"]=-16751.571428571,["x"]=-396308.85714286,},
+[11]={["y"]=-17263,["x"]=-396234.57142857,},
+[12]={["y"]=-17577.285714286,["x"]=-396640.28571429,},
+[13]={["y"]=-17614.428571429,["x"]=-397400.28571429,},
+[14]={["y"]=-19405.857142857,["x"]=-399428.85714286,},
+[15]={["y"]=-19234.428571429,["x"]=-399683.14285714,},
+[16]={["y"]=-18708.714285714,["x"]=-399408.85714286,},
+[17]={["y"]=-18397.285714286,["x"]=-399657.42857143,},
+[18]={["y"]=-17814.428571429,["x"]=-399823.42857143,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=-18687,["x"]=-399380.28571429,},
+[2]={["y"]=-18620.714285714,["x"]=-399436.85714286,},
+[3]={["y"]=-16217.857142857,["x"]=-396596.85714286,},
+[4]={["y"]=-16300.142857143,["x"]=-396530,},
+[5]={["y"]=-18687,["x"]=-399380.85714286,},
+},
+[2]={
+[1]={["y"]=-18451.571428572,["x"]=-399580.57142857,},
+[2]={["y"]=-18392.142857143,["x"]=-399628.57142857,},
+[3]={["y"]=-16011,["x"]=-396806.85714286,},
+[4]={["y"]=-16074.714285714,["x"]=-396751.71428572,},
+[5]={["y"]=-18451.571428572,["x"]=-399580.85714285,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+McCarran={
+PointsBoundary={
+[1]={["y"]=-29455.285714286,["x"]=-416277.42857142,},
+[2]={["y"]=-28860.142857143,["x"]=-416492,},
+[3]={["y"]=-25044.428571429,["x"]=-416344.85714285,},
+[4]={["y"]=-24580.142857143,["x"]=-415959.14285714,},
+[5]={["y"]=-25073,["x"]=-415630.57142857,},
+[6]={["y"]=-25087.285714286,["x"]=-415130.57142857,},
+[7]={["y"]=-25830.142857143,["x"]=-414866.28571428,},
+[8]={["y"]=-26658.714285715,["x"]=-414880.57142857,},
+[9]={["y"]=-26973,["x"]=-415273.42857142,},
+[10]={["y"]=-27380.142857143,["x"]=-415187.71428571,},
+[11]={["y"]=-27715.857142857,["x"]=-414144.85714285,},
+[12]={["y"]=-27551.571428572,["x"]=-413473.42857142,},
+[13]={["y"]=-28630.142857143,["x"]=-413201.99999999,},
+[14]={["y"]=-29494.428571429,["x"]=-415437.71428571,},
+[15]={["y"]=-29455.571428572,["x"]=-416277.71428571,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=-29408.428571429,["x"]=-416016.28571428,},
+[2]={["y"]=-29408.142857144,["x"]=-416105.42857142,},
+[3]={["y"]=-24680.714285715,["x"]=-416003.14285713,},
+[4]={["y"]=-24681.857142858,["x"]=-415926.57142856,},
+[5]={["y"]=-29408.42857143,["x"]=-416016.57142856,},
+},
+[2]={
+[1]={["y"]=-28575.571428572,["x"]=-416303.14285713,},
+[2]={["y"]=-28575.571428572,["x"]=-416382.57142856,},
+[3]={["y"]=-25111.000000001,["x"]=-416309.7142857,},
+[4]={["y"]=-25111.000000001,["x"]=-416249.14285713,},
+[5]={["y"]=-28575.571428572,["x"]=-416303.7142857,},
+},
+[3]={
+[1]={["y"]=-29331.000000001,["x"]=-416275.42857141,},
+[2]={["y"]=-29259.000000001,["x"]=-416306.85714284,},
+[3]={["y"]=-28005.571428572,["x"]=-413449.7142857,},
+[4]={["y"]=-28068.714285715,["x"]=-413422.85714284,},
+[5]={["y"]=-29331.000000001,["x"]=-416275.7142857,},
+},
+[4]={
+[1]={["y"]=-29073.285714286,["x"]=-416386.57142856,},
+[2]={["y"]=-28997.285714286,["x"]=-416417.42857141,},
+[3]={["y"]=-27697.571428572,["x"]=-413464.57142856,},
+[4]={["y"]=-27767.857142858,["x"]=-413434.28571427,},
+[5]={["y"]=-29073.000000001,["x"]=-416386.85714284,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+Creech={
+PointsBoundary={
+[1]={["y"]=-74522.714285715,["x"]=-360887.99999998,},
+[2]={["y"]=-74197,["x"]=-360556.57142855,},
+[3]={["y"]=-74402.714285715,["x"]=-359639.42857141,},
+[4]={["y"]=-74637,["x"]=-359279.42857141,},
+[5]={["y"]=-75759.857142857,["x"]=-359005.14285712,},
+[6]={["y"]=-75834.142857143,["x"]=-359045.14285712,},
+[7]={["y"]=-75902.714285714,["x"]=-359782.28571427,},
+[8]={["y"]=-76099.857142857,["x"]=-360399.42857141,},
+[9]={["y"]=-77314.142857143,["x"]=-360219.42857141,},
+[10]={["y"]=-77728.428571429,["x"]=-360445.14285713,},
+[11]={["y"]=-77585.571428571,["x"]=-360585.14285713,},
+[12]={["y"]=-76471.285714286,["x"]=-360819.42857141,},
+[13]={["y"]=-76325.571428571,["x"]=-360942.28571427,},
+[14]={["y"]=-74671.857142857,["x"]=-360927.7142857,},
+[15]={["y"]=-74522.714285714,["x"]=-360888.85714284,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=-74237.571428571,["x"]=-360591.7142857,},
+[2]={["y"]=-74234.428571429,["x"]=-360493.71428571,},
+[3]={["y"]=-77605.285714286,["x"]=-360399.14285713,},
+[4]={["y"]=-77608.714285715,["x"]=-360498.85714285,},
+[5]={["y"]=-74237.857142857,["x"]=-360591.7142857,},
+},
+[2]={
+[1]={["y"]=-75807.571428572,["x"]=-359073.42857142,},
+[2]={["y"]=-74770.142857144,["x"]=-360581.71428571,},
+[3]={["y"]=-74641.285714287,["x"]=-360585.42857142,},
+[4]={["y"]=-75734.142857144,["x"]=-359023.14285714,},
+[5]={["y"]=-75807.285714287,["x"]=-359073.42857142,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+GroomLake={
+PointsBoundary={
+[1]={["y"]=-88916.714285714,["x"]=-289102.28571425,},
+[2]={["y"]=-87023.571428572,["x"]=-290388.57142857,},
+[3]={["y"]=-85916.428571429,["x"]=-290674.28571428,},
+[4]={["y"]=-87645.000000001,["x"]=-286567.14285714,},
+[5]={["y"]=-88380.714285715,["x"]=-286388.57142857,},
+[6]={["y"]=-89670.714285715,["x"]=-283524.28571428,},
+[7]={["y"]=-89797.857142858,["x"]=-283567.14285714,},
+[8]={["y"]=-88635.000000001,["x"]=-286749.99999999,},
+[9]={["y"]=-89177.857142858,["x"]=-287207.14285714,},
+[10]={["y"]=-89092.142857144,["x"]=-288892.85714285,},
+[11]={["y"]=-88917.000000001,["x"]=-289102.85714285,},
+},
+PointsRunways={
+[1]={
+[1]={["y"]=-86039.000000001,["x"]=-290606.28571428,},
+[2]={["y"]=-85965.285714287,["x"]=-290573.99999999,},
+[3]={["y"]=-87692.714285715,["x"]=-286634.85714285,},
+[4]={["y"]=-87756.714285715,["x"]=-286663.99999999,},
+[5]={["y"]=-86038.714285715,["x"]=-290606.85714285,},
+},
+[2]={
+[1]={["y"]=-86808.428571429,["x"]=-290375.7142857,},
+[2]={["y"]=-86732.714285715,["x"]=-290344.28571427,},
+[3]={["y"]=-89672.714285714,["x"]=-283546.57142855,},
+[4]={["y"]=-89772.142857143,["x"]=-283587.71428569,},
+[5]={["y"]=-86808.142857143,["x"]=-290375.7142857,},
+},
+},
+ZoneBoundary={},
+ZoneRunways={},
+MaximumSpeed=50,
+},
+},
+}
+function AIRBASEPOLICE_NEVADA:New(SetClient)
+local self=BASE:Inherit(self,AIRBASEPOLICE_BASE:New(SetClient,self.Airbases))
+end
+do
+DETECTION_BASE={
+ClassName="DETECTION_BASE",
+DetectionSetGroup=nil,
+DetectionRange=nil,
+DetectedObjects={},
+DetectionRun=0,
+DetectedObjectsIdentified={},
+DetectedItems={},
+}
+function DETECTION_BASE:New(DetectionSetGroup)
+local self=BASE:Inherit(self,FSM:New())
+self.DetectedItemCount=0
+self.DetectedItemMax=0
+self.DetectedItems={}
+self.DetectionSetGroup=DetectionSetGroup
+self.RefreshTimeInterval=30
+self:InitDetectVisual(nil)
+self:InitDetectOptical(nil)
+self:InitDetectRadar(nil)
+self:InitDetectRWR(nil)
+self:InitDetectIRST(nil)
+self:InitDetectDLINK(nil)
+self:FilterCategories({
+Unit.Category.AIRPLANE,
+Unit.Category.GROUND_UNIT,
+Unit.Category.HELICOPTER,
+Unit.Category.SHIP,
+Unit.Category.STRUCTURE
+})
+self:SetFriendliesRange(6000)
+self:SetStartState("Stopped")
+self:AddTransition("Stopped","Start","Detecting")
+self:AddTransition("Detecting","Detect","Detecting")
+self:AddTransition("Detecting","DetectionGroup","Detecting")
+self:AddTransition("Detecting","Detected","Detecting")
+self:AddTransition("*","Stop","Stopped")
+return self
+end
+do
+function DETECTION_BASE:onafterStart(From,Event,To)
+self:__Detect(1)
+end
+function DETECTION_BASE:onafterDetect(From,Event,To)
+self:E({From,Event,To})
+local DetectDelay=0.1
+self.DetectionCount=0
+self.DetectionRun=0
+self:UnIdentifyAllDetectedObjects()
+local DetectionTimeStamp=timer.getTime()
+for DetectionGroupID,DetectionGroupData in pairs(self.DetectionSetGroup:GetSet())do
+self:__DetectionGroup(DetectDelay,DetectionGroupData,DetectionTimeStamp)
+self.DetectionCount=self.DetectionCount+1
+DetectDelay=DetectDelay+1
+end
+end
+function DETECTION_BASE:onafterDetectionGroup(From,Event,To,DetectionGroup,DetectionTimeStamp)
+self:E({From,Event,To})
+self.DetectionRun=self.DetectionRun+1
+local HasDetectedObjects=false
+if DetectionGroup:IsAlive()then
+self:T({"DetectionGroup is Alive",DetectionGroup:GetName()})
+local DetectionGroupName=DetectionGroup:GetName()
+local DetectionUnit=DetectionGroup:GetUnit(1)
+local DetectedUnits={}
+local DetectedTargets=DetectionGroup:GetDetectedTargets(
+self.DetectVisual,
+self.DetectOptical,
+self.DetectRadar,
+self.DetectIRST,
+self.DetectRWR,
+self.DetectDLINK
+)
+self:F(DetectedTargets)
+for DetectionObjectID,Detection in pairs(DetectedTargets)do
+local DetectedObject=Detection.object
+if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then
+local TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity=DetectionUnit:IsTargetDetected(
+DetectedObject,
+self.DetectVisual,
+self.DetectOptical,
+self.DetectRadar,
+self.DetectIRST,
+self.DetectRWR,
+self.DetectDLINK
+)
+self:T2({TargetIsDetected=TargetIsDetected,TargetIsVisible=TargetIsVisible,TargetLastTime=TargetLastTime,TargetKnowType=TargetKnowType,TargetKnowDistance=TargetKnowDistance,TargetLastPos=TargetLastPos,TargetLastVelocity=TargetLastVelocity})
+local DetectionAccepted=true
+local DetectedObjectName=DetectedObject:getName()
+local DetectedObjectType=DetectedObject:getTypeName()
+local DetectedObjectVec3=DetectedObject:getPoint()
+local DetectedObjectVec2={x=DetectedObjectVec3.x,y=DetectedObjectVec3.z}
+local DetectionGroupVec3=DetectionGroup:GetVec3()
+local DetectionGroupVec2={x=DetectionGroupVec3.x,y=DetectionGroupVec3.z}
+local Distance=((DetectedObjectVec3.x-DetectionGroupVec3.x)^2+
+(DetectedObjectVec3.y-DetectionGroupVec3.y)^2+
+(DetectedObjectVec3.z-DetectionGroupVec3.z)^2
+)^0.5/1000
+local DetectedUnitCategory=DetectedObject:getDesc().category
+self:F({"Detected Target:",DetectionGroupName,DetectedObjectName,DetectedObjectType,Distance,DetectedUnitCategory})
+DetectionAccepted=self._.FilterCategories[DetectedUnitCategory]~=nil and DetectionAccepted or false
+if self.AcceptRange and Distance>self.AcceptRange then
+DetectionAccepted=false
+end
+if self.AcceptZones then
+for AcceptZoneID,AcceptZone in pairs(self.AcceptZones)do
+local AcceptZone=AcceptZone
+if AcceptZone:IsVec2InZone(DetectedObjectVec2)==false then
+DetectionAccepted=false
+end
+end
+end
+if self.RejectZones then
+for RejectZoneID,RejectZone in pairs(self.RejectZones)do
+local RejectZone=RejectZone
+if RejectZone:IsPointVec2InZone(DetectedObjectVec2)==true then
+DetectionAccepted=false
+end
+end
+end
+if not self.DetectedObjects[DetectedObjectName]and Detection.visible and self.DistanceProbability then
+local DistanceFactor=Distance/4
+local DistanceProbabilityReversed=(1-self.DistanceProbability)*DistanceFactor
+local DistanceProbability=1-DistanceProbabilityReversed
+DistanceProbability=DistanceProbability*30/300
+local Probability=math.random()
+self:T({Probability,DistanceProbability})
+if Probability>DistanceProbability then
+DetectionAccepted=false
+end
+end
+if not self.DetectedObjects[DetectedObjectName]and Detection.visible and self.AlphaAngleProbability then
+local NormalVec2={x=DetectedObjectVec2.x-DetectionGroupVec2.x,y=DetectedObjectVec2.y-DetectionGroupVec2.y}
+local AlphaAngle=math.atan2(NormalVec2.y,NormalVec2.x)
+local Sinus=math.sin(AlphaAngle)
+local AlphaAngleProbabilityReversed=(1-self.AlphaAngleProbability)*(1-Sinus)
+local AlphaAngleProbability=1-AlphaAngleProbabilityReversed
+AlphaAngleProbability=AlphaAngleProbability*30/300
+local Probability=math.random()
+self:T({Probability,AlphaAngleProbability})
+if Probability>AlphaAngleProbability then
+DetectionAccepted=false
+end
+end
+if not self.DetectedObjects[DetectedObjectName]and Detection.visible and self.ZoneProbability then
+for ZoneDataID,ZoneData in pairs(self.ZoneProbability)do
+self:E({ZoneData})
+local ZoneObject=ZoneData[1]
+local ZoneProbability=ZoneData[2]
+ZoneProbability=ZoneProbability*30/300
+if ZoneObject:IsPointVec2InZone(DetectedObjectVec2)==true then
+local Probability=math.random()
+self:T({Probability,ZoneProbability})
+if Probability>ZoneProbability then
+DetectionAccepted=false
+break
+end
+end
+end
+end
+if DetectionAccepted then
+HasDetectedObjects=true
+self.DetectedObjects[DetectedObjectName]=self.DetectedObjects[DetectedObjectName]or{}
+self.DetectedObjects[DetectedObjectName].Name=DetectedObjectName
+self.DetectedObjects[DetectedObjectName].IsDetected=TargetIsDetected
+self.DetectedObjects[DetectedObjectName].IsVisible=TargetIsVisible
+self.DetectedObjects[DetectedObjectName].LastTime=TargetLastTime
+self.DetectedObjects[DetectedObjectName].LastPos=TargetLastPos
+self.DetectedObjects[DetectedObjectName].LastVelocity=TargetLastVelocity
+self.DetectedObjects[DetectedObjectName].KnowType=TargetKnowType
+self.DetectedObjects[DetectedObjectName].KnowDistance=Detection.distance
+self.DetectedObjects[DetectedObjectName].Distance=Distance
+self.DetectedObjects[DetectedObjectName].DetectionTimeStamp=DetectionTimeStamp
+self:F({DetectedObject=self.DetectedObjects[DetectedObjectName]})
+local DetectedUnit=UNIT:FindByName(DetectedObjectName)
+DetectedUnits[DetectedObjectName]=DetectedUnit
+else
+if self.DetectedObjects[DetectedObjectName]then
+self.DetectedObjects[DetectedObjectName]=nil
+end
+end
+end
+self:T2(self.DetectedObjects)
+end
+if HasDetectedObjects then
+self:__Detected(0.1,DetectedUnits)
+end
+end
+if self.DetectionCount>0 and self.DetectionRun==self.DetectionCount then
+self:T("--> Create Detection Sets")
+for DetectedObjectName,DetectedObject in pairs(self.DetectedObjects)do
+if self.DetectedObjects[DetectedObjectName].IsDetected==true and self.DetectedObjects[DetectedObjectName].DetectionTimeStamp+60<=DetectionTimeStamp then
+self.DetectedObjects[DetectedObjectName].IsDetected=false
+end
+end
+self:CreateDetectionItems()
+for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do
+self:UpdateDetectedItemDetection(DetectedItem)
+self:CleanDetectionItem(DetectedItem,DetectedItemID)
+end
+self:__Detect(self.RefreshTimeInterval)
+end
+end
+end
+do
+function DETECTION_BASE:CleanDetectionItem(DetectedItem,DetectedItemID)
+self:F2()
+local DetectedSet=DetectedItem.Set
+if DetectedSet:Count()==0 then
+self:RemoveDetectedItem(DetectedItemID)
+end
+return self
+end
+function DETECTION_BASE:ForgetDetectedUnit(UnitName)
+self:F2()
+local DetectedItems=self:GetDetectedItems()
+for DetectedItemIndex,DetectedItem in pairs(DetectedItems)do
+local DetectedSet=self:GetDetectedSet(DetectedItemIndex)
+if DetectedSet then
+DetectedSet:RemoveUnitsByName(UnitName)
+end
+end
+return self
+end
+function DETECTION_BASE:CreateDetectionItems()
+self:F2()
+self:E("Error, in DETECTION_BASE class...")
+return self
+end
+end
+do
+function DETECTION_BASE:InitDetectVisual(DetectVisual)
+self.DetectVisual=DetectVisual
+return self
+end
+function DETECTION_BASE:InitDetectOptical(DetectOptical)
+self:F2()
+self.DetectOptical=DetectOptical
+return self
+end
+function DETECTION_BASE:InitDetectRadar(DetectRadar)
+self:F2()
+self.DetectRadar=DetectRadar
+return self
+end
+function DETECTION_BASE:InitDetectIRST(DetectIRST)
+self:F2()
+self.DetectIRST=DetectIRST
+return self
+end
+function DETECTION_BASE:InitDetectRWR(DetectRWR)
+self:F2()
+self.DetectRWR=DetectRWR
+return self
+end
+function DETECTION_BASE:InitDetectDLINK(DetectDLINK)
+self:F2()
+self.DetectDLINK=DetectDLINK
+return self
+end
+end
+do
+function DETECTION_BASE:FilterCategories(FilterCategories)
+self:F2()
+self._.FilterCategories={}
+if type(FilterCategories)=="table"then
+for CategoryID,Category in pairs(FilterCategories)do
+self._.FilterCategories[Category]=Category
+end
+else
+self._.FilterCategories[FilterCategories]=FilterCategories
+end
+return self
+end
+end
+do
+function DETECTION_BASE:SetRefreshTimeInterval(RefreshTimeInterval)
+self:F2()
+self.RefreshTimeInterval=RefreshTimeInterval
+return self
+end
+end
+do
+function DETECTION_BASE:SetFriendliesRange(FriendliesRange)
+self:F2()
+self.FriendliesRange=FriendliesRange
+return self
+end
+end
+do
+function DETECTION_BASE:SetIntercept(Intercept,InterceptDelay)
+self:F2()
+self.Intercept=Intercept
+self.InterceptDelay=InterceptDelay
+return self
+end
+end
+do
+function DETECTION_BASE:SetAcceptRange(AcceptRange)
+self:F2()
+self.AcceptRange=AcceptRange
+return self
+end
+function DETECTION_BASE:SetAcceptZones(AcceptZones)
+self:F2()
+if type(AcceptZones)=="table"then
+if AcceptZones.ClassName and AcceptZones:IsInstanceOf(ZONE_BASE)then
+self.AcceptZones={AcceptZones}
+else
+self.AcceptZones=AcceptZones
+end
+else
+self:E({"AcceptZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object",AcceptZones})
+error()
+end
+return self
+end
+function DETECTION_BASE:SetRejectZones(RejectZones)
+self:F2()
+if type(RejectZones)=="table"then
+if RejectZones.ClassName and RejectZones:IsInstanceOf(ZONE_BASE)then
+self.RejectZones={RejectZones}
+else
+self.RejectZones=RejectZones
+end
+else
+self:E({"RejectZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object",RejectZones})
+error()
+end
+return self
+end
+end
+do
+function DETECTION_BASE:SetDistanceProbability(DistanceProbability)
+self:F2()
+self.DistanceProbability=DistanceProbability
+return self
+end
+function DETECTION_BASE:SetAlphaAngleProbability(AlphaAngleProbability)
+self:F2()
+self.AlphaAngleProbability=AlphaAngleProbability
+return self
+end
+function DETECTION_BASE:SetZoneProbability(ZoneArray)
+self:F2()
+self.ZoneProbability=ZoneArray
+return self
+end
+end
+do
+function DETECTION_BASE:AcceptChanges(DetectedItem)
+DetectedItem.Changed=false
+DetectedItem.Changes={}
+return self
+end
+function DETECTION_BASE:AddChangeItem(DetectedItem,ChangeCode,ItemUnitType)
+DetectedItem.Changed=true
+local ID=DetectedItem.ID
+DetectedItem.Changes=DetectedItem.Changes or{}
+DetectedItem.Changes[ChangeCode]=DetectedItem.Changes[ChangeCode]or{}
+DetectedItem.Changes[ChangeCode].ID=ID
+DetectedItem.Changes[ChangeCode].ItemUnitType=ItemUnitType
+self:E({"Change on Detection Item:",DetectedItem.ID,ChangeCode,ItemUnitType})
+return self
+end
+function DETECTION_BASE:AddChangeUnit(DetectedItem,ChangeCode,ChangeUnitType)
+DetectedItem.Changed=true
+local ID=DetectedItem.ID
+DetectedItem.Changes=DetectedItem.Changes or{}
+DetectedItem.Changes[ChangeCode]=DetectedItem.Changes[ChangeCode]or{}
+DetectedItem.Changes[ChangeCode][ChangeUnitType]=DetectedItem.Changes[ChangeCode][ChangeUnitType]or 0
+DetectedItem.Changes[ChangeCode][ChangeUnitType]=DetectedItem.Changes[ChangeCode][ChangeUnitType]+1
+DetectedItem.Changes[ChangeCode].ID=ID
+self:E({"Change on Detection Item:",DetectedItem.ID,ChangeCode,ChangeUnitType})
+return self
+end
+end
+do
+function DETECTION_BASE:SetFriendlyPrefixes(FriendlyPrefixes)
+self.FriendlyPrefixes=self.FriendlyPrefixes or{}
+if type(FriendlyPrefixes)~="table"then
+FriendlyPrefixes={FriendlyPrefixes}
+end
+for PrefixID,Prefix in pairs(FriendlyPrefixes)do
+self:F({FriendlyPrefix=Prefix})
+self.FriendlyPrefixes[Prefix]=Prefix
+end
+return self
+end
+function DETECTION_BASE:IsFriendliesNearBy(DetectedItem)
+return DetectedItem.FriendliesNearBy~=nil or false
+end
+function DETECTION_BASE:GetFriendliesNearBy(DetectedItem)
+return DetectedItem.FriendliesNearBy
+end
+function DETECTION_BASE:FilterFriendliesCategory(FriendliesCategory)
+self.FriendliesCategory=FriendliesCategory
+return self
+end
+function DETECTION_BASE:IsFriendliesNearIntercept(DetectedItem)
+return DetectedItem.FriendliesNearIntercept~=nil or false
+end
+function DETECTION_BASE:GetFriendliesNearIntercept(DetectedItem)
+return DetectedItem.FriendliesNearIntercept
+end
+function DETECTION_BASE:GetFriendliesDistance(DetectedItem)
+return DetectedItem.FriendliesDistance
+end
+function DETECTION_BASE:IsPlayersNearBy(DetectedItem)
+return DetectedItem.PlayersNearBy~=nil
+end
+function DETECTION_BASE:GetPlayersNearBy(DetectedItem)
+return DetectedItem.PlayersNearBy
+end
+function DETECTION_BASE:ReportFriendliesNearBy(ReportGroupData)
+self:F2()
+local DetectedItem=ReportGroupData.DetectedItem
+local DetectedSet=ReportGroupData.DetectedItem.Set
+local DetectedUnit=DetectedSet:GetFirst()
+DetectedItem.FriendliesNearBy=nil
+if DetectedUnit and DetectedUnit:IsAlive()then
+local DetectedUnitCoord=DetectedUnit:GetCoordinate()
+local InterceptCoord=ReportGroupData.InterceptCoord or DetectedUnitCoord
+local SphereSearch={
+id=world.VolumeType.SPHERE,
+params={
+point=InterceptCoord:GetVec3(),
+radius=self.FriendliesRange,
+}
+}
+local FindNearByFriendlies=function(FoundDCSUnit,ReportGroupData)
+local DetectedItem=ReportGroupData.DetectedItem
+local DetectedSet=ReportGroupData.DetectedItem.Set
+local DetectedUnit=DetectedSet:GetFirst()
+local DetectedUnitCoord=DetectedUnit:GetCoordinate()
+local InterceptCoord=ReportGroupData.InterceptCoord or DetectedUnitCoord
+local ReportSetGroup=ReportGroupData.ReportSetGroup
+local EnemyCoalition=DetectedUnit:GetCoalition()
+local FoundUnitCoalition=FoundDCSUnit:getCoalition()
+local FoundUnitName=FoundDCSUnit:getName()
+local FoundUnitGroupName=FoundDCSUnit:getGroup():getName()
+local EnemyUnitName=DetectedUnit:GetName()
+local FoundUnitInReportSetGroup=ReportSetGroup:FindGroup(FoundUnitGroupName)~=nil
+self:T({"Friendlies search:",FoundUnitName,FoundUnitCoalition,EnemyUnitName,EnemyCoalition,FoundUnitInReportSetGroup})
+if FoundUnitInReportSetGroup==true then
+for PrefixID,Prefix in pairs(self.FriendlyPrefixes or{})do
+self:F({"FriendlyPrefix:",Prefix})
+if string.find(FoundUnitName,Prefix:gsub("-","%%-"),1)then
+FoundUnitInReportSetGroup=false
+break
+end
+end
+end
+self:F({"Friendlies search:",FoundUnitName,FoundUnitCoalition,EnemyUnitName,EnemyCoalition,FoundUnitInReportSetGroup})
+if FoundUnitCoalition~=EnemyCoalition and FoundUnitInReportSetGroup==false then
+local FriendlyUnit=UNIT:Find(FoundDCSUnit)
+local FriendlyUnitName=FriendlyUnit:GetName()
+local FriendlyUnitCategory=FriendlyUnit:GetDesc().category
+self:T({FriendlyUnitCategory=FriendlyUnitCategory,FriendliesCategory=self.FriendliesCategory})
+DetectedItem.FriendliesNearBy=DetectedItem.FriendliesNearBy or{}
+DetectedItem.FriendliesNearBy[FriendlyUnitName]=FriendlyUnit
+local Distance=DetectedUnitCoord:Get2DDistance(FriendlyUnit:GetCoordinate())
+DetectedItem.FriendliesDistance=DetectedItem.FriendliesDistance or{}
+DetectedItem.FriendliesDistance[Distance]=FriendlyUnit
+self:T({FriendlyUnitName=FriendlyUnitName,Distance=Distance})
+return true
+end
+return true
+end
+world.searchObjects(Object.Category.UNIT,SphereSearch,FindNearByFriendlies,ReportGroupData)
+DetectedItem.PlayersNearBy=nil
+local DetectionZone=ZONE_UNIT:New("DetectionPlayers",DetectedUnit,self.FriendliesRange)
+_DATABASE:ForEachPlayer(
+function(PlayerUnitName)
+local PlayerUnit=UNIT:FindByName(PlayerUnitName)
+if PlayerUnit and PlayerUnit:IsInZone(DetectionZone)then
+local PlayerUnitCategory=PlayerUnit:GetDesc().category
+if(not self.FriendliesCategory)or(self.FriendliesCategory and(self.FriendliesCategory==PlayerUnitCategory))then
+DetectedItem.FriendliesNearBy=DetectedItem.FriendliesNearBy or{}
+local PlayerUnitName=PlayerUnit:GetName()
+DetectedItem.PlayersNearBy=DetectedItem.PlayersNearBy or{}
+DetectedItem.PlayersNearBy[PlayerUnitName]=PlayerUnit
+DetectedItem.FriendliesNearBy=DetectedItem.FriendliesNearBy or{}
+DetectedItem.FriendliesNearBy[PlayerUnitName]=PlayerUnit
+local Distance=DetectedUnitCoord:Get2DDistance(PlayerUnit:GetCoordinate())
+DetectedItem.FriendliesDistance=DetectedItem.FriendliesDistance or{}
+DetectedItem.FriendliesDistance[Distance]=PlayerUnit
+end
+end
+end
+)
+end
+end
+end
+function DETECTION_BASE:IsDetectedObjectIdentified(DetectedObject)
+local DetectedObjectName=DetectedObject.Name
+if DetectedObjectName then
+local DetectedObjectIdentified=self.DetectedObjectsIdentified[DetectedObjectName]==true
+self:T3(DetectedObjectIdentified)
+return DetectedObjectIdentified
+else
+return nil
+end
+end
+function DETECTION_BASE:IdentifyDetectedObject(DetectedObject)
+local DetectedObjectName=DetectedObject.Name
+self.DetectedObjectsIdentified[DetectedObjectName]=true
+end
+function DETECTION_BASE:UnIdentifyDetectedObject(DetectedObject)
+local DetectedObjectName=DetectedObject.Name
+self.DetectedObjectsIdentified[DetectedObjectName]=false
+end
+function DETECTION_BASE:UnIdentifyAllDetectedObjects()
+self.DetectedObjectsIdentified={}
+end
+function DETECTION_BASE:GetDetectedObject(ObjectName)
+if ObjectName then
+local DetectedObject=self.DetectedObjects[ObjectName]
+if DetectedObject then
+local DetectedUnit=UNIT:FindByName(ObjectName)
+if DetectedUnit and DetectedUnit:IsAlive()then
+if self:IsDetectedObjectIdentified(DetectedObject)==false then
+return DetectedObject
+end
+end
+end
+end
+return nil
+end
+function DETECTION_BASE:GetDetectedUnitTypeName(DetectedUnit)
+if DetectedUnit and DetectedUnit:IsAlive()then
+local DetectedUnitName=DetectedUnit:GetName()
+local DetectedObject=self.DetectedObjects[DetectedUnitName]
+if DetectedObject then
+if DetectedObject.KnowType then
+return DetectedUnit:GetTypeName()
+else
+return"Unknown"
+end
+else
+return"Unknown"
+end
+else
+return"Dead:"..DetectedUnit:GetName()
+end
+return"Undetected:"..DetectedUnit:GetName()
+end
+function DETECTION_BASE:AddDetectedItem(ItemPrefix,DetectedItemIndex,Set)
+local DetectedItem={}
+self.DetectedItemCount=self.DetectedItemCount+1
+self.DetectedItemMax=self.DetectedItemMax+1
+if DetectedItemIndex then
+self.DetectedItems[DetectedItemIndex]=DetectedItem
+else
+self.DetectedItems[self.DetectedItemCount]=DetectedItem
+end
+DetectedItem.Set=Set or SET_UNIT:New():FilterDeads():FilterCrashes()
+DetectedItem.Index=DetectedItemIndex or self.DetectedItemCount
+DetectedItem.ItemID=ItemPrefix.."."..self.DetectedItemMax
+DetectedItem.ID=self.DetectedItemMax
+DetectedItem.Removed=false
+return DetectedItem
+end
+function DETECTION_BASE:AddDetectedItemZone(DetectedItemIndex,Set,Zone)
+local DetectedItem=self:AddDetectedItem("AREA",DetectedItemIndex,Set)
+DetectedItem.Zone=Zone
+return DetectedItem
+end
+function DETECTION_BASE:RemoveDetectedItem(DetectedItemIndex)
+if self.DetectedItems[DetectedItemIndex]then
+self.DetectedItemCount=self.DetectedItemCount-1
+self.DetectedItems[DetectedItemIndex]=nil
+end
+end
+function DETECTION_BASE:GetDetectedItems()
+return self.DetectedItems
+end
+function DETECTION_BASE:GetDetectedItemsCount()
+local DetectedCount=self.DetectedItemCount
+return DetectedCount
+end
+function DETECTION_BASE:GetDetectedItem(Index)
+local DetectedItem=self.DetectedItems[Index]
+if DetectedItem then
+return DetectedItem
+end
+return nil
+end
+function DETECTION_BASE:GetDetectedItemID(Index)
+local DetectedItem=self.DetectedItems[Index]
+if DetectedItem then
+return DetectedItem.ItemID
+end
+return""
+end
+function DETECTION_BASE:GetDetectedID(Index)
+local DetectedItem=self.DetectedItems[Index]
+if DetectedItem then
+return DetectedItem.ID
+end
+return""
+end
+function DETECTION_BASE:GetDetectedSet(Index)
+local DetectedItem=self:GetDetectedItem(Index)
+local DetectedSetUnit=DetectedItem.Set
+if DetectedSetUnit then
+return DetectedSetUnit
+end
+return nil
+end
+function DETECTION_BASE:UpdateDetectedItemDetection(DetectedItem)
+local IsDetected=false
+for UnitName,UnitData in pairs(DetectedItem.Set:GetSet())do
+local DetectedObject=self.DetectedObjects[UnitName]
+self:F({UnitName=UnitName,IsDetected=DetectedObject.IsDetected})
+if DetectedObject.IsDetected then
+IsDetected=true
+break
+end
+end
+self:F({IsDetected=DetectedItem.IsDetected})
+DetectedItem.IsDetected=IsDetected
+return IsDetected
+end
+function DETECTION_BASE:IsDetectedItemDetected(DetectedItem)
+return DetectedItem.IsDetected
+end
+do
+function DETECTION_BASE:GetDetectedItemZone(Index)
+local DetectedZone=self.DetectedItems[Index].Zone
+if DetectedZone then
+return DetectedZone
+end
+local Detected
+return nil
+end
+end
+function DETECTION_BASE:SetDetectedItemCoordinate(DetectedItem,Coordinate,DetectedItemUnit)
+self:F({Coordinate=Coordinate})
+if DetectedItem then
+if DetectedItemUnit then
+DetectedItem.Coordinate=Coordinate
+DetectedItem.Coordinate:SetHeading(DetectedItemUnit:GetHeading())
+DetectedItem.Coordinate.y=DetectedItemUnit:GetAltitude()
+DetectedItem.Coordinate:SetVelocity(DetectedItemUnit:GetVelocityMPS())
+end
+end
+end
+function DETECTION_BASE:GetDetectedItemCoordinate(Index)
+self:F({Index=Index})
+local DetectedItem=self:GetDetectedItem(Index)
+if DetectedItem then
+return DetectedItem.Coordinate
+end
+return nil
+end
+function DETECTION_BASE:SetDetectedItemThreatLevel(DetectedItem)
+local DetectedSet=DetectedItem.Set
+if DetectedItem then
+DetectedItem.ThreatLevel,DetectedItem.ThreatText=DetectedSet:CalculateThreatLevelA2G()
+end
+end
+function DETECTION_BASE:GetDetectedItemThreatLevel(Index)
+self:F({Index=Index})
+local DetectedItem=self:GetDetectedItem(Index)
+if DetectedItem then
+return DetectedItem.ThreatLevel or 0,DetectedItem.ThreatText or""
+end
+return nil,""
+end
+function DETECTION_BASE:DetectedItemMenu(Index,AttackGroup)
+self:F(Index)
+return nil
+end
+function DETECTION_BASE:DetectedItemReportSummary(Index,AttackGroup,Settings)
+self:F(Index)
+return nil
+end
+function DETECTION_BASE:DetectedReportDetailed(AttackGroup)
+self:F()
+return nil
+end
+function DETECTION_BASE:GetDetectionSetGroup()
+local DetectionSetGroup=self.DetectionSetGroup
+return DetectionSetGroup
+end
+function DETECTION_BASE:Schedule(DelayTime,RepeatInterval)
+self:F2()
+self.ScheduleDelayTime=DelayTime
+self.ScheduleRepeatInterval=RepeatInterval
+self.DetectionScheduler=SCHEDULER:New(self,self._DetectionScheduler,{self,"Detection"},DelayTime,RepeatInterval)
+return self
+end
+end
+do
+DETECTION_UNITS={
+ClassName="DETECTION_UNITS",
+DetectionRange=nil,
+}
+function DETECTION_UNITS:New(DetectionSetGroup)
+local self=BASE:Inherit(self,DETECTION_BASE:New(DetectionSetGroup))
+self._SmokeDetectedUnits=false
+self._FlareDetectedUnits=false
+self._SmokeDetectedZones=false
+self._FlareDetectedZones=false
+self._BoundDetectedZones=false
+return self
+end
+function DETECTION_UNITS:GetChangeText(DetectedItem)
+self:F(DetectedItem)
+local MT={}
+for ChangeCode,ChangeData in pairs(DetectedItem.Changes)do
+if ChangeCode=="AU"then
+local MTUT={}
+for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
+if ChangeUnitType~="ID"then
+MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
+end
+end
+MT[#MT+1]=" New target(s) detected: "..table.concat(MTUT,", ").."."
+end
+if ChangeCode=="RU"then
+local MTUT={}
+for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
+if ChangeUnitType~="ID"then
+MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
+end
+end
+MT[#MT+1]=" Invisible or destroyed target(s): "..table.concat(MTUT,", ").."."
+end
+end
+return table.concat(MT,"\n")
+end
+function DETECTION_UNITS:CreateDetectionItems()
+self:F2(#self.DetectedObjects)
+for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do
+local DetectedItemSet=DetectedItem.Set
+for DetectedUnitName,DetectedUnitData in pairs(DetectedItemSet:GetSet())do
+local DetectedUnit=DetectedUnitData
+local DetectedObject=nil
+if DetectedUnit:IsAlive()then
+DetectedObject=self:GetDetectedObject(DetectedUnit:GetName())
+end
+if DetectedObject then
+self:IdentifyDetectedObject(DetectedObject)
+DetectedItem.TypeName=DetectedUnit:GetTypeName()
+DetectedItem.CategoryName=DetectedUnit:GetCategoryName()
+DetectedItem.Name=DetectedObject.Name
+DetectedItem.IsVisible=DetectedObject.IsVisible
+DetectedItem.LastTime=DetectedObject.LastTime
+DetectedItem.LastPos=DetectedObject.LastPos
+DetectedItem.LastVelocity=DetectedObject.LastVelocity
+DetectedItem.KnowType=DetectedObject.KnowType
+DetectedItem.KnowDistance=DetectedObject.KnowDistance
+DetectedItem.Distance=DetectedObject.Distance
+else
+self:AddChangeUnit(DetectedItem,"RU",DetectedUnitName)
+DetectedItemSet:Remove(DetectedUnitName)
+end
+end
+end
+for DetectedUnitName,DetectedObjectData in pairs(self.DetectedObjects)do
+local DetectedObject=self:GetDetectedObject(DetectedUnitName)
+if DetectedObject then
+self:T({"Detected Unit #",DetectedUnitName})
+local DetectedUnit=UNIT:FindByName(DetectedUnitName)
+if DetectedUnit then
+local DetectedTypeName=DetectedUnit:GetTypeName()
+local DetectedItem=self:GetDetectedItem(DetectedUnitName)
+if not DetectedItem then
+self:T("Added new DetectedItem")
+DetectedItem=self:AddDetectedItem("UNIT",DetectedUnitName)
+DetectedItem.TypeName=DetectedUnit:GetTypeName()
+DetectedItem.Name=DetectedObject.Name
+DetectedItem.IsVisible=DetectedObject.IsVisible
+DetectedItem.LastTime=DetectedObject.LastTime
+DetectedItem.LastPos=DetectedObject.LastPos
+DetectedItem.LastVelocity=DetectedObject.LastVelocity
+DetectedItem.KnowType=DetectedObject.KnowType
+DetectedItem.KnowDistance=DetectedObject.KnowDistance
+DetectedItem.Distance=DetectedObject.Distance
+end
+DetectedItem.Set:AddUnit(DetectedUnit)
+self:AddChangeUnit(DetectedItem,"AU",DetectedTypeName)
+end
+end
+end
+for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do
+local DetectedItem=DetectedItemData
+local DetectedSet=DetectedItem.Set
+local DetectedFirstUnit=DetectedSet:GetFirst()
+local DetectedFirstUnitCoord=DetectedFirstUnit:GetCoordinate()
+self:SetDetectedItemCoordinate(DetectedItem,DetectedFirstUnitCoord,DetectedFirstUnit)
+self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSetGroup})
+end
+end
+function DETECTION_UNITS:DetectedItemMenu(Index,AttackGroup)
+self:F(Index)
+local DetectedItem=self:GetDetectedItem(Index)
+local DetectedSet=self:GetDetectedSet(Index)
+local DetectedItemID=self:GetDetectedItemID(Index)
+self:T(DetectedSet)
+if DetectedSet then
+local ReportSummary=""
+local UnitDistanceText=""
+local UnitCategoryText=""
+local DetectedItemCoordinate=self:GetDetectedItemCoordinate(Index)
+local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup)
+ReportSummary=string.format(
+"%s - %s",
+DetectedItemID,
+DetectedItemCoordText
+)
+self:T(ReportSummary)
+return ReportSummary
+end
+end
+function DETECTION_UNITS:DetectedItemReportSummary(Index,AttackGroup,Settings)
+self:F({Index,self.DetectedItems})
+local DetectedItem=self:GetDetectedItem(Index)
+local DetectedItemID=self:GetDetectedItemID(Index)
+if DetectedItem then
+local ReportSummary=""
+local UnitDistanceText=""
+local UnitCategoryText=""
+if DetectedItem.KnowType then
+local UnitCategoryName=DetectedItem.CategoryName
+if UnitCategoryName then
+UnitCategoryText=UnitCategoryName
+end
+if DetectedItem.TypeName then
+UnitCategoryText=UnitCategoryText.." ("..DetectedItem.TypeName..")"
+end
+else
+UnitCategoryText="Unknown"
+end
+if DetectedItem.KnowDistance then
+if DetectedItem.IsVisible then
+UnitDistanceText=" at "..string.format("%.2f",DetectedItem.Distance).." km"
+end
+else
+if DetectedItem.IsVisible then
+UnitDistanceText=" at +/- "..string.format("%.0f",DetectedItem.Distance).." km"
+end
+end
+local DetectedItemCoordinate=self:GetDetectedItemCoordinate(Index)
+local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings)
+local ThreatLevelA2G=self:GetDetectedItemThreatLevel(Index)
+local Report=REPORT:New()
+Report:Add(DetectedItemID..", "..DetectedItemCoordText)
+Report:Add(string.format("Threat: [%s]",string.rep("â– ",ThreatLevelA2G)))
+Report:Add(string.format("Type: %s%s",UnitCategoryText,UnitDistanceText))
+return Report
+end
+return nil
+end
+function DETECTION_UNITS:DetectedReportDetailed(AttackGroup)
+self:F()
+local Report=REPORT:New()
+for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do
+local DetectedItem=DetectedItem
+local ReportSummary=self:DetectedItemReportSummary(DetectedItemID,AttackGroup)
+Report:SetTitle("Detected units:")
+Report:Add(ReportSummary:Text())
+end
+local ReportText=Report:Text()
+return ReportText
+end
+end
+do
+DETECTION_TYPES={
+ClassName="DETECTION_TYPES",
+DetectionRange=nil,
+}
+function DETECTION_TYPES:New(DetectionSetGroup)
+local self=BASE:Inherit(self,DETECTION_BASE:New(DetectionSetGroup))
+self._SmokeDetectedUnits=false
+self._FlareDetectedUnits=false
+self._SmokeDetectedZones=false
+self._FlareDetectedZones=false
+self._BoundDetectedZones=false
+return self
+end
+function DETECTION_TYPES:GetChangeText(DetectedItem)
+self:F(DetectedItem)
+local MT={}
+for ChangeCode,ChangeData in pairs(DetectedItem.Changes)do
+if ChangeCode=="AU"then
+local MTUT={}
+for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
+if ChangeUnitType~="ID"then
+MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
+end
+end
+MT[#MT+1]=" New target(s) detected: "..table.concat(MTUT,", ").."."
+end
+if ChangeCode=="RU"then
+local MTUT={}
+for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
+if ChangeUnitType~="ID"then
+MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
+end
+end
+MT[#MT+1]=" Invisible or destroyed target(s): "..table.concat(MTUT,", ").."."
+end
+end
+return table.concat(MT,"\n")
+end
+function DETECTION_TYPES:CreateDetectionItems()
+self:F2(#self.DetectedObjects)
+for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do
+local DetectedItemSet=DetectedItem.Set
+local DetectedTypeName=DetectedItem.TypeName
+for DetectedUnitName,DetectedUnitData in pairs(DetectedItemSet:GetSet())do
+local DetectedUnit=DetectedUnitData
+local DetectedObject=nil
+if DetectedUnit:IsAlive()then
+DetectedObject=self:GetDetectedObject(DetectedUnit:GetName())
+end
+if DetectedObject then
+self:IdentifyDetectedObject(DetectedObject)
+else
+self:AddChangeUnit(DetectedItem,"RU",DetectedUnitName)
+DetectedItemSet:Remove(DetectedUnitName)
+end
+end
+end
+for DetectedUnitName,DetectedObjectData in pairs(self.DetectedObjects)do
+local DetectedObject=self:GetDetectedObject(DetectedUnitName)
+if DetectedObject then
+self:T({"Detected Unit #",DetectedUnitName})
+local DetectedUnit=UNIT:FindByName(DetectedUnitName)
+if DetectedUnit then
+local DetectedTypeName=DetectedUnit:GetTypeName()
+local DetectedItem=self:GetDetectedItem(DetectedTypeName)
+if not DetectedItem then
+DetectedItem=self:AddDetectedItem("TYPE",DetectedTypeName)
+DetectedItem.TypeName=DetectedUnit:GetTypeName()
+end
+DetectedItem.Set:AddUnit(DetectedUnit)
+self:AddChangeUnit(DetectedItem,"AU",DetectedTypeName)
+end
+end
+end
+for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do
+local DetectedItem=DetectedItemData
+local DetectedSet=DetectedItem.Set
+local DetectedFirstUnit=DetectedSet:GetFirst()
+local DetectedUnitCoord=DetectedFirstUnit:GetCoordinate()
+self:SetDetectedItemCoordinate(DetectedItem,DetectedUnitCoord,DetectedFirstUnit)
+self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSetGroup})
+end
+end
+function DETECTION_TYPES:DetectedItemMenu(DetectedTypeName,AttackGroup)
+self:F(DetectedTypeName)
+local DetectedItem=self:GetDetectedItem(DetectedTypeName)
+local DetectedItemID=self:GetDetectedItemID(DetectedTypeName)
+if DetectedItem then
+local DetectedItemCoordinate=self:GetDetectedItemCoordinate(DetectedTypeName)
+local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup)
+local ReportSummary=string.format(
+"%s - %s",
+DetectedItemID,
+DetectedItemCoordText
+)
+self:T(ReportSummary)
+return ReportSummary
+end
+end
+function DETECTION_TYPES:DetectedItemReportSummary(DetectedTypeName,AttackGroup,Settings)
+self:F(DetectedTypeName)
+local DetectedItem=self:GetDetectedItem(DetectedTypeName)
+local DetectedSet=self:GetDetectedSet(DetectedTypeName)
+local DetectedItemID=self:GetDetectedItemID(DetectedTypeName)
+self:T(DetectedItem)
+if DetectedItem then
+local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedTypeName)
+local DetectedItemsCount=DetectedSet:Count()
+local DetectedItemType=DetectedItem.TypeName
+local DetectedItemCoordinate=self:GetDetectedItemCoordinate(DetectedTypeName)
+local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings)
+local Report=REPORT:New()
+Report:Add(DetectedItemID..", "..DetectedItemCoordText)
+Report:Add(string.format("Threat: [%s]",string.rep("â– ",ThreatLevelA2G)))
+Report:Add(string.format("Type: %2d of %s",DetectedItemsCount,DetectedItemType))
+return Report
+end
+end
+function DETECTION_TYPES:DetectedReportDetailed(AttackGroup)
+self:F()
+local Report=REPORT:New()
+for DetectedItemTypeName,DetectedItem in pairs(self.DetectedItems)do
+local DetectedItem=DetectedItem
+local ReportSummary=self:DetectedItemReportSummary(DetectedItemTypeName,AttackGroup)
+Report:SetTitle("Detected types:")
+Report:Add(ReportSummary:Text())
+end
+local ReportText=Report:Text()
+return ReportText
+end
+end
+do
+DETECTION_AREAS={
+ClassName="DETECTION_AREAS",
+DetectionZoneRange=nil,
+}
+function DETECTION_AREAS:New(DetectionSetGroup,DetectionZoneRange)
+local self=BASE:Inherit(self,DETECTION_BASE:New(DetectionSetGroup))
+self.DetectionZoneRange=DetectionZoneRange
+self._SmokeDetectedUnits=false
+self._FlareDetectedUnits=false
+self._SmokeDetectedZones=false
+self._FlareDetectedZones=false
+self._BoundDetectedZones=false
+return self
+end
+function DETECTION_AREAS:DetectedItemMenu(Index,AttackGroup)
+self:F(Index)
+local DetectedItem=self:GetDetectedItem(Index)
+local DetectedItemID=self:GetDetectedItemID(Index)
+if DetectedItem then
+local DetectedSet=self:GetDetectedSet(Index)
+local ReportSummaryItem
+local DetectedZone=self:GetDetectedItemZone(Index)
+local DetectedItemCoordinate=DetectedZone:GetCoordinate()
+local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup)
+local ReportSummary=string.format(
+"%s - %s",
+DetectedItemID,
+DetectedItemCoordText
+)
+return ReportSummary
+end
+return nil
+end
+function DETECTION_AREAS:DetectedItemReportSummary(Index,AttackGroup,Settings)
+self:F(Index)
+local DetectedItem=self:GetDetectedItem(Index)
+local DetectedItemID=self:GetDetectedItemID(Index)
+if DetectedItem then
+local DetectedSet=self:GetDetectedSet(Index)
+local ReportSummaryItem
+local DetectedZone=self:GetDetectedItemZone(Index)
+local DetectedItemCoordinate=DetectedZone:GetCoordinate()
+local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings)
+local ThreatLevelA2G=self:GetDetectedItemThreatLevel(Index)
+local DetectedItemsCount=DetectedSet:Count()
+local DetectedItemsTypes=DetectedSet:GetTypeNames()
+local Report=REPORT:New()
+Report:Add(DetectedItemID..", "..DetectedItemCoordText)
+Report:Add(string.format("Threat: [%s]",string.rep("â– ",ThreatLevelA2G)))
+Report:Add(string.format("Type: %2d of %s",DetectedItemsCount,DetectedItemsTypes))
+return Report
+end
+return nil
+end
+function DETECTION_AREAS:DetectedReportDetailed(AttackGroup)
+self:F()
+local Report=REPORT:New()
+for DetectedItemIndex,DetectedItem in pairs(self.DetectedItems)do
+local DetectedItem=DetectedItem
+local ReportSummary=self:DetectedItemReportSummary(DetectedItemIndex,AttackGroup)
+Report:SetTitle("Detected areas:")
+Report:Add(ReportSummary:Text())
+end
+local ReportText=Report:Text()
+return ReportText
+end
+function DETECTION_AREAS:CalculateIntercept(DetectedItem)
+local DetectedCoord=DetectedItem.Coordinate
+local DetectedSpeed=DetectedCoord:GetVelocity()
+local DetectedHeading=DetectedCoord:GetHeading()
+if self.Intercept then
+local DetectedSet=DetectedItem.Set
+local TranslateDistance=DetectedSpeed*self.InterceptDelay
+local InterceptCoord=DetectedCoord:Translate(TranslateDistance,DetectedHeading)
+DetectedItem.InterceptCoord=InterceptCoord
+else
+DetectedItem.InterceptCoord=DetectedCoord
+end
+end
+function DETECTION_AREAS:NearestFAC(DetectedItem)
+local NearestRecce=nil
+local DistanceRecce=1000000000
+for RecceGroupName,RecceGroup in pairs(self.DetectionSetGroup:GetSet())do
+if RecceGroup and RecceGroup:IsAlive()then
+for RecceUnit,RecceUnit in pairs(RecceGroup:GetUnits())do
+if RecceUnit:IsActive()then
+local RecceUnitCoord=RecceUnit:GetCoordinate()
+local Distance=RecceUnitCoord:Get2DDistance(self:GetDetectedItemCoordinate(DetectedItem.Index))
+if Distance=timer.getTime()))then
+TargetSetUnit:ForEachUnitPerThreatLevel(10,0,
+function(TargetUnit)
+self:F({TargetUnit=TargetUnit:GetName()})
+if MarkingCountFLmax then
+FLmin=FLmax*0.75
+end
+if self.category==RAT.cat.heli then
+FLmin=math.max(H_departure,H_destination)+50
+FLmax=math.max(H_departure,H_destination)+1000
+end
+FLmax=math.min(FLmax,self.aircraft.ceiling*0.9)
+if self.FLminuser then
+FLmin=self.FLminuser
+end
+if self.FLmaxuser then
+FLmax=self.FLmaxuser
+end
+if self.aircraft.FLcruiseFLmax then
+self.aircraft.FLcruise=FLmax
+end
+local FLcruise=self:_Random_Gaussian(self.aircraft.FLcruise,(FLmax-FLmin)/4,FLmin,FLmax)
+if self.FLuser then
+FLcruise=self.FLuser
+end
+local h_climb=FLcruise-H_departure
+local d_climb=h_climb/math.tan(PhiClimb)
+local h_descent=FLcruise-(H_holding+h_holding)
+local d_descent=h_descent/math.tan(PhiDescent)
+local d_cruise=d_total-d_climb-d_descent
+local text=string.format("\n******************************************************\n")
+text=text..string.format("Template = %s\n\n",self.SpawnTemplatePrefix)
+text=text..string.format("Speeds:\n")
+text=text..string.format("VxCruiseMin = %6.1f m/s = %5.1f km/h\n",VxCruiseMin,VxCruiseMin*3.6)
+text=text..string.format("VxCruiseMax = %6.1f m/s = %5.1f km/h\n",VxCruiseMax,VxCruiseMax*3.6)
+text=text..string.format("VxCruise = %6.1f m/s = %5.1f km/h\n",VxCruise,VxCruise*3.6)
+text=text..string.format("VxClimb = %6.1f m/s = %5.1f km/h\n",VxClimb,VxClimb*3.6)
+text=text..string.format("VxDescent = %6.1f m/s = %5.1f km/h\n",VxDescent,VxDescent*3.6)
+text=text..string.format("VxHolding = %6.1f m/s = %5.1f km/h\n",VxHolding,VxHolding*3.6)
+text=text..string.format("VxFinal = %6.1f m/s = %5.1f km/h\n",VxFinal,VxFinal*3.6)
+text=text..string.format("VyClimb = %6.1f m/s\n",VyClimb)
+text=text..string.format("\nDistances:\n")
+text=text..string.format("d_climb = %6.1f km\n",d_climb/1000)
+text=text..string.format("d_cruise = %6.1f km\n",d_cruise/1000)
+text=text..string.format("d_descent = %6.1f km\n",d_descent/1000)
+text=text..string.format("d_holding = %6.1f km\n",d_holding/1000)
+text=text..string.format("d_total = %6.1f km\n",d_total/1000)
+text=text..string.format("\nHeights:\n")
+text=text..string.format("H_departure = %6.1f m ASL\n",H_departure)
+text=text..string.format("H_destination = %6.1f m ASL\n",H_destination)
+text=text..string.format("H_holding = %6.1f m ASL\n",H_holding)
+text=text..string.format("h_climb = %6.1f m\n",h_climb)
+text=text..string.format("h_descent = %6.1f m\n",h_descent)
+text=text..string.format("h_holding = %6.1f m\n",h_holding)
+text=text..string.format("delta H = %6.1f m\n",deltaH)
+text=text..string.format("FLmin = %6.1f m ASL = FL%03d\n",FLmin,FLmin/RAT.unit.FL2m)
+text=text..string.format("FLcruise = %6.1f m ASL = FL%03d\n",FLcruise,FLcruise/RAT.unit.FL2m)
+text=text..string.format("FLmax = %6.1f m ASL = FL%03d\n",FLmax,FLmax/RAT.unit.FL2m)
+text=text..string.format("\nAngles:\n")
+text=text..string.format("Alpha climb = %6.1f Deg\n",math.deg(AlphaClimb))
+text=text..string.format("Alpha descent = %6.1f Deg\n",math.deg(AlphaDescent))
+text=text..string.format("Phi (slope) = %6.1f Deg\n",math.deg(phi))
+text=text..string.format("Phi climb = %6.1f Deg\n",math.deg(PhiClimb))
+text=text..string.format("Phi descent = %6.1f Deg\n",math.deg(PhiDescent))
+text=text..string.format("Heading = %6.1f Deg\n",heading)
+text=text..string.format("******************************************************\n")
+env.info(RAT.id..text)
+if d_cruise<0 then
+d_cruise=100
+end
+local c0=Pdeparture
+local c1=c0:Translate(d_climb/2,heading)
+local c2=c1:Translate(d_climb/2,heading)
+local c3=c2:Translate(d_cruise,heading)
+local c4=c3:Translate(d_descent/2,heading)
+local c5=Pholding
+local c6=Pdestination
+local wp0=self:_Waypoint(takeoff,c0,VxClimb,H_departure,departure)
+local wp1=self:_Waypoint(RAT.wp.climb,c1,VxClimb,H_departure+(FLcruise-H_departure)/2)
+local wp2=self:_Waypoint(RAT.wp.cruise,c2,VxCruise,FLcruise)
+local wp3=self:_Waypoint(RAT.wp.cruise,c3,VxCruise,FLcruise)
+local wp4=self:_Waypoint(RAT.wp.descent,c4,VxDescent,FLcruise-(FLcruise-(h_holding+H_holding))/2)
+local wp5=self:_Waypoint(RAT.wp.holding,c5,VxHolding,H_holding+h_holding)
+local wp6=self:_Waypoint(RAT.wp.landing,c6,VxFinal,H_destination,destination)
+local waypoints={wp0,wp1,wp2,wp3,wp4,wp5,wp6}
+if self.placemarkers then
+self:_PlaceMarkers(waypoints)
+end
+self:_Routeinfo(waypoints,"Waypoint info in set_route:")
+return departure,destination,waypoints
+end
+function RAT:_PickDeparture(takeoff)
+local departures={}
+if takeoff==RAT.wp.air then
+if self.random_departure then
+for _,airport in pairs(self.airports)do
+if not self:_Excluded(airport:GetName())then
+table.insert(departures,airport:GetZone())
+end
+end
+else
+for _,name in pairs(self.departure_zones)do
+if not self:_Excluded(name)then
+table.insert(departures,ZONE:New(name))
+end
+end
+for _,name in pairs(self.departure_ports)do
+if not self:_Excluded(name)then
+table.insert(departures,AIRBASE:FindByName(name):GetZone())
+end
+end
+end
+else
+if self.random_departure then
+for _,airport in pairs(self.airports)do
+if not self:_Excluded(airport:GetName())then
+table.insert(departures,airport)
+end
+end
+else
+for _,name in pairs(self.departure_ports)do
+if not self:_Excluded(name)then
+table.insert(departures,AIRBASE:FindByName(name))
+end
+end
+end
+end
+local departure=departures[math.random(#departures)]
+local text
+if departure and departure:GetName()then
+if takeoff==RAT.wp.air then
+text="Chosen departure zone: "..departure:GetName()
+else
+text="Chosen departure airport: "..departure:GetName().." (ID "..departure:GetID()..")"
+end
+env.info(RAT.id..text)
+if self.debug then
+MESSAGE:New(text,30):ToAll()
+end
+else
+departure=nil
+end
+return departure
+end
+function RAT:_PickDestination(destinations,_random)
+local destination=nil
+if destinations and#destinations>0 then
+destination=destinations[math.random(#destinations)]
+local text="Chosen destination airport: "..destination:GetName().." (ID "..destination:GetID()..")"
+env.info(RAT.id..text)
+if self.debug then
+MESSAGE:New(text,30):ToAll()
+end
+else
+env.error(RAT.id.."No destination airport found.")
+end
+return destination
+end
+function RAT:_GetDestinations(departure,q,minrange,maxrange)
+minrange=minrange or self.mindist
+maxrange=maxrange or self.maxdist
+local possible_destinations={}
+if self.random_destination then
+for _,airport in pairs(self.airports)do
+local name=airport:GetName()
+if self:_IsFriendly(name)and not self:_Excluded(name)and name~=departure:GetName()then
+local distance=q:Get2DDistance(airport:GetCoordinate())
+if distance>=minrange and distance<=maxrange then
+table.insert(possible_destinations,airport)
+end
+end
+end
+else
+for _,name in pairs(self.destination_ports)do
+if name~=departure:GetName()then
+local airport=AIRBASE:FindByName(name)
+table.insert(possible_destinations,airport)
+end
+end
+end
+env.info(RAT.id.."Number of possible destination airports = "..#possible_destinations)
+if#possible_destinations>0 then
+local function compare(a,b)
+local qa=q:Get2DDistance(a:GetCoordinate())
+local qb=q:Get2DDistance(b:GetCoordinate())
+return qaself.Tinactive then
+if Dg<50 then
+stationary=true
+end
+self.ratcraft[i]["Tlastcheck"]=Tnow
+self.ratcraft[i]["Pground"]=coords
+end
+else
+self.ratcraft[i]["Tground"]=Tnow
+self.ratcraft[i]["Tlastcheck"]=Tnow
+self.ratcraft[i]["Pground"]=coords
+end
+end
+local Pn=coords
+local Dtravel=Pn:Get2DDistance(self.ratcraft[i]["Pnow"])
+self.ratcraft[i]["Pnow"]=Pn
+self.ratcraft[i]["Distance"]=self.ratcraft[i]["Distance"]+Dtravel
+local Ddestination=Pn:Get2DDistance(self.ratcraft[i].destination:GetCoordinate())
+local Hp=COORDINATE:New(self.ratcraft[i].waypoints[6].x,self.ratcraft[i].waypoints[6].alt,self.ratcraft[i].waypoints[6].y)
+local Dholding=Pn:Get2DDistance(Hp)
+local status=self.ratcraft[i].status
+local DRholding
+if self.category==RAT.cat.plane then
+DRholding=8000
+else
+DRholding=2000
+end
+if self.ATCswitch and Dholding<=DRholding and string.match(status,"On journey")then
+RAT:_ATCRegisterFlight(group:GetName(),Tnow)
+self.ratcraft[i].status="Holding"
+end
+if(forID and i==forID)or(not forID)then
+local text=string.format("ID %i of group %s\n",i,prefix)
+if self.commute then
+text=text..string.format("%s commuting between %s and %s\n",type,departure,destination)
+elseif self.continuejourney then
+text=text..string.format("%s travelling from %s to %s (and continueing form there)\n",type,departure,destination)
+else
+text=text..string.format("%s travelling from %s to %s\n",type,departure,destination)
+end
+text=text..string.format("Status: %s",self.ratcraft[i].status)
+if airborne then
+text=text.." [airborne]\n"
+else
+text=text.." [on ground]\n"
+end
+text=text..string.format("Fuel = %3.0f %%\n",fuel)
+text=text..string.format("Life = %3.0f %%\n",life)
+text=text..string.format("FL%03d = %i m\n",alt/RAT.unit.FL2m,alt)
+text=text..string.format("Distance travelled = %6.1f km\n",self.ratcraft[i]["Distance"]/1000)
+text=text..string.format("Distance to destination = %6.1f km",Dholding/1000)
+if not airborne then
+text=text..string.format("\nTime on ground = %6.0f seconds\n",Tg)
+text=text..string.format("Position change = %8.1f m since %3.0f seconds.",Dg,dTlast)
+end
+if self.debug then
+env.info(RAT.id..text)
+end
+if self.reportstatus or message then
+MESSAGE:New(text,20):ToAll()
+end
+end
+if not airborne then
+if stationary then
+local text=string.format("Group %s is despawned after being %4.0f seconds inaktive on ground.",self.SpawnTemplatePrefix,dTlast)
+env.info(RAT.id..text)
+self:_Despawn(group)
+end
+if life<10 and Dtravel<100 then
+local text=string.format("Damaged group %s is despawned. Life = %3.0f",self.SpawnTemplatePrefix,life)
+self:_Despawn(group)
+end
+end
+end
+else
+if self.debug then
+local text=string.format("Group %i does not exist.",i)
+env.info(RAT.id..text)
+end
+end
+end
+end
+function RAT:_GetLife(group)
+local life=0.0
+if group and group:IsAlive()then
+local unit=group:GetUnit(1)
+if unit then
+life=unit:GetLife()/unit:GetLife0()*100
+else
+if self.debug then
+env.error(RAT.id.."Unit does not exist in RAT_Getlife(). Returning zero.")
+end
+end
+else
+if self.debug then
+env.error(RAT.id.."Group does not exist in RAT_Getlife(). Returning zero.")
+end
+end
+return life
+end
+function RAT:_SetStatus(group,status)
+local index=self:GetSpawnIndexFromGroup(group)
+env.info(RAT.id.."Status for group "..group:GetName()..": "..status)
+self.ratcraft[index].status=status
+end
+function RAT:_OnBirth(EventData)
+local SpawnGroup=EventData.IniGroup
+if SpawnGroup then
+local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
+if EventPrefix then
+if EventPrefix==self.alias then
+local text="Event: Group "..SpawnGroup:GetName().." was born."
+env.info(RAT.id..text)
+local status
+if SpawnGroup:InAir()then
+status="Just born (after air start)"
+else
+status="Starting engines (after birth)"
+end
+self:_SetStatus(SpawnGroup,status)
+end
+end
+else
+if self.debug then
+env.error("Group does not exist in RAT:_OnBirthDay().")
+end
+end
+end
+function RAT:_EngineStartup(EventData)
+local SpawnGroup=EventData.IniGroup
+if SpawnGroup then
+local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
+if EventPrefix then
+if EventPrefix==self.alias then
+local text="Event: Group "..SpawnGroup:GetName().." started engines."
+env.info(RAT.id..text)
+local status
+if SpawnGroup:InAir()then
+status="On journey (after air start)"
+else
+status="Taxiing (after engines started)"
+end
+self:_SetStatus(SpawnGroup,status)
+end
+end
+else
+if self.debug then
+env.error("Group does not exist in RAT:_EngineStartup().")
+end
+end
+end
+function RAT:_OnTakeoff(EventData)
+local SpawnGroup=EventData.IniGroup
+if SpawnGroup then
+local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
+if EventPrefix then
+if EventPrefix==self.alias then
+local text="Event: Group "..SpawnGroup:GetName().." is airborne."
+env.info(RAT.id..text)
+self:_SetStatus(SpawnGroup,"On journey (after takeoff)")
+end
+end
+else
+if self.debug then
+env.error("Group does not exist in RAT:_OnTakeoff().")
+end
+end
+end
+function RAT:_OnLand(EventData)
+local SpawnGroup=EventData.IniGroup
+if SpawnGroup then
+local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
+if EventPrefix then
+if EventPrefix==self.alias then
+local text="Event: Group "..SpawnGroup:GetName().." landed."
+env.info(RAT.id..text)
+self:_SetStatus(SpawnGroup,"Taxiing (after landing)")
+if self.ATCswitch then
+RAT:_ATCFlightLanded(SpawnGroup:GetName())
+end
+if self.respawn_at_landing then
+text="Event: Group "..SpawnGroup:GetName().." will be respawned."
+env.info(RAT.id..text)
+self:_Respawn(SpawnGroup)
+end
+end
+end
+else
+if self.debug then
+env.error("Group does not exist in RAT:_OnLand().")
+end
+end
+end
+function RAT:_OnEngineShutdown(EventData)
+local SpawnGroup=EventData.IniGroup
+if SpawnGroup then
+local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
+if EventPrefix then
+if EventPrefix==self.alias then
+local text="Event: Group "..SpawnGroup:GetName().." shut down its engines."
+env.info(RAT.id..text)
+self:_SetStatus(SpawnGroup,"Parking (shutting down engines)")
+if not self.respawn_at_landing then
+text="Event: Group "..SpawnGroup:GetName().." will be respawned."
+env.info(RAT.id..text)
+self:_Respawn(SpawnGroup)
+end
+text="Event: Group "..SpawnGroup:GetName().." will be destroyed now."
+env.info(RAT.id..text)
+self:_Despawn(SpawnGroup)
+end
+end
+else
+if self.debug then
+env.error("Group does not exist in RAT:_OnEngineShutdown().")
+end
+end
+end
+function RAT:_OnDead(EventData)
+local SpawnGroup=EventData.IniGroup
+if SpawnGroup then
+local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
+if EventPrefix then
+if EventPrefix==self.alias then
+local text="Event: Group "..SpawnGroup:GetName().." died."
+env.info(RAT.id..text)
+self:_SetStatus(SpawnGroup,"Destroyed (after dead)")
+end
+end
+else
+if self.debug then
+env.error("Group does not exist in RAT:_OnDead().")
+end
+end
+end
+function RAT:_OnCrash(EventData)
+local SpawnGroup=EventData.IniGroup
+env.info(string.format("%sGroup %s crashed!",RAT.id,SpawnGroup:GetName()))
+if SpawnGroup then
+local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
+if EventPrefix then
+if EventPrefix==self.alias then
+local text="Event: Group "..SpawnGroup:GetName().." crashed."
+env.info(RAT.id..text)
+self:_SetStatus(SpawnGroup,"Crashed")
+end
+end
+else
+if self.debug then
+env.error("Group does not exist in RAT:_OnCrash().")
+end
+end
+end
+function RAT:_Despawn(group)
+local index=self:GetSpawnIndexFromGroup(group)
+self.ratcraft[index].group:Destroy()
+self.ratcraft[index].group=nil
+self.alive=self.alive-1
+if self.f10menu then
+self.Menu[self.SubMenuName]["groups"][index]:Remove()
+end
+end
+function RAT:_Waypoint(Type,Coord,Speed,Altitude,Airport)
+local _Altitude=Altitude or Coord.y
+local Hland=Coord:GetLandHeight()
+local _Type=nil
+local _Action=nil
+local _alttype="RADIO"
+local _AID=nil
+if Type==RAT.wp.cold then
+_Type="TakeOffParking"
+_Action="From Parking Area"
+_Altitude=0
+_alttype="RADIO"
+_AID=Airport:GetID()
+elseif Type==RAT.wp.hot then
+_Type="TakeOffParkingHot"
+_Action="From Parking Area Hot"
+_Altitude=0
+_alttype="RADIO"
+_AID=Airport:GetID()
+elseif Type==RAT.wp.runway then
+_Type="TakeOff"
+_Action="From Parking Area"
+_Altitude=0
+_alttype="RADIO"
+_AID=Airport:GetID()
+elseif Type==RAT.wp.air then
+_Type="Turning Point"
+_Action="Turning Point"
+_alttype="BARO"
+elseif Type==RAT.wp.climb then
+_Type="Turning Point"
+_Action="Turning Point"
+_alttype="BARO"
+elseif Type==RAT.wp.cruise then
+_Type="Turning Point"
+_Action="Turning Point"
+_alttype="BARO"
+elseif Type==RAT.wp.descent then
+_Type="Turning Point"
+_Action="Turning Point"
+_alttype="BARO"
+elseif Type==RAT.wp.holding then
+_Type="Turning Point"
+_Action="Turning Point"
+_alttype="BARO"
+elseif Type==RAT.wp.landing then
+_Type="Land"
+_Action="Landing"
+_Altitude=0
+_alttype="RADIO"
+_AID=Airport:GetID()
+else
+env.error("Unknown waypoint type in RAT:Waypoint() function!")
+_Type="Turning Point"
+_Action="Turning Point"
+_alttype="RADIO"
+end
+local text=string.format("\n******************************************************\n")
+text=text..string.format("Template = %s\n",self.SpawnTemplatePrefix)
+text=text..string.format("Type: %i - %s\n",Type,_Type)
+text=text..string.format("Action: %s\n",_Action)
+text=text..string.format("Coord: x = %6.1f km, y = %6.1f km, alt = %6.1f m\n",Coord.x/1000,Coord.z/1000,Coord.y)
+text=text..string.format("Speed = %6.1f m/s = %6.1f km/h = %6.1f knots\n",Speed,Speed*3.6,Speed*1.94384)
+text=text..string.format("Land = %6.1f m ASL\n",Hland)
+text=text..string.format("Altitude = %6.1f m (%s)\n",_Altitude,_alttype)
+if Airport then
+if Type==RAT.wp.air then
+text=text..string.format("Zone = %s\n",Airport:GetName())
+else
+text=text..string.format("Airport = %s with ID %i\n",Airport:GetName(),Airport:GetID())
+end
+else
+text=text..string.format("No airport/zone specified\n")
+end
+text=text.."******************************************************\n"
+if self.debug then
+env.info(RAT.id..text)
+end
+local RoutePoint={}
+RoutePoint.x=Coord.x
+RoutePoint.y=Coord.z
+RoutePoint.alt=_Altitude
+RoutePoint.alt_type=_alttype
+RoutePoint.type=_Type
+RoutePoint.action=_Action
+RoutePoint.speed=Speed
+RoutePoint.speed_locked=true
+RoutePoint.ETA=nil
+RoutePoint.ETA_locked=false
+RoutePoint.name="RAT waypoint"
+if(Airport~=nil)and Type~=RAT.wp.air then
+local AirbaseID=Airport:GetID()
+local AirbaseCategory=Airport:GetDesc().category
+if AirbaseCategory==Airbase.Category.SHIP then
+RoutePoint.linkUnit=AirbaseID
+RoutePoint.helipadId=AirbaseID
+elseif AirbaseCategory==Airbase.Category.HELIPAD then
+RoutePoint.linkUnit=AirbaseID
+RoutePoint.helipadId=AirbaseID
+elseif AirbaseCategory==Airbase.Category.AIRDROME then
+RoutePoint.airdromeId=AirbaseID
+else
+end
+end
+RoutePoint.properties={
+["vnav"]=1,
+["scale"]=0,
+["angle"]=0,
+["vangle"]=0,
+["steer"]=2,
+}
+if Type==RAT.wp.holding then
+local Duration=self:_Randomize(90,0.9)
+RoutePoint.task=self:_TaskHolding({x=Coord.x,y=Coord.z},Altitude,Speed,Duration)
+else
+RoutePoint.task={}
+RoutePoint.task.id="ComboTask"
+RoutePoint.task.params={}
+RoutePoint.task.params.tasks={}
+end
+return RoutePoint
+end
+function RAT:_Routeinfo(waypoints,comment)
+local text=string.format("\n******************************************************\n")
+text=text..string.format("Template = %s\n",self.SpawnTemplatePrefix)
+if comment then
+text=text..comment.."\n"
+end
+text=text..string.format("Number of waypoints = %i\n",#waypoints)
+for i=1,#waypoints do
+local p=waypoints[i]
+text=text..string.format("WP #%i: x = %6.1f km, y = %6.1f km, alt = %6.1f m\n",i-1,p.x/1000,p.y/1000,p.alt)
+end
+local total=0.0
+for i=1,#waypoints-1 do
+local point1=waypoints[i]
+local point2=waypoints[i+1]
+local x1=point1.x
+local y1=point1.y
+local x2=point2.x
+local y2=point2.y
+local d=math.sqrt((x1-x2)^2+(y1-y2)^2)
+local heading=self:_Course(point1,point2)
+total=total+d
+text=text..string.format("Distance from WP %i-->%i = %6.1f km. Heading = %i.\n",i-1,i,d/1000,heading)
+end
+text=text..string.format("Total distance = %6.1f km\n",total/1000)
+local text=string.format("******************************************************\n")
+if self.debug then
+env.info(RAT.id..text)
+end
+return total
+end
+function RAT:_TaskHolding(P1,Altitude,Speed,Duration)
+local dx=3000
+local dy=0
+if self.category==RAT.cat.heli then
+dx=200
+dy=0
+end
+local P2={}
+P2.x=P1.x+dx
+P2.y=P1.y+dy
+local Task={
+id='Orbit',
+params={
+pattern=AI.Task.OrbitPattern.RACE_TRACK,
+point=P1,
+point2=P2,
+speed=Speed,
+altitude=Altitude
+}
+}
+local DCSTask={}
+DCSTask.id="ControlledTask"
+DCSTask.params={}
+DCSTask.params.task=Task
+if self.ATCswitch then
+local userflagname=string.format("%s#%03d",self.alias,self.SpawnIndex+1)
+DCSTask.params.stopCondition={userFlag=userflagname,userFlagValue=1,duration=1800}
+else
+DCSTask.params.stopCondition={duration=Duration}
+end
+return DCSTask
+end
+function RAT:_FLmax(alpha,beta,d,phi,h0)
+local gamma=math.rad(180)-alpha-beta
+local a=d*math.sin(alpha)/math.sin(gamma)
+local b=d*math.sin(beta)/math.sin(gamma)
+local h1=b*math.sin(alpha)
+local h2=a*math.sin(beta)
+local h3=b*math.cos(math.pi/2-(alpha+phi))
+local text=string.format("\nFLmax = FL%3.0f = %6.1f m.\n",h1/RAT.unit.FL2m,h1)
+text=text..string.format("FLmax = FL%3.0f = %6.1f m.\n",h2/RAT.unit.FL2m,h2)
+text=text..string.format("FLmax = FL%3.0f = %6.1f m.",h3/RAT.unit.FL2m,h3)
+if self.debug then
+env.info(RAT.id..text)
+end
+return h3+h0
+end
+function RAT:_MinDistance(alpha,beta,h)
+local d1=h/math.tan(alpha)
+local d2=h/math.tan(beta)
+return d1+d2
+end
+function RAT:_AirportExists(name)
+for _,airport in pairs(self.airports_map)do
+if airport:GetName()==name then
+return true
+end
+end
+return false
+end
+function RAT:_SetROE(group,roe)
+env.info(RAT.id.."Setting ROE to "..roe.." for group "..group:GetName())
+if self.roe==RAT.ROE.returnfire then
+group:OptionROEReturnFire()
+elseif self.roe==RAT.ROE.weaponfree then
+group:OptionROEWeaponFree()
+else
+group:OptionROEHoldFire()
+end
+end
+function RAT:_SetROT(group,rot)
+env.info(RAT.id.."Setting ROT to "..rot.." for group "..group:GetName())
+if self.rot==RAT.ROT.passive then
+group:OptionROTPassiveDefense()
+elseif self.rot==RAT.ROT.evade then
+group:OptionROTEvadeFire()
+else
+group:OptionROTNoReaction()
+end
+end
+function RAT:_SetCoalitionTable()
+if self.friendly==RAT.coal.neutral then
+self.ctable={coalition.side.NEUTRAL}
+elseif self.friendly==RAT.coal.same then
+self.ctable={self.coalition,coalition.side.NEUTRAL}
+elseif self.friendly==RAT.coal.sameonly then
+self.ctable={self.coalition}
+else
+env.error("Unknown friendly coalition in _SetCoalitionTable(). Defaulting to NEUTRAL.")
+self.ctable={self.coalition,coalition.side.NEUTRAL}
+end
+end
+function RAT:_Course(a,b)
+local dx=b.x-a.x
+local ay
+if a.alt then
+ay=a.y
+else
+ay=a.z
+end
+local by
+if b.alt then
+by=b.y
+else
+by=b.z
+end
+local dy=by-ay
+local angle=math.deg(math.atan2(dy,dx))
+if angle<0 then
+angle=360+angle
+end
+return angle
+end
+function RAT:_Randomize(value,fac,lower,upper)
+local min
+if lower then
+min=math.max(value-value*fac,lower)
+else
+min=value-value*fac
+end
+local max
+if upper then
+max=math.min(value+value*fac,upper)
+else
+max=value+value*fac
+end
+local r=math.random(min,max)
+if self.debug then
+local text=string.format("Random: value = %6.2f, fac = %4.2f, min = %6.2f, max = %6.2f, r = %6.2f",value,fac,min,max,r)
+env.info(RAT.id..text)
+end
+return r
+end
+function RAT:_Random_Gaussian(x0,sigma,xmin,xmax)
+sigma=sigma or 10
+local r
+local gotit=false
+local i=0
+while not gotit do
+local x1=math.random()
+local x2=math.random()
+r=math.sqrt(-2*sigma*sigma*math.log(x1))*math.cos(2*math.pi*x2)+x0
+i=i+1
+if(r>=xmin and r<=xmax)or i>100 then
+gotit=true
+end
+end
+return r
+end
+function RAT:_PlaceMarkers(waypoints)
+self:_SetMarker("Takeoff",waypoints[1])
+self:_SetMarker("Climb",waypoints[2])
+self:_SetMarker("Begin of Cruise",waypoints[3])
+self:_SetMarker("End of Cruise",waypoints[4])
+self:_SetMarker("Descent",waypoints[5])
+self:_SetMarker("Holding Point",waypoints[6])
+self:_SetMarker("Destination",waypoints[7])
+end
+function RAT:_SetMarker(text,wp)
+RAT.markerid=RAT.markerid+1
+self.markerids[#self.markerids+1]=RAT.markerid
+if self.debug then
+env.info(RAT.id..self.SpawnTemplatePrefix..": placing marker with ID "..RAT.markerid..": "..text)
+end
+local vec={x=wp.x,y=wp.alt,z=wp.y}
+local text1=string.format("%s:\n%s",self.alias,text)
+trigger.action.markToAll(RAT.markerid,text1,vec)
+end
+function RAT:_DeleteMarkers()
+for k,v in ipairs(self.markerids)do
+trigger.action.removeMark(v)
+end
+for k,v in ipairs(self.markerids)do
+self.markerids[k]=nil
+end
+end
+function RAT:_ModifySpawnTemplate(waypoints)
+local PointVec3={x=waypoints[1].x,y=waypoints[1].alt,z=waypoints[1].y}
+local heading=self:_Course(waypoints[1],waypoints[2])
+if self:_GetSpawnIndex(self.SpawnIndex+1)then
+local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate
+if SpawnTemplate then
+self:T(SpawnTemplate)
+for UnitID=1,#SpawnTemplate.units do
+self:T('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
+local UnitTemplate=SpawnTemplate.units[UnitID]
+local SX=UnitTemplate.x
+local SY=UnitTemplate.y
+local BX=SpawnTemplate.route.points[1].x
+local BY=SpawnTemplate.route.points[1].y
+local TX=PointVec3.x+(SX-BX)
+local TY=PointVec3.z+(SY-BY)
+SpawnTemplate.units[UnitID].x=TX
+SpawnTemplate.units[UnitID].y=TY
+SpawnTemplate.units[UnitID].alt=PointVec3.y
+SpawnTemplate.units[UnitID].heading=math.rad(heading)
+if self.livery then
+SpawnTemplate.units[UnitID].livery_id=self.livery[math.random(#self.livery)]
+end
+SpawnTemplate.units[UnitID]["skill"]=self.skill
+SpawnTemplate.units[UnitID]["onboard_num"]=self.SpawnIndex
+SpawnTemplate.CoalitionID=self.coalition
+if self.country then
+SpawnTemplate.CountryID=self.country
+end
+UnitTemplate.parking=nil
+UnitTemplate.parking_id=nil
+UnitTemplate.alt=PointVec3.y
+self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
+end
+for i,wp in ipairs(waypoints)do
+SpawnTemplate.route.points[i]=wp
+end
+SpawnTemplate.x=PointVec3.x
+SpawnTemplate.y=PointVec3.z
+self.SpawnGroups[self.SpawnIndex].SpawnTemplate=SpawnTemplate
+self:T(SpawnTemplate)
+end
+end
+end
+function RAT:_ATCInit(airports_map)
+if not RAT.ATC.init then
+env.info(RAT.id.."Starting RAT ATC.")
+RAT.ATC.init=true
+for _,ap in pairs(airports_map)do
+local name=ap:GetName()
+RAT.ATC.airport[name]={}
+RAT.ATC.airport[name].queue={}
+RAT.ATC.airport[name].busy=false
+RAT.ATC.airport[name].onfinal=nil
+RAT.ATC.airport[name].traffic=0
+end
+SCHEDULER:New(nil,RAT._ATCCheck,{self},5,15)
+SCHEDULER:New(nil,RAT._ATCStatus,{self},5,60)
+RAT.ATC.T0=timer.getTime()
+end
+end
+function RAT:_ATCAddFlight(name,dest)
+env.info(string.format("%s%s ATC: Adding flight %s with destination %s.",RAT.id,dest,name,dest))
+RAT.ATC.flight[name]={}
+RAT.ATC.flight[name].destination=dest
+RAT.ATC.flight[name].Tarrive=-1
+RAT.ATC.flight[name].holding=-1
+RAT.ATC.flight[name].Tonfinal=-1
+end
+function RAT:_ATCDelFlight(t,entry)
+for k,_ in pairs(t)do
+if k==entry then
+t[entry]=nil
+end
+end
+end
+function RAT:_ATCRegisterFlight(name,time)
+RAT.ATC.flight[name].Tarrive=time
+RAT.ATC.flight[name].holding=0
+end
+function RAT:_ATCStatus()
+local Tnow=timer.getTime()
+for name,_ in pairs(RAT.ATC.flight)do
+local hold=RAT.ATC.flight[name].holding
+local dest=RAT.ATC.flight[name].destination
+if hold>=0 then
+local busy="Runway is currently clear"
+if RAT.ATC.airport[dest].busy then
+if RAT.ATC.airport[dest].onfinal then
+busy="Runway is occupied by "..RAT.ATC.airport[dest].onfinal
+else
+busy="Runway is occupied"
+end
+end
+env.info(string.format("%s%s ATC: Flight %s is holding for %i:%02d. %s.",RAT.id,dest,name,hold/60,hold%60,busy))
+elseif hold==RAT.ATC.onfinal then
+local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal
+env.info(string.format("%s%s ATC: Flight %s was cleared for landing. Waiting %i:%02d for landing event.",RAT.id,dest,name,Tfinal/60,Tfinal%60))
+if Tfinal>300 then
+end
+elseif hold==RAT.ATC.unregistered then
+else
+env.error(RAT.id.."Unknown holding time in RAT:_ATCStatus().")
+end
+end
+end
+function RAT:_ATCCheck()
+RAT:_ATCQueue()
+local Tnow=timer.getTime()
+for name,_ in pairs(RAT.ATC.airport)do
+local qw={}
+for qID,flight in ipairs(RAT.ATC.airport[name].queue)do
+local nqueue=#RAT.ATC.airport[name].queue
+if RAT.ATC.airport[name].busy then
+RAT.ATC.flight[flight].holding=Tnow-RAT.ATC.flight[flight].Tarrive
+local text=string.format("%s ATC: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.",name,flight,qID,nqueue,RAT.ATC.flight[flight].holding/60,RAT.ATC.flight[flight].holding%60)
+env.info(text)
+else
+RAT:_ATCClearForLanding(name,flight)
+table.insert(qw,qID)
+end
+end
+for _,i in pairs(qw)do
+table.remove(RAT.ATC.airport[name].queue,i)
+end
+end
+end
+function RAT:_ATCClearForLanding(airport,flight)
+RAT.ATC.flight[flight].holding=RAT.ATC.onfinal
+RAT.ATC.airport[airport].busy=true
+RAT.ATC.airport[airport].onfinal=flight
+RAT.ATC.flight[flight].Tonfinal=timer.getTime()
+trigger.action.setUserFlag(flight,1)
+local flagvalue=trigger.misc.getUserFlag(flight)
+local text1=string.format("%s%s ATC: Flight %s cleared for final approach (flag=%d).",RAT.id,airport,flight,flagvalue)
+local text2=string.format("%s ATC: Flight %s you are cleared for landing.",airport,flight)
+env.info(text1)
+MESSAGE:New(text2,10):ToAll()
+end
+function RAT:_ATCFlightLanded(name)
+if RAT.ATC.flight[name]then
+local dest=RAT.ATC.flight[name].destination
+local Tnow=timer.getTime()
+local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal
+local Thold=RAT.ATC.flight[name].Tonfinal-RAT.ATC.flight[name].Tarrive
+RAT.ATC.airport[dest].busy=false
+RAT.ATC.airport[dest].onfinal=nil
+RAT:_ATCDelFlight(RAT.ATC.flight,name)
+RAT.ATC.airport[dest].traffic=RAT.ATC.airport[dest].traffic+1
+local text1=string.format("%s%s ATC: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",RAT.id,dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)
+local text2=string.format("%s ATC: Flight %s landed. Welcome to %s.",dest,name,dest)
+env.info(text1)
+env.info(string.format("%s%s ATC: Number of planes landed in total %d.",RAT.id,dest,RAT.ATC.airport[dest].traffic))
+MESSAGE:New(text2,10):ToAll()
+end
+end
+function RAT:_ATCQueue()
+for airport,_ in pairs(RAT.ATC.airport)do
+local _queue={}
+for name,_ in pairs(RAT.ATC.flight)do
+local hold=RAT.ATC.flight[name].holding
+local dest=RAT.ATC.flight[name].destination
+if hold>=0 and airport==dest then
+_queue[#_queue+1]={name,hold}
+end
+end
+local function compare(a,b)
+return a[2]>b[2]
+end
+table.sort(_queue,compare)
+RAT.ATC.airport[airport].queue={}
+for k,v in ipairs(_queue)do
+table.insert(RAT.ATC.airport[airport].queue,v[1])
+end
+end
+end
+do
+ZONE_GOAL={
+ClassName="ZONE_GOAL",
+}
+function ZONE_GOAL:New(Zone)
+local self=BASE:Inherit(self,FSM:New())
+self:F({Zone=Zone})
+self.Zone=Zone
+self.Goal=GOAL:New()
+self.SmokeTime=nil
+self:AddTransition("*","DestroyedUnit","*")
+return self
+end
+function ZONE_GOAL:GetZone()
+return self.Zone
+end
+function ZONE_GOAL:GetZoneName()
+return self.Zone:GetName()
+end
+function ZONE_GOAL:Smoke(SmokeColor)
+self:F({SmokeColor=SmokeColor})
+self.SmokeColor=SmokeColor
+end
+function ZONE_GOAL:Flare(FlareColor)
+self.Zone:FlareZone(FlareColor,math.random(1,360))
+end
+function ZONE_GOAL:onafterGuard()
+self:E("Guard")
+if not self.SmokeScheduler then
+self.SmokeScheduler=self:ScheduleRepeat(1,1,0.1,nil,self.StatusSmoke,self)
+end
+end
+function ZONE_GOAL:StatusSmoke()
+self:F({self.SmokeTime,self.SmokeColor})
+local CurrentTime=timer.getTime()
+if self.SmokeTime==nil or self.SmokeTime+300<=CurrentTime then
+if self.SmokeColor then
+self.Zone:GetCoordinate():Smoke(self.SmokeColor)
+self.SmokeTime=CurrentTime
+end
+end
+end
+function ZONE_GOAL:__Destroyed(EventData)
+self:E({"EventDead",EventData})
+self:E({EventData.IniUnit})
+local Vec3=EventData.IniDCSUnit:getPosition().p
+self:E({Vec3=Vec3})
+local ZoneGoal=self:GetZone()
+self:E({ZoneGoal})
+if EventData.IniDCSUnit then
+if ZoneGoal:IsVec3InZone(Vec3)then
+local PlayerHits=_DATABASE.HITS[EventData.IniUnitName]
+if PlayerHits then
+for PlayerName,PlayerHit in pairs(PlayerHits.Players or{})do
+self.Goal:AddPlayerContribution(PlayerName)
+self:DestroyedUnit(EventData.IniUnitName,PlayerName)
+end
+end
+end
+end
+end
+function ZONE_GOAL:MonitorDestroyedUnits()
+self:HandleEvent(EVENTS.Dead,self.__Destroyed)
+self:HandleEvent(EVENTS.Crash,self.__Destroyed)
+end
+end
+do
+ZONE_GOAL_COALITION={
+ClassName="ZONE_GOAL_COALITION",
+}
+ZONE_GOAL_COALITION.States={}
+function ZONE_GOAL_COALITION:New(Zone,Coalition)
+local self=BASE:Inherit(self,ZONE_GOAL:New(Zone))
+self:F({Zone=Zone,Coalition=Coalition})
+self:SetCoalition(Coalition)
+do
+end
+do
+end
+do
+end
+do
+end
+self:AddTransition("*","Guard","Guarded")
+self:AddTransition("*","Empty","Empty")
+self:AddTransition({"Guarded","Empty"},"Attack","Attacked")
+self:AddTransition({"Guarded","Attacked","Empty"},"Capture","Captured")
+return self
+end
+function ZONE_GOAL_COALITION:SetCoalition(Coalition)
+self.Coalition=Coalition
+end
+function ZONE_GOAL_COALITION:GetCoalition()
+return self.Coalition
+end
+function ZONE_GOAL_COALITION:GetCoalitionName()
+if self.Coalition==coalition.side.BLUE then
+return"Blue"
+end
+if self.Coalition==coalition.side.RED then
+return"Red"
+end
+if self.Coalition==coalition.side.NEUTRAL then
+return"Neutral"
+end
+return""
+end
+function ZONE_GOAL_COALITION:IsGuarded()
+local IsGuarded=self.Zone:IsAllInZoneOfCoalition(self.Coalition)
+self:E({IsGuarded=IsGuarded})
+return IsGuarded
+end
+function ZONE_GOAL_COALITION:IsEmpty()
+local IsEmpty=self.Zone:IsNoneInZone()
+self:E({IsEmpty=IsEmpty})
+return IsEmpty
+end
+function ZONE_GOAL_COALITION:IsCaptured()
+local IsCaptured=self.Zone:IsAllInZoneOfOtherCoalition(self.Coalition)
+self:E({IsCaptured=IsCaptured})
+return IsCaptured
+end
+function ZONE_GOAL_COALITION:IsAttacked()
+local IsAttacked=self.Zone:IsSomeInZoneOfCoalition(self.Coalition)
+self:E({IsAttacked=IsAttacked})
+return IsAttacked
+end
+function ZONE_GOAL_COALITION:Mark()
+local Coord=self.Zone:GetCoordinate()
+local ZoneName=self:GetZoneName()
+local State=self:GetState()
+if self.MarkRed and self.MarkBlue then
+self:E({MarkRed=self.MarkRed,MarkBlue=self.MarkBlue})
+Coord:RemoveMark(self.MarkRed)
+Coord:RemoveMark(self.MarkBlue)
+end
+if self.Coalition==coalition.side.BLUE then
+self.MarkBlue=Coord:MarkToCoalitionBlue("Guard Zone: "..ZoneName.."\nStatus: "..State)
+self.MarkRed=Coord:MarkToCoalitionRed("Capture Zone: "..ZoneName.."\nStatus: "..State)
+else
+self.MarkRed=Coord:MarkToCoalitionRed("Guard Zone: "..ZoneName.."\nStatus: "..State)
+self.MarkBlue=Coord:MarkToCoalitionBlue("Capture Zone: "..ZoneName.."\nStatus: "..State)
+end
+end
+function ZONE_GOAL_COALITION:onenterGuarded()
+if self.Coalition==coalition.side.BLUE then
+else
+end
+self:Mark()
+end
+function ZONE_GOAL_COALITION:onenterCaptured()
+local NewCoalition=self.Zone:GetCoalition()
+self:E({NewCoalition=NewCoalition})
+self:SetCoalition(NewCoalition)
+self:Mark()
+end
+function ZONE_GOAL_COALITION:onenterEmpty()
+self:Mark()
+end
+function ZONE_GOAL_COALITION:onenterAttacked()
+self:Mark()
+end
+function ZONE_GOAL_COALITION:onafterGuard()
+if not self.SmokeScheduler then
+self.SmokeScheduler=self:ScheduleRepeat(1,1,0.1,nil,self.StatusSmoke,self)
+end
+if not self.ScheduleStatusZone then
+self.ScheduleStatusZone=self:ScheduleRepeat(15,15,0.1,nil,self.StatusZone,self)
+end
+end
+function ZONE_GOAL_COALITION:IsCaptured()
+local IsCaptured=self.Zone:IsAllInZoneOfOtherCoalition(self.Coalition)
+self:E({IsCaptured=IsCaptured})
+return IsCaptured
+end
+function ZONE_GOAL_COALITION:IsAttacked()
+local IsAttacked=self.Zone:IsSomeInZoneOfCoalition(self.Coalition)
+self:E({IsAttacked=IsAttacked})
+return IsAttacked
+end
+function ZONE_GOAL_COALITION:StatusZone()
+local State=self:GetState()
+self:E({State=self:GetState()})
+self.Zone:Scan()
+if State~="Guarded"and self:IsGuarded()then
+self:Guard()
+end
+if State~="Empty"and self:IsEmpty()then
+self:Empty()
+end
+if State~="Attacked"and self:IsAttacked()then
+self:Attack()
+end
+if State~="Captured"and self:IsCaptured()then
+self:Capture()
+end
+end
+end
+do
+ZONE_CAPTURE_COALITION={
+ClassName="ZONE_CAPTURE_COALITION",
+}
+ZONE_CAPTURE_COALITION.States={}
+function ZONE_CAPTURE_COALITION:New(Zone,Coalition)
+local self=BASE:Inherit(self,ZONE_GOAL_COALITION:New(Zone,Coalition))
+self:F({Zone=Zone,Coalition=Coalition})
+return self
+end
+function ZONE_CAPTURE_COALITION:onenterCaptured()
+self:GetParent(self,ZONE_CAPTURE_COALITION).onenterCaptured(self)
+self.Goal:Achieved()
+end
+end
+AI_BALANCER={
+ClassName="AI_BALANCER",
+PatrolZones={},
+AIGroups={},
+Earliest=5,
+Latest=60,
+}
+function AI_BALANCER:New(SetClient,SpawnAI)
+local self=BASE:Inherit(self,FSM_SET:New(SET_GROUP:New()))
+self:SetStartState("None")
+self:AddTransition("*","Monitor","Monitoring")
+self:AddTransition("*","Spawn","Spawning")
+self:AddTransition("Spawning","Spawned","Spawned")
+self:AddTransition("*","Destroy","Destroying")
+self:AddTransition("*","Return","Returning")
+self.SetClient=SetClient
+self.SetClient:FilterOnce()
+self.SpawnAI=SpawnAI
+self.SpawnQueue={}
+self.ToNearestAirbase=false
+self.ToHomeAirbase=false
+self:__Monitor(1)
+return self
+end
+function AI_BALANCER:InitSpawnInterval(Earliest,Latest)
+self.Earliest=Earliest
+self.Latest=Latest
+return self
+end
+function AI_BALANCER:ReturnToNearestAirbases(ReturnThresholdRange,ReturnAirbaseSet)
+self.ToNearestAirbase=true
+self.ReturnThresholdRange=ReturnThresholdRange
+self.ReturnAirbaseSet=ReturnAirbaseSet
+end
+function AI_BALANCER:ReturnToHomeAirbase(ReturnThresholdRange)
+self.ToHomeAirbase=true
+self.ReturnThresholdRange=ReturnThresholdRange
+end
+function AI_BALANCER:onenterSpawning(SetGroup,From,Event,To,ClientName)
+local AIGroup=self.SpawnAI:Spawn()
+if AIGroup then
+AIGroup:E("Spawning new AIGroup")
+SetGroup:Add(ClientName,AIGroup)
+self.SpawnQueue[ClientName]=nil
+self:Spawned(AIGroup)
+end
+end
+function AI_BALANCER:onenterDestroying(SetGroup,From,Event,To,ClientName,AIGroup)
+AIGroup:Destroy()
+SetGroup:Flush()
+SetGroup:Remove(ClientName)
+SetGroup:Flush()
+end
+function AI_BALANCER:onenterReturning(SetGroup,From,Event,To,AIGroup)
+local AIGroupTemplate=AIGroup:GetTemplate()
+if self.ToHomeAirbase==true then
+local WayPointCount=#AIGroupTemplate.route.points
+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 ClosestAirbase=self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2(PointVec2)
+self:T(ClosestAirbase.AirbaseName)
+AIGroup:MessageToRed("Returning to "..ClosestAirbase:GetName().." ...",30)
+local RTBRoute=AIGroup:RouteReturnToAirbase(ClosestAirbase)
+AIGroupTemplate.route=RTBRoute
+AIGroup:Respawn(AIGroupTemplate)
+end
+end
+function AI_BALANCER:onenterMonitoring(SetGroup)
+self:T2({self.SetClient:Count()})
+self.SetClient:ForEachClient(
+function(Client)
+self:T3(Client.ClientName)
+local AIGroup=self.Set:Get(Client.UnitName)
+if Client:IsAlive()then
+if AIGroup and AIGroup:IsAlive()==true then
+if self.ToNearestAirbase==false and self.ToHomeAirbase==false then
+self:Destroy(Client.UnitName,AIGroup)
+else
+local PlayerInRange={Value=false}
+local RangeZone=ZONE_RADIUS:New('RangeZone',AIGroup:GetVec2(),self.ReturnThresholdRange)
+self:T2(RangeZone)
+_DATABASE:ForEachPlayer(
+function(RangeTestUnit,RangeZone,AIGroup,PlayerInRange)
+self:T2({PlayerInRange,RangeTestUnit.UnitName,RangeZone.ZoneName})
+if RangeTestUnit:IsInZone(RangeZone)==true then
+self:T2("in zone")
+if RangeTestUnit:GetCoalition()~=AIGroup:GetCoalition()then
+self:T2("in range")
+PlayerInRange.Value=true
+end
+end
+end,
+function(RangeZone,AIGroup,PlayerInRange)
+if PlayerInRange.Value==false then
+self:Return(AIGroup)
+end
+end
+,RangeZone,AIGroup,PlayerInRange
+)
+end
+self.Set:Remove(Client.UnitName)
+end
+else
+if not AIGroup or not AIGroup:IsAlive()==true then
+self:T("Client "..Client.UnitName.." not alive.")
+if not self.SpawnQueue[Client.UnitName]then
+self:__Spawn(math.random(self.Earliest,self.Latest),Client.UnitName)
+self.SpawnQueue[Client.UnitName]=true
+self:E("New AI Spawned for Client "..Client.UnitName)
+end
+end
+end
+return true
+end
+)
+self:__Monitor(10)
+end
+AI_A2A={
+ClassName="AI_A2A",
+}
+function AI_A2A:New(AIGroup)
+local self=BASE:Inherit(self,FSM_CONTROLLABLE:New())
+self:SetControllable(AIGroup)
+self:SetFuelThreshold(.2,60)
+self:SetDamageThreshold(0.4)
+self:SetDisengageRadius(70000)
+self:SetStartState("Stopped")
+self:AddTransition("*","Start","Started")
+self:AddTransition("*","Stop","Stopped")
+self:AddTransition("*","Status","*")
+self:AddTransition("*","RTB","*")
+self:AddTransition("Patrolling","Refuel","Refuelling")
+self:AddTransition("*","Takeoff","Airborne")
+self:AddTransition("*","Return","Returning")
+self:AddTransition("*","Hold","Holding")
+self:AddTransition("*","Home","Home")
+self:AddTransition("*","LostControl","LostControl")
+self:AddTransition("*","Fuel","Fuel")
+self:AddTransition("*","Damaged","Damaged")
+self:AddTransition("*","Eject","*")
+self:AddTransition("*","Crash","Crashed")
+self:AddTransition("*","PilotDead","*")
+self.IdleCount=0
+return self
+end
+function GROUP:OnEventTakeoff(EventData,Fsm)
+Fsm:Takeoff()
+self:UnHandleEvent(EVENTS.Takeoff)
+end
+function AI_A2A:SetDispatcher(Dispatcher)
+self.Dispatcher=Dispatcher
+end
+function AI_A2A:GetDispatcher()
+return self.Dispatcher
+end
+function AI_A2A:SetTargetDistance(Coordinate)
+local CurrentCoord=self.Controllable:GetCoordinate()
+self.TargetDistance=CurrentCoord:Get2DDistance(Coordinate)
+self.ClosestTargetDistance=(not self.ClosestTargetDistance or self.ClosestTargetDistance>self.TargetDistance)and self.TargetDistance or self.ClosestTargetDistance
+end
+function AI_A2A:ClearTargetDistance()
+self.TargetDistance=nil
+self.ClosestTargetDistance=nil
+end
+function AI_A2A:SetSpeed(PatrolMinSpeed,PatrolMaxSpeed)
+self:F2({PatrolMinSpeed,PatrolMaxSpeed})
+self.PatrolMinSpeed=PatrolMinSpeed
+self.PatrolMaxSpeed=PatrolMaxSpeed
+end
+function AI_A2A:SetAltitude(PatrolFloorAltitude,PatrolCeilingAltitude)
+self:F2({PatrolFloorAltitude,PatrolCeilingAltitude})
+self.PatrolFloorAltitude=PatrolFloorAltitude
+self.PatrolCeilingAltitude=PatrolCeilingAltitude
+end
+function AI_A2A:SetHomeAirbase(HomeAirbase)
+self:F2({HomeAirbase})
+self.HomeAirbase=HomeAirbase
+end
+function AI_A2A:SetTanker(TankerName)
+self:F2({TankerName})
+self.TankerName=TankerName
+end
+function AI_A2A:SetDisengageRadius(DisengageRadius)
+self:F2({DisengageRadius})
+self.DisengageRadius=DisengageRadius
+end
+function AI_A2A:SetStatusOff()
+self:F2()
+self.CheckStatus=false
+end
+function AI_A2A:SetFuelThreshold(PatrolFuelThresholdPercentage,PatrolOutOfFuelOrbitTime)
+self.PatrolManageFuel=true
+self.PatrolFuelThresholdPercentage=PatrolFuelThresholdPercentage
+self.PatrolOutOfFuelOrbitTime=PatrolOutOfFuelOrbitTime
+self.Controllable:OptionRTBBingoFuel(false)
+return self
+end
+function AI_A2A:SetDamageThreshold(PatrolDamageThreshold)
+self.PatrolManageDamage=true
+self.PatrolDamageThreshold=PatrolDamageThreshold
+return self
+end
+function AI_A2A:onafterStart(Controllable,From,Event,To)
+self:F2()
+self:__Status(10)
+self:HandleEvent(EVENTS.PilotDead,self.OnPilotDead)
+self:HandleEvent(EVENTS.Crash,self.OnCrash)
+self:HandleEvent(EVENTS.Ejection,self.OnEjection)
+Controllable:OptionROEHoldFire()
+Controllable:OptionROTVertical()
+end
+function AI_A2A:onbeforeStatus()
+return self.CheckStatus
+end
+function AI_A2A:onafterStatus()
+self:F(" Checking Status")
+if self.Controllable and self.Controllable:IsAlive()then
+local RTB=false
+local DistanceFromHomeBase=self.HomeAirbase:GetCoordinate():Get2DDistance(self.Controllable:GetCoordinate())
+if not self:Is("Holding")and not self:Is("Returning")then
+local DistanceFromHomeBase=self.HomeAirbase:GetCoordinate():Get2DDistance(self.Controllable:GetCoordinate())
+self:F({DistanceFromHomeBase=DistanceFromHomeBase})
+if DistanceFromHomeBase>self.DisengageRadius then
+self:E(self.Controllable:GetName().." is too far from home base, RTB!")
+self:Hold(300)
+RTB=false
+end
+end
+if self:Is("Fuel")or self:Is("Damaged")or self:Is("LostControl")then
+if DistanceFromHomeBase<5000 then
+self:E(self.Controllable:GetName().." is too far from home base, RTB!")
+self:Home("Destroy")
+end
+end
+if not self:Is("Fuel")and not self:Is("Home")then
+local Fuel=self.Controllable:GetFuel()
+self:F({Fuel=Fuel})
+if Fuel=2 then
+if Damage~=InitialLife then
+self:Damaged()
+else
+self:E(self.Controllable:GetName().." control lost! ")
+self:LostControl()
+end
+else
+self.IdleCount=self.IdleCount+1
+end
+end
+else
+self.IdleCount=0
+end
+if RTB==true then
+self:__RTB(0.5)
+end
+self:__Status(10)
+end
+end
+function AI_A2A.RTBRoute(AIGroup,Fsm)
+AIGroup:F({"AI_A2A.RTBRoute:",AIGroup:GetName()})
+if AIGroup:IsAlive()then
+Fsm:__RTB(0.5)
+end
+end
+function AI_A2A.RTBHold(AIGroup,Fsm)
+AIGroup:F({"AI_A2A.RTBHold:",AIGroup:GetName()})
+if AIGroup:IsAlive()then
+Fsm:__RTB(0.5)
+Fsm:Return()
+local Task=AIGroup:TaskOrbitCircle(4000,400)
+AIGroup:SetTask(Task)
+end
+end
+function AI_A2A:onafterRTB(AIGroup,From,Event,To)
+self:F({AIGroup,From,Event,To})
+if AIGroup and AIGroup:IsAlive()then
+self:E("Group "..AIGroup:GetName().." ... RTB! ( "..self:GetState().." )")
+self:ClearTargetDistance()
+AIGroup:ClearTasks()
+local EngageRoute={}
+local CurrentCoord=AIGroup:GetCoordinate()
+local ToTargetCoord=self.HomeAirbase:GetCoordinate()
+local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed)
+local ToAirbaseAngle=CurrentCoord:GetAngleDegrees(CurrentCoord:GetDirectionVec3(ToTargetCoord))
+local Distance=CurrentCoord:Get2DDistance(ToTargetCoord)
+local ToAirbaseCoord=CurrentCoord:Translate(5000,ToAirbaseAngle)
+if Distance<5000 then
+self:E("RTB and near the airbase!")
+self:Home()
+return
+end
+local ToRTBRoutePoint=ToAirbaseCoord:WaypointAir(
+self.PatrolAltType,
+POINT_VEC3.RoutePointType.TurningPoint,
+POINT_VEC3.RoutePointAction.TurningPoint,
+ToTargetSpeed,
+true
+)
+self:F({Angle=ToAirbaseAngle,ToTargetSpeed=ToTargetSpeed})
+self:T2({self.MinSpeed,self.MaxSpeed,ToTargetSpeed})
+EngageRoute[#EngageRoute+1]=ToRTBRoutePoint
+EngageRoute[#EngageRoute+1]=ToRTBRoutePoint
+AIGroup:OptionROEHoldFire()
+AIGroup:OptionROTEvadeFire()
+AIGroup:WayPointInitialize(EngageRoute)
+local Tasks={}
+Tasks[#Tasks+1]=AIGroup:TaskFunction("AI_A2A.RTBRoute",self)
+EngageRoute[#EngageRoute].task=AIGroup:TaskCombo(Tasks)
+AIGroup:Route(EngageRoute,0.5)
+end
+end
+function AI_A2A:onafterHome(AIGroup,From,Event,To)
+self:F({AIGroup,From,Event,To})
+self:E("Group "..self.Controllable:GetName().." ... Home! ( "..self:GetState().." )")
+if AIGroup and AIGroup:IsAlive()then
+end
+end
+function AI_A2A:onafterHold(AIGroup,From,Event,To,HoldTime)
+self:F({AIGroup,From,Event,To})
+self:E("Group "..self.Controllable:GetName().." ... Holding! ( "..self:GetState().." )")
+if AIGroup and AIGroup:IsAlive()then
+local OrbitTask=AIGroup:TaskOrbitCircle(math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude),self.PatrolMinSpeed)
+local TimedOrbitTask=AIGroup:TaskControlled(OrbitTask,AIGroup:TaskCondition(nil,nil,nil,nil,HoldTime,nil))
+local RTBTask=AIGroup:TaskFunction("AI_A2A.RTBHold",self)
+local OrbitHoldTask=AIGroup:TaskOrbitCircle(4000,self.PatrolMinSpeed)
+AIGroup:SetTask(AIGroup:TaskCombo({TimedOrbitTask,RTBTask,OrbitHoldTask}),1)
+end
+end
+function AI_A2A.Resume(AIGroup,Fsm)
+AIGroup:F({"AI_A2A.Resume:",AIGroup:GetName()})
+if AIGroup:IsAlive()then
+Fsm:__RTB(0.5)
+end
+end
+function AI_A2A:onafterRefuel(AIGroup,From,Event,To)
+self:F({AIGroup,From,Event,To})
+self:E("Group "..self.Controllable:GetName().." ... Refuelling! ( "..self:GetState().." )")
+if AIGroup and AIGroup:IsAlive()then
+local Tanker=GROUP:FindByName(self.TankerName)
+if Tanker:IsAlive()and Tanker:IsAirPlane()then
+local RefuelRoute={}
+local CurrentCoord=AIGroup:GetCoordinate()
+local ToRefuelCoord=Tanker:GetCoordinate()
+local ToRefuelSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed)
+local ToRefuelRoutePoint=ToRefuelCoord:WaypointAir(
+self.PatrolAltType,
+POINT_VEC3.RoutePointType.TurningPoint,
+POINT_VEC3.RoutePointAction.TurningPoint,
+ToRefuelSpeed,
+true
+)
+self:F({ToRefuelSpeed=ToRefuelSpeed})
+RefuelRoute[#RefuelRoute+1]=ToRefuelRoutePoint
+RefuelRoute[#RefuelRoute+1]=ToRefuelRoutePoint
+AIGroup:OptionROEHoldFire()
+AIGroup:OptionROTEvadeFire()
+local Tasks={}
+Tasks[#Tasks+1]=AIGroup:TaskRefueling()
+Tasks[#Tasks+1]=AIGroup:TaskFunction(self:GetClassName()..".Resume",self)
+RefuelRoute[#RefuelRoute].task=AIGroup:TaskCombo(Tasks)
+AIGroup:Route(RefuelRoute,0.5)
+else
+self:RTB()
+end
+end
+end
+function AI_A2A:onafterDead()
+self:SetStatusOff()
+end
+function AI_A2A:OnCrash(EventData)
+if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then
+self:E(self.Controllable:GetUnits())
+if#self.Controllable:GetUnits()==1 then
+self:__Crash(1,EventData)
+end
+end
+end
+function AI_A2A:OnEjection(EventData)
+if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then
+self:__Eject(1,EventData)
+end
+end
+function AI_A2A:OnPilotDead(EventData)
+if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then
+self:__PilotDead(1,EventData)
+end
+end
+AI_A2A_PATROL={
+ClassName="AI_A2A_PATROL",
+}
+function AI_A2A_PATROL:New(AIPatrol,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
+local self=BASE:Inherit(self,AI_A2A:New(AIPatrol))
+self.PatrolZone=PatrolZone
+self.PatrolFloorAltitude=PatrolFloorAltitude
+self.PatrolCeilingAltitude=PatrolCeilingAltitude
+self.PatrolMinSpeed=PatrolMinSpeed
+self.PatrolMaxSpeed=PatrolMaxSpeed
+self.PatrolAltType=PatrolAltType or"RADIO"
+self:AddTransition({"Started","Airborne","Refuelling"},"Patrol","Patrolling")
+self:AddTransition("Patrolling","Route","Patrolling")
+self:AddTransition("*","Reset","Patrolling")
+return self
+end
+function AI_A2A_PATROL:SetSpeed(PatrolMinSpeed,PatrolMaxSpeed)
+self:F2({PatrolMinSpeed,PatrolMaxSpeed})
+self.PatrolMinSpeed=PatrolMinSpeed
+self.PatrolMaxSpeed=PatrolMaxSpeed
+end
+function AI_A2A_PATROL:SetAltitude(PatrolFloorAltitude,PatrolCeilingAltitude)
+self:F2({PatrolFloorAltitude,PatrolCeilingAltitude})
+self.PatrolFloorAltitude=PatrolFloorAltitude
+self.PatrolCeilingAltitude=PatrolCeilingAltitude
+end
+function AI_A2A_PATROL:onafterPatrol(AIPatrol,From,Event,To)
+self:F2()
+self:ClearTargetDistance()
+self:__Route(1)
+self.AIPatrol:OnReSpawn(
+function(PatrolGroup)
+self:E("ReSpawn")
+self:__Reset(1)
+self:__Route(5)
+end
+)
+end
+function AI_A2A_PATROL.PatrolRoute(AIPatrol,Fsm)
+AIPatrol:F({"AI_A2A_PATROL.PatrolRoute:",AIPatrol:GetName()})
+if AIPatrol:IsAlive()then
+Fsm:Route()
+end
+end
+function AI_A2A_PATROL:onafterRoute(AIPatrol,From,Event,To)
+self:F2()
+if From=="RTB"then
+return
+end
+if AIPatrol:IsAlive()then
+local PatrolRoute={}
+local CurrentCoord=AIPatrol:GetCoordinate()
+local ToTargetCoord=self.PatrolZone:GetRandomPointVec2()
+ToTargetCoord:SetAlt(math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude))
+self:SetTargetDistance(ToTargetCoord)
+local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed)
+local ToPatrolRoutePoint=ToTargetCoord:WaypointAir(
+self.PatrolAltType,
+POINT_VEC3.RoutePointType.TurningPoint,
+POINT_VEC3.RoutePointAction.TurningPoint,
+ToTargetSpeed,
+true
+)
+PatrolRoute[#PatrolRoute+1]=ToPatrolRoutePoint
+PatrolRoute[#PatrolRoute+1]=ToPatrolRoutePoint
+local Tasks={}
+Tasks[#Tasks+1]=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute",self)
+PatrolRoute[#PatrolRoute].task=AIPatrol:TaskCombo(Tasks)
+AIPatrol:OptionROEReturnFire()
+AIPatrol:OptionROTEvadeFire()
+AIPatrol:Route(PatrolRoute,0.5)
+end
+end
+function AI_A2A_PATROL.Resume(AIPatrol)
+AIPatrol:F({"AI_A2A_PATROL.Resume:",AIPatrol:GetName()})
+if AIPatrol:IsAlive()then
+local _AI_A2A=AIPatrol:GetState(AIPatrol,"AI_A2A")
+_AI_A2A:__Reset(1)
+_AI_A2A:__Route(5)
+end
+end
+AI_A2A_CAP={
+ClassName="AI_A2A_CAP",
+}
+function AI_A2A_CAP:New(AICap,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,PatrolAltType)
+local self=BASE:Inherit(self,AI_A2A_PATROL:New(AICap,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType))
+self.Accomplished=false
+self.Engaging=false
+self.EngageMinSpeed=EngageMinSpeed
+self.EngageMaxSpeed=EngageMaxSpeed
+self:AddTransition({"Patrolling","Engaging","Returning","Airborne"},"Engage","Engaging")
+self:AddTransition("Engaging","Fired","Engaging")
+self:AddTransition("*","Destroy","*")
+self:AddTransition("Engaging","Abort","Patrolling")
+self:AddTransition("Engaging","Accomplish","Patrolling")
+return self
+end
+function AI_A2A_CAP:onafterStart(AICap,From,Event,To)
+AICap:HandleEvent(EVENTS.Takeoff,nil,self)
+end
+function AI_A2A_CAP:SetEngageZone(EngageZone)
+self:F2()
+if EngageZone then
+self.EngageZone=EngageZone
+else
+self.EngageZone=nil
+end
+end
+function AI_A2A_CAP:SetEngageRange(EngageRange)
+self:F2()
+if EngageRange then
+self.EngageRange=EngageRange
+else
+self.EngageRange=nil
+end
+end
+function AI_A2A_CAP:onafterPatrol(AICap,From,Event,To)
+self:GetParent(self).onafterPatrol(self,AICap,From,Event,To)
+self:HandleEvent(EVENTS.Dead)
+end
+function AI_A2A_CAP.AttackRoute(AICap,Fsm)
+AICap:F({"AI_A2A_CAP.AttackRoute:",AICap:GetName()})
+if AICap:IsAlive()then
+Fsm:__Engage(0.5)
+end
+end
+function AI_A2A_CAP:onbeforeEngage(AICap,From,Event,To)
+if self.Accomplished==true then
+return false
+end
+end
+function AI_A2A_CAP:onafterAbort(AICap,From,Event,To)
+AICap:ClearTasks()
+self:__Route(0.5)
+end
+function AI_A2A_CAP:onafterEngage(AICap,From,Event,To,AttackSetUnit)
+self:F({AICap,From,Event,To,AttackSetUnit})
+self.AttackSetUnit=AttackSetUnit or self.AttackSetUnit
+local FirstAttackUnit=self.AttackSetUnit:GetFirst()
+if FirstAttackUnit and FirstAttackUnit:IsAlive()then
+if AICap:IsAlive()then
+local EngageRoute={}
+local CurrentCoord=AICap:GetCoordinate()
+local ToTargetCoord=self.AttackSetUnit:GetFirst():GetCoordinate()
+local ToTargetSpeed=math.random(self.EngageMinSpeed,self.EngageMaxSpeed)
+local ToInterceptAngle=CurrentCoord:GetAngleDegrees(CurrentCoord:GetDirectionVec3(ToTargetCoord))
+local ToPatrolRoutePoint=CurrentCoord:Translate(5000,ToInterceptAngle):WaypointAir(
+self.PatrolAltType,
+POINT_VEC3.RoutePointType.TurningPoint,
+POINT_VEC3.RoutePointAction.TurningPoint,
+ToTargetSpeed,
+true
+)
+self:F({Angle=ToInterceptAngle,ToTargetSpeed=ToTargetSpeed})
+self:T2({self.MinSpeed,self.MaxSpeed,ToTargetSpeed})
+EngageRoute[#EngageRoute+1]=ToPatrolRoutePoint
+EngageRoute[#EngageRoute+1]=ToPatrolRoutePoint
+local AttackTasks={}
+for AttackUnitID,AttackUnit in pairs(self.AttackSetUnit:GetSet())do
+local AttackUnit=AttackUnit
+self:T({"Attacking Unit:",AttackUnit:GetName(),AttackUnit:IsAlive(),AttackUnit:IsAir()})
+if AttackUnit:IsAlive()and AttackUnit:IsAir()then
+AttackTasks[#AttackTasks+1]=AICap:TaskAttackUnit(AttackUnit)
+end
+end
+if#AttackTasks==0 then
+self:E("No targets found -> Going back to Patrolling")
+self:__Abort(0.5)
+else
+AICap:OptionROEOpenFire()
+AICap:OptionROTEvadeFire()
+AttackTasks[#AttackTasks+1]=AICap:TaskFunction("AI_A2A_CAP.AttackRoute",self)
+EngageRoute[#EngageRoute].task=AICap:TaskCombo(AttackTasks)
+end
+AICap:Route(EngageRoute,0.5)
+end
+else
+self:E("No targets found -> Going back to Patrolling")
+self:__Abort(0.5)
+end
+end
+function AI_A2A_CAP:onafterAccomplish(AICap,From,Event,To)
+self.Accomplished=true
+self:SetDetectionOff()
+end
+function AI_A2A_CAP:onafterDestroy(AICap,From,Event,To,EventData)
+if EventData.IniUnit then
+self.AttackUnits[EventData.IniUnit]=nil
+end
+end
+function AI_A2A_CAP:OnEventDead(EventData)
+self:F({"EventDead",EventData})
+if EventData.IniDCSUnit then
+if self.AttackUnits and self.AttackUnits[EventData.IniUnit]then
+self:__Destroy(1,EventData)
+end
+end
+end
+function AI_A2A_CAP.Resume(AICap)
+AICap:F({"AI_A2A_CAP.Resume:",AICap:GetName()})
+if AICap:IsAlive()then
+local _AI_A2A=AICap:GetState(AICap,"AI_A2A")
+_AI_A2A:__Reset(1)
+_AI_A2A:__Route(5)
+end
+end
+AI_A2A_GCI={
+ClassName="AI_A2A_GCI",
+}
+function AI_A2A_GCI:New(AIIntercept,EngageMinSpeed,EngageMaxSpeed)
+local self=BASE:Inherit(self,AI_A2A:New(AIIntercept))
+self.Accomplished=false
+self.Engaging=false
+self.EngageMinSpeed=EngageMinSpeed
+self.EngageMaxSpeed=EngageMaxSpeed
+self.PatrolMinSpeed=EngageMinSpeed
+self.PatrolMaxSpeed=EngageMaxSpeed
+self.PatrolAltType="RADIO"
+self:AddTransition({"Started","Engaging","Returning","Airborne"},"Engage","Engaging")
+self:AddTransition("Engaging","Fired","Engaging")
+self:AddTransition("*","Destroy","*")
+self:AddTransition("Engaging","Abort","Patrolling")
+self:AddTransition("Engaging","Accomplish","Patrolling")
+return self
+end
+function AI_A2A_GCI:onafterStart(AIIntercept,From,Event,To)
+AIIntercept:HandleEvent(EVENTS.Takeoff,nil,self)
+end
+function AI_A2A_GCI:onafterEngage(AIIntercept,From,Event,To)
+self:HandleEvent(EVENTS.Dead)
+end
+function AI_A2A_GCI.InterceptRoute(AIIntercept,Fsm)
+AIIntercept:F({"AI_A2A_GCI.InterceptRoute:",AIIntercept:GetName()})
+if AIIntercept:IsAlive()then
+Fsm:__Engage(0.5)
+end
+end
+function AI_A2A_GCI:onbeforeEngage(AIIntercept,From,Event,To)
+if self.Accomplished==true then
+return false
+end
+end
+function AI_A2A_GCI:onafterAbort(AIIntercept,From,Event,To)
+AIIntercept:ClearTasks()
+self:Return()
+self:__RTB(0.5)
+end
+function AI_A2A_GCI:onafterEngage(AIIntercept,From,Event,To,AttackSetUnit)
+self:F({AIIntercept,From,Event,To,AttackSetUnit})
+self.AttackSetUnit=AttackSetUnit or self.AttackSetUnit
+local FirstAttackUnit=self.AttackSetUnit:GetFirst()
+if FirstAttackUnit and FirstAttackUnit:IsAlive()then
+if AIIntercept:IsAlive()then
+local EngageRoute={}
+local CurrentCoord=AIIntercept:GetCoordinate()
+local CurrentCoord=AIIntercept:GetCoordinate()
+local ToTargetCoord=self.AttackSetUnit:GetFirst():GetCoordinate()
+self:SetTargetDistance(ToTargetCoord)
+local ToTargetSpeed=math.random(self.EngageMinSpeed,self.EngageMaxSpeed)
+local ToInterceptAngle=CurrentCoord:GetAngleDegrees(CurrentCoord:GetDirectionVec3(ToTargetCoord))
+local ToPatrolRoutePoint=CurrentCoord:Translate(15000,ToInterceptAngle):WaypointAir(
+self.PatrolAltType,
+POINT_VEC3.RoutePointType.TurningPoint,
+POINT_VEC3.RoutePointAction.TurningPoint,
+ToTargetSpeed,
+true
+)
+self:F({Angle=ToInterceptAngle,ToTargetSpeed=ToTargetSpeed})
+self:F({self.EngageMinSpeed,self.EngageMaxSpeed,ToTargetSpeed})
+EngageRoute[#EngageRoute+1]=ToPatrolRoutePoint
+EngageRoute[#EngageRoute+1]=ToPatrolRoutePoint
+local AttackTasks={}
+for AttackUnitID,AttackUnit in pairs(self.AttackSetUnit:GetSet())do
+local AttackUnit=AttackUnit
+if AttackUnit:IsAlive()and AttackUnit:IsAir()then
+self:T({"Intercepting Unit:",AttackUnit:GetName(),AttackUnit:IsAlive(),AttackUnit:IsAir()})
+AttackTasks[#AttackTasks+1]=AIIntercept:TaskAttackUnit(AttackUnit)
+end
+end
+if#AttackTasks==0 then
+self:E("No targets found -> Going RTB")
+self:Return()
+self:__RTB(0.5)
+else
+AIIntercept:OptionROEOpenFire()
+AIIntercept:OptionROTEvadeFire()
+AttackTasks[#AttackTasks+1]=AIIntercept:TaskFunction("AI_A2A_GCI.InterceptRoute",self)
+EngageRoute[#EngageRoute].task=AIIntercept:TaskCombo(AttackTasks)
+end
+AIIntercept:Route(EngageRoute,0.5)
+end
+else
+self:E("No targets found -> Going RTB")
+self:Return()
+self:__RTB(0.5)
+end
+end
+function AI_A2A_GCI:onafterAccomplish(AIIntercept,From,Event,To)
+self.Accomplished=true
+self:SetDetectionOff()
+end
+function AI_A2A_GCI:onafterDestroy(AIIntercept,From,Event,To,EventData)
+if EventData.IniUnit then
+self.AttackUnits[EventData.IniUnit]=nil
+end
+end
+function AI_A2A_GCI:OnEventDead(EventData)
+self:F({"EventDead",EventData})
+if EventData.IniDCSUnit then
+if self.AttackUnits and self.AttackUnits[EventData.IniUnit]then
+self:__Destroy(1,EventData)
+end
+end
+end
+do
+AI_A2A_DISPATCHER={
+ClassName="AI_A2A_DISPATCHER",
+Detection=nil,
+}
+AI_A2A_DISPATCHER.Takeoff=GROUP.Takeoff
+AI_A2A_DISPATCHER.Landing={
+NearAirbase=1,
+AtRunway=2,
+AtEngineShutdown=3,
+}
+function AI_A2A_DISPATCHER:New(Detection)
+local self=BASE:Inherit(self,DETECTION_MANAGER:New(nil,Detection))
+self.Detection=Detection
+self.DefenderSquadrons={}
+self.DefenderSpawns={}
+self.DefenderTasks={}
+self.DefenderDefault={}
+self.Detection:FilterCategories({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
+self.Detection:SetRefreshTimeInterval(30)
+self:SetEngageRadius()
+self:SetGciRadius()
+self:SetIntercept(300)
+self:SetDisengageRadius(300000)
+self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Air)
+self:SetDefaultTakeoffInAirAltitude(500)
+self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.NearAirbase)
+self:SetDefaultOverhead(1)
+self:SetDefaultGrouping(1)
+self:SetDefaultFuelThreshold(0.15,0)
+self:SetDefaultDamageThreshold(0.4)
+self:SetDefaultCapTimeInterval(180,600)
+self:SetDefaultCapLimit(1)
+self:AddTransition("Started","Assign","Started")
+self:AddTransition("*","CAP","*")
+self:AddTransition("*","GCI","*")
+self:AddTransition("*","ENGAGE","*")
+self:HandleEvent(EVENTS.Crash,self.OnEventCrashOrDead)
+self:HandleEvent(EVENTS.Dead,self.OnEventCrashOrDead)
+self:HandleEvent(EVENTS.Land)
+self:HandleEvent(EVENTS.EngineShutdown)
+self:SetTacticalDisplay(false)
+self:__Start(5)
+return self
+end
+function AI_A2A_DISPATCHER:OnEventCrashOrDead(EventData)
+self.Detection:ForgetDetectedUnit(EventData.IniUnitName)
+end
+function AI_A2A_DISPATCHER:OnEventLand(EventData)
+self:E("Landed")
+local DefenderUnit=EventData.IniUnit
+local Defender=EventData.IniGroup
+local Squadron=self:GetSquadronFromDefender(Defender)
+if Squadron then
+self:F({SquadronName=Squadron.Name})
+local LandingMethod=self:GetSquadronLanding(Squadron.Name)
+if LandingMethod==AI_A2A_DISPATCHER.Landing.AtRunway then
+local DefenderSize=Defender:GetSize()
+if DefenderSize==1 then
+self:RemoveDefenderFromSquadron(Squadron,Defender)
+end
+DefenderUnit:Destroy()
+return
+end
+if DefenderUnit:GetLife()~=DefenderUnit:GetLife0()then
+DefenderUnit:Destroy()
+return
+end
+if DefenderUnit:GetFuel()<=self.DefenderDefault.FuelThreshold then
+DefenderUnit:Destroy()
+return
+end
+end
+end
+function AI_A2A_DISPATCHER:OnEventEngineShutdown(EventData)
+local DefenderUnit=EventData.IniUnit
+local Defender=EventData.IniGroup
+local Squadron=self:GetSquadronFromDefender(Defender)
+if Squadron then
+self:F({SquadronName=Squadron.Name})
+local LandingMethod=self:GetSquadronLanding(Squadron.Name)
+if LandingMethod==AI_A2A_DISPATCHER.Landing.AtEngineShutdown then
+local DefenderSize=Defender:GetSize()
+if DefenderSize==1 then
+self:RemoveDefenderFromSquadron(Squadron,Defender)
+end
+DefenderUnit:Destroy()
+end
+end
+end
+function AI_A2A_DISPATCHER:SetEngageRadius(EngageRadius)
+self.Detection:SetFriendliesRange(EngageRadius or 100000)
+return self
+end
+function AI_A2A_DISPATCHER:SetDisengageRadius(DisengageRadius)
+self.DisengageRadius=DisengageRadius or 300000
+return self
+end
+function AI_A2A_DISPATCHER:SetGciRadius(GciRadius)
+self.GciRadius=GciRadius or 200000
+return self
+end
+function AI_A2A_DISPATCHER:SetBorderZone(BorderZone)
+self.Detection:SetAcceptZones(BorderZone)
+return self
+end
+function AI_A2A_DISPATCHER:SetTacticalDisplay(TacticalDisplay)
+self.TacticalDisplay=TacticalDisplay
+return self
+end
+function AI_A2A_DISPATCHER:SetDefaultDamageThreshold(DamageThreshold)
+self.DefenderDefault.DamageThreshold=DamageThreshold
+return self
+end
+function AI_A2A_DISPATCHER:SetDefaultCapTimeInterval(CapMinSeconds,CapMaxSeconds)
+self.DefenderDefault.CapMinSeconds=CapMinSeconds
+self.DefenderDefault.CapMaxSeconds=CapMaxSeconds
+return self
+end
+function AI_A2A_DISPATCHER:SetDefaultCapLimit(CapLimit)
+self.DefenderDefault.CapLimit=CapLimit
+return self
+end
+function AI_A2A_DISPATCHER:SetIntercept(InterceptDelay)
+self.DefenderDefault.InterceptDelay=InterceptDelay
+local Detection=self.Detection
+Detection:SetIntercept(true,InterceptDelay)
+return self
+end
+function AI_A2A_DISPATCHER:GetAIFriendliesNearBy(DetectedItem)
+local FriendliesNearBy=self.Detection:GetFriendliesDistance(DetectedItem)
+return FriendliesNearBy
+end
+function AI_A2A_DISPATCHER:GetDefenderTasks()
+return self.DefenderTasks or{}
+end
+function AI_A2A_DISPATCHER:GetDefenderTask(Defender)
+return self.DefenderTasks[Defender]
+end
+function AI_A2A_DISPATCHER:GetDefenderTaskFsm(Defender)
+return self:GetDefenderTask(Defender).Fsm
+end
+function AI_A2A_DISPATCHER:GetDefenderTaskTarget(Defender)
+return self:GetDefenderTask(Defender).Target
+end
+function AI_A2A_DISPATCHER:GetDefenderTaskSquadronName(Defender)
+return self:GetDefenderTask(Defender).SquadronName
+end
+function AI_A2A_DISPATCHER:ClearDefenderTask(Defender)
+if Defender:IsAlive()and self.DefenderTasks[Defender]then
+local Target=self.DefenderTasks[Defender].Target
+local Message="Clearing ("..self.DefenderTasks[Defender].Type..") "
+Message=Message..Defender:GetName()
+if Target then
+Message=Message..(Target and(" from "..Target.Index.." ["..Target.Set:Count().."]"))or""
+end
+self:F({Target=Message})
+end
+self.DefenderTasks[Defender]=nil
+return self
+end
+function AI_A2A_DISPATCHER:ClearDefenderTaskTarget(Defender)
+local DefenderTask=self:GetDefenderTask(Defender)
+if Defender:IsAlive()and DefenderTask then
+local Target=DefenderTask.Target
+local Message="Clearing ("..DefenderTask.Type..") "
+Message=Message..Defender:GetName()
+if Target then
+Message=Message..(Target and(" from "..Target.Index.." ["..Target.Set:Count().."]"))or""
+end
+self:F({Target=Message})
+end
+if Defender and DefenderTask and DefenderTask.Target then
+DefenderTask.Target=nil
+end
+return self
+end
+function AI_A2A_DISPATCHER:SetDefenderTask(SquadronName,Defender,Type,Fsm,Target)
+self:F({SquadronName=SquadronName,Defender=Defender:GetName()})
+self.DefenderTasks[Defender]=self.DefenderTasks[Defender]or{}
+self.DefenderTasks[Defender].Type=Type
+self.DefenderTasks[Defender].Fsm=Fsm
+self.DefenderTasks[Defender].SquadronName=SquadronName
+if Target then
+self:SetDefenderTaskTarget(Defender,Target)
+end
+return self
+end
+function AI_A2A_DISPATCHER:SetDefenderTaskTarget(Defender,AttackerDetection)
+local Message="("..self.DefenderTasks[Defender].Type..") "
+Message=Message..Defender:GetName()
+Message=Message..(AttackerDetection and(" target "..AttackerDetection.Index.." ["..AttackerDetection.Set:Count().."]"))or""
+self:F({AttackerDetection=Message})
+if AttackerDetection then
+self.DefenderTasks[Defender].Target=AttackerDetection
+end
+return self
+end
+function AI_A2A_DISPATCHER:SetSquadron(SquadronName,AirbaseName,TemplatePrefixes,Resources)
+self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
+local DefenderSquadron=self.DefenderSquadrons[SquadronName]
+DefenderSquadron.Name=SquadronName
+DefenderSquadron.Airbase=AIRBASE:FindByName(AirbaseName)
+if not DefenderSquadron.Airbase then
+error("Cannot find airbase with name:"..AirbaseName)
+end
+DefenderSquadron.Spawn={}
+if type(TemplatePrefixes)=="string"then
+local SpawnTemplate=TemplatePrefixes
+self.DefenderSpawns[SpawnTemplate]=self.DefenderSpawns[SpawnTemplate]or SPAWN:New(SpawnTemplate)
+DefenderSquadron.Spawn[1]=self.DefenderSpawns[SpawnTemplate]
+else
+for TemplateID,SpawnTemplate in pairs(TemplatePrefixes)do
+self.DefenderSpawns[SpawnTemplate]=self.DefenderSpawns[SpawnTemplate]or SPAWN:New(SpawnTemplate)
+DefenderSquadron.Spawn[#DefenderSquadron.Spawn+1]=self.DefenderSpawns[SpawnTemplate]
+end
+end
+DefenderSquadron.Resources=Resources
+DefenderSquadron.TemplatePrefixes=TemplatePrefixes
+self:E({Squadron={SquadronName,AirbaseName,TemplatePrefixes,Resources}})
+return self
+end
+function AI_A2A_DISPATCHER:GetSquadron(SquadronName)
+local DefenderSquadron=self.DefenderSquadrons[SquadronName]
+if not DefenderSquadron then
+error("Unknown Squadron:"..SquadronName)
+end
+return DefenderSquadron
+end
+function AI_A2A_DISPATCHER:SetSquadronCap(SquadronName,Zone,FloorAltitude,CeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType)
+self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
+self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{}
+local DefenderSquadron=self:GetSquadron(SquadronName)
+local Cap=self.DefenderSquadrons[SquadronName].Cap
+Cap.Name=SquadronName
+Cap.Zone=Zone
+Cap.FloorAltitude=FloorAltitude
+Cap.CeilingAltitude=CeilingAltitude
+Cap.PatrolMinSpeed=PatrolMinSpeed
+Cap.PatrolMaxSpeed=PatrolMaxSpeed
+Cap.EngageMinSpeed=EngageMinSpeed
+Cap.EngageMaxSpeed=EngageMaxSpeed
+Cap.AltType=AltType
+self:SetSquadronCapInterval(SquadronName,self.DefenderDefault.CapLimit,self.DefenderDefault.CapMinSeconds,self.DefenderDefault.CapMaxSeconds,1)
+self:E({CAP={SquadronName,Zone,FloorAltitude,CeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType}})
+local RecceSet=self.Detection:GetDetectionSetGroup()
+RecceSet:FilterPrefixes(DefenderSquadron.TemplatePrefixes)
+RecceSet:FilterStart()
+self.Detection:SetFriendlyPrefixes(DefenderSquadron.TemplatePrefixes)
+return self
+end
+function AI_A2A_DISPATCHER:SetSquadronCapInterval(SquadronName,CapLimit,LowInterval,HighInterval,Probability)
+self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
+self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{}
+local DefenderSquadron=self:GetSquadron(SquadronName)
+local Cap=self.DefenderSquadrons[SquadronName].Cap
+if Cap then
+Cap.LowInterval=LowInterval or 180
+Cap.HighInterval=HighInterval or 600
+Cap.Probability=Probability or 1
+Cap.CapLimit=CapLimit or 1
+Cap.Scheduler=Cap.Scheduler or SCHEDULER:New(self)
+local Scheduler=Cap.Scheduler
+local ScheduleID=Cap.ScheduleID
+local Variance=(Cap.HighInterval-Cap.LowInterval)/2
+local Repeat=Cap.LowInterval+Variance
+local Randomization=Variance/Repeat
+local Start=math.random(1,Cap.HighInterval)
+if ScheduleID then
+Scheduler:Stop(ScheduleID)
+end
+Cap.ScheduleID=Scheduler:Schedule(self,self.SchedulerCAP,{SquadronName},Start,Repeat,Randomization)
+else
+error("This squadron does not exist:"..SquadronName)
+end
+end
+function AI_A2A_DISPATCHER:GetCAPDelay(SquadronName)
+self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
+self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{}
+local DefenderSquadron=self:GetSquadron(SquadronName)
+local Cap=self.DefenderSquadrons[SquadronName].Cap
+if Cap then
+return math.random(Cap.LowInterval,Cap.HighInterval)
+else
+error("This squadron does not exist:"..SquadronName)
+end
+end
+function AI_A2A_DISPATCHER:CanCAP(SquadronName)
+self:F({SquadronName=SquadronName})
+self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
+self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{}
+local DefenderSquadron=self:GetSquadron(SquadronName)
+if(not DefenderSquadron.Resources)or(DefenderSquadron.Resources and DefenderSquadron.Resources>0)then
+local Cap=DefenderSquadron.Cap
+if Cap then
+local CapCount=self:CountCapAirborne(SquadronName)
+self:E({CapCount=CapCount})
+if CapCount0)then
+local Gci=DefenderSquadron.Gci
+if Gci then
+return DefenderSquadron
+end
+end
+return nil
+end
+function AI_A2A_DISPATCHER:SetSquadronGci(SquadronName,EngageMinSpeed,EngageMaxSpeed)
+self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
+self.DefenderSquadrons[SquadronName].Gci=self.DefenderSquadrons[SquadronName].Gci or{}
+local Intercept=self.DefenderSquadrons[SquadronName].Gci
+Intercept.Name=SquadronName
+Intercept.EngageMinSpeed=EngageMinSpeed
+Intercept.EngageMaxSpeed=EngageMaxSpeed
+self:E({GCI={SquadronName,EngageMinSpeed,EngageMaxSpeed}})
+end
+function AI_A2A_DISPATCHER:SetDefaultOverhead(Overhead)
+self.DefenderDefault.Overhead=Overhead
+return self
+end
+function AI_A2A_DISPATCHER:SetSquadronOverhead(SquadronName,Overhead)
+local DefenderSquadron=self:GetSquadron(SquadronName)
+DefenderSquadron.Overhead=Overhead
+return self
+end
+function AI_A2A_DISPATCHER:SetDefaultGrouping(Grouping)
+self.DefenderDefault.Grouping=Grouping
+return self
+end
+function AI_A2A_DISPATCHER:SetSquadronGrouping(SquadronName,Grouping)
+local DefenderSquadron=self:GetSquadron(SquadronName)
+DefenderSquadron.Grouping=Grouping
+return self
+end
+function AI_A2A_DISPATCHER:SetDefaultTakeoff(Takeoff)
+self.DefenderDefault.Takeoff=Takeoff
+return self
+end
+function AI_A2A_DISPATCHER:SetSquadronTakeoff(SquadronName,Takeoff)
+local DefenderSquadron=self:GetSquadron(SquadronName)
+DefenderSquadron.Takeoff=Takeoff
+return self
+end
+function AI_A2A_DISPATCHER:GetDefaultTakeoff()
+return self.DefenderDefault.Takeoff
+end
+function AI_A2A_DISPATCHER:GetSquadronTakeoff(SquadronName)
+local DefenderSquadron=self:GetSquadron(SquadronName)
+return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff
+end
+function AI_A2A_DISPATCHER:SetDefaultTakeoffInAir()
+self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Air)
+return self
+end
+function AI_A2A_DISPATCHER:SetSquadronTakeoffInAir(SquadronName,TakeoffAltitude)
+self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Air)
+if TakeoffAltitude then
+self:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude)
+end
+return self
+end
+function AI_A2A_DISPATCHER:SetDefaultTakeoffFromRunway()
+self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Runway)
+return self
+end
+function AI_A2A_DISPATCHER:SetSquadronTakeoffFromRunway(SquadronName)
+self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Runway)
+return self
+end
+function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingHot()
+self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Hot)
+return self
+end
+function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingHot(SquadronName)
+self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Hot)
+return self
+end
+function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingCold()
+self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Cold)
+return self
+end
+function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingCold(SquadronName)
+self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Cold)
+return self
+end
+function AI_A2A_DISPATCHER:SetDefaultTakeoffInAirAltitude(TakeoffAltitude)
+self.DefenderDefault.TakeoffAltitude=TakeoffAltitude
+return self
+end
+function AI_A2A_DISPATCHER:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude)
+local DefenderSquadron=self:GetSquadron(SquadronName)
+DefenderSquadron.TakeoffAltitude=TakeoffAltitude
+return self
+end
+function AI_A2A_DISPATCHER:SetDefaultLanding(Landing)
+self.DefenderDefault.Landing=Landing
+return self
+end
+function AI_A2A_DISPATCHER:SetSquadronLanding(SquadronName,Landing)
+local DefenderSquadron=self:GetSquadron(SquadronName)
+DefenderSquadron.Landing=Landing
+return self
+end
+function AI_A2A_DISPATCHER:GetDefaultLanding()
+return self.DefenderDefault.Landing
+end
+function AI_A2A_DISPATCHER:GetSquadronLanding(SquadronName)
+local DefenderSquadron=self:GetSquadron(SquadronName)
+return DefenderSquadron.Landing or self.DefenderDefault.Landing
+end
+function AI_A2A_DISPATCHER:SetDefaultLandingNearAirbase()
+self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.NearAirbase)
+return self
+end
+function AI_A2A_DISPATCHER:SetSquadronLandingNearAirbase(SquadronName)
+self:SetSquadronLanding(SquadronName,AI_A2A_DISPATCHER.Landing.NearAirbase)
+return self
+end
+function AI_A2A_DISPATCHER:SetDefaultLandingAtRunway()
+self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.AtRunway)
+return self
+end
+function AI_A2A_DISPATCHER:SetSquadronLandingAtRunway(SquadronName)
+self:SetSquadronLanding(SquadronName,AI_A2A_DISPATCHER.Landing.AtRunway)
+return self
+end
+function AI_A2A_DISPATCHER:SetDefaultLandingAtEngineShutdown()
+self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.AtEngineShutdown)
+return self
+end
+function AI_A2A_DISPATCHER:SetSquadronLandingAtEngineShutdown(SquadronName)
+self:SetSquadronLanding(SquadronName,AI_A2A_DISPATCHER.Landing.AtEngineShutdown)
+return self
+end
+function AI_A2A_DISPATCHER:SetDefaultFuelThreshold(FuelThreshold)
+self.DefenderDefault.FuelThreshold=FuelThreshold
+return self
+end
+function AI_A2A_DISPATCHER:SetSquadronFuelThreshold(SquadronName,FuelThreshold)
+local DefenderSquadron=self:GetSquadron(SquadronName)
+DefenderSquadron.FuelThreshold=FuelThreshold
+return self
+end
+function AI_A2A_DISPATCHER:SetDefaultTanker(TankerName)
+self.DefenderDefault.TankerName=TankerName
+return self
+end
+function AI_A2A_DISPATCHER:SetSquadronTanker(SquadronName,TankerName)
+local DefenderSquadron=self:GetSquadron(SquadronName)
+DefenderSquadron.TankerName=TankerName
+return self
+end
+function AI_A2A_DISPATCHER:AddDefenderToSquadron(Squadron,Defender,Size)
+self.Defenders=self.Defenders or{}
+local DefenderName=Defender:GetName()
+self.Defenders[DefenderName]=Squadron
+if Squadron.Resources then
+Squadron.Resources=Squadron.Resources-Size
+end
+self:E({DefenderName=DefenderName,SquadronResources=Squadron.Resources})
+end
+function AI_A2A_DISPATCHER:RemoveDefenderFromSquadron(Squadron,Defender)
+self.Defenders=self.Defenders or{}
+local DefenderName=Defender:GetName()
+if Squadron.Resources then
+Squadron.Resources=Squadron.Resources+Defender:GetSize()
+end
+self.Defenders[DefenderName]=nil
+self:F({DefenderName=DefenderName,SquadronResources=Squadron.Resources})
+end
+function AI_A2A_DISPATCHER:GetSquadronFromDefender(Defender)
+self.Defenders=self.Defenders or{}
+local DefenderName=Defender:GetName()
+self:F({DefenderName=DefenderName})
+return self.Defenders[DefenderName]
+end
+function AI_A2A_DISPATCHER:EvaluateSWEEP(DetectedItem)
+self:F({DetectedItem.ItemID})
+local DetectedSet=DetectedItem.Set
+local DetectedZone=DetectedItem.Zone
+if DetectedItem.IsDetected==false then
+local TargetSetUnit=SET_UNIT:New()
+TargetSetUnit:SetDatabase(DetectedSet)
+TargetSetUnit:FilterOnce()
+return TargetSetUnit
+end
+return nil
+end
+function AI_A2A_DISPATCHER:CountCapAirborne(SquadronName)
+local CapCount=0
+local DefenderSquadron=self.DefenderSquadrons[SquadronName]
+if DefenderSquadron then
+for AIGroup,DefenderTask in pairs(self:GetDefenderTasks())do
+if DefenderTask.SquadronName==SquadronName then
+if DefenderTask.Type=="CAP"then
+if AIGroup:IsAlive()then
+if DefenderTask.Fsm:Is("Patrolling")or DefenderTask.Fsm:Is("Engaging")or DefenderTask.Fsm:Is("Refuelling")then
+CapCount=CapCount+1
+end
+end
+end
+end
+end
+end
+return CapCount
+end
+function AI_A2A_DISPATCHER:CountDefendersEngaged(AttackerDetection)
+local DefenderCount=0
+self:E("Counting Defenders Engaged for Attacker:")
+local DetectedSet=AttackerDetection.Set
+DetectedSet:Flush()
+local DefenderTasks=self:GetDefenderTasks()
+for DefenderGroup,DefenderTask in pairs(DefenderTasks)do
+local Defender=DefenderGroup
+local DefenderTaskTarget=DefenderTask.Target
+local DefenderSquadronName=DefenderTask.SquadronName
+if DefenderTaskTarget and DefenderTaskTarget.Index==AttackerDetection.Index then
+local Squadron=self:GetSquadron(DefenderSquadronName)
+local SquadronOverhead=Squadron.Overhead or self.DefenderDefault.Overhead
+local DefenderSize=Defender:GetInitialSize()
+DefenderCount=DefenderCount+DefenderSize/SquadronOverhead
+self:F("Defender Group Name: "..Defender:GetName()..", Size: "..DefenderSize)
+end
+end
+self:F({DefenderCount=DefenderCount})
+return DefenderCount
+end
+function AI_A2A_DISPATCHER:CountDefendersToBeEngaged(AttackerDetection,DefenderCount)
+local Friendlies=nil
+local AttackerSet=AttackerDetection.Set
+local AttackerCount=AttackerSet:Count()
+local DefenderFriendlies=self:GetAIFriendliesNearBy(AttackerDetection)
+for FriendlyDistance,AIFriendly in UTILS.spairs(DefenderFriendlies or{})do
+if AttackerCount>DefenderCount then
+local Friendly=AIFriendly:GetGroup()
+if Friendly and Friendly:IsAlive()then
+local DefenderTask=self:GetDefenderTask(Friendly)
+if DefenderTask then
+if DefenderTask.Type=="CAP"or DefenderTask.Type=="GCI"then
+if DefenderTask.Target==nil then
+if DefenderTask.Fsm:Is("Returning")
+or DefenderTask.Fsm:Is("Patrolling")then
+Friendlies=Friendlies or{}
+Friendlies[Friendly]=Friendly
+DefenderCount=DefenderCount+Friendly:GetSize()
+self:F({Friendly=Friendly:GetName(),FriendlyDistance=FriendlyDistance})
+end
+end
+end
+end
+end
+else
+break
+end
+end
+return Friendlies
+end
+function AI_A2A_DISPATCHER:onafterCAP(From,Event,To,SquadronName)
+self:F({SquadronName=SquadronName})
+self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
+self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{}
+local DefenderSquadron=self:CanCAP(SquadronName)
+if DefenderSquadron then
+local Cap=DefenderSquadron.Cap
+if Cap then
+local Spawn=DefenderSquadron.Spawn[math.random(1,#DefenderSquadron.Spawn)]
+local DefenderGrouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping
+Spawn:InitGrouping(DefenderGrouping)
+local TakeoffMethod=self:GetSquadronTakeoff(SquadronName)
+local DefenderCAP=Spawn:SpawnAtAirbase(DefenderSquadron.Airbase,TakeoffMethod,DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude)
+self:AddDefenderToSquadron(DefenderSquadron,DefenderCAP,DefenderGrouping)
+if DefenderCAP then
+local Fsm=AI_A2A_CAP:New(DefenderCAP,Cap.Zone,Cap.FloorAltitude,Cap.CeilingAltitude,Cap.PatrolMinSpeed,Cap.PatrolMaxSpeed,Cap.EngageMinSpeed,Cap.EngageMaxSpeed,Cap.AltType)
+Fsm:SetDispatcher(self)
+Fsm:SetHomeAirbase(DefenderSquadron.Airbase)
+Fsm:SetFuelThreshold(DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold,60)
+Fsm:SetDamageThreshold(self.DefenderDefault.DamageThreshold)
+Fsm:SetDisengageRadius(self.DisengageRadius)
+Fsm:SetTanker(DefenderSquadron.TankerName or self.DefenderDefault.TankerName)
+Fsm:Start()
+self:SetDefenderTask(SquadronName,DefenderCAP,"CAP",Fsm)
+function Fsm:onafterTakeoff(Defender,From,Event,To)
+self:F({"GCI Birth",Defender:GetName()})
+local Dispatcher=Fsm:GetDispatcher()
+local Squadron=Dispatcher:GetSquadronFromDefender(Defender)
+if Squadron then
+Fsm:__Patrol(2)
+end
+end
+function Fsm:onafterRTB(Defender,From,Event,To)
+self:F({"CAP RTB",Defender:GetName()})
+self:GetParent(self).onafterRTB(self,Defender,From,Event,To)
+local Dispatcher=self:GetDispatcher()
+Dispatcher:ClearDefenderTaskTarget(Defender)
+end
+function Fsm:onafterHome(Defender,From,Event,To,Action)
+self:E({"CAP Home",Defender:GetName()})
+self:GetParent(self).onafterHome(self,Defender,From,Event,To)
+local Dispatcher=self:GetDispatcher()
+local Squadron=Dispatcher:GetSquadronFromDefender(Defender)
+if Action and Action=="Destroy"then
+Dispatcher:RemoveDefenderFromSquadron(Squadron,Defender)
+Defender:Destroy()
+end
+if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2A_DISPATCHER.Landing.NearAirbase then
+Dispatcher:RemoveDefenderFromSquadron(Squadron,Defender)
+Defender:Destroy()
+end
+end
+end
+end
+end
+end
+function AI_A2A_DISPATCHER:onafterENGAGE(From,Event,To,AttackerDetection,Defenders)
+if Defenders then
+for DefenderID,Defender in pairs(Defenders)do
+local Fsm=self:GetDefenderTaskFsm(Defender)
+Fsm:__Engage(1,AttackerDetection.Set)
+self:SetDefenderTaskTarget(Defender,AttackerDetection)
+end
+end
+end
+function AI_A2A_DISPATCHER:onafterGCI(From,Event,To,AttackerDetection,DefendersMissing,DefenderFriendlies)
+self:F({From,Event,To,AttackerDetection.Index,DefendersMissing,DefenderFriendlies})
+local AttackerSet=AttackerDetection.Set
+local AttackerUnit=AttackerSet:GetFirst()
+if AttackerUnit and AttackerUnit:IsAlive()then
+local AttackerCount=AttackerSet:Count()
+local DefenderCount=0
+for DefenderID,DefenderGroup in pairs(DefenderFriendlies or{})do
+local Fsm=self:GetDefenderTaskFsm(DefenderGroup)
+Fsm:__Engage(1,AttackerSet)
+self:SetDefenderTaskTarget(DefenderGroup,AttackerDetection)
+DefenderCount=DefenderCount+DefenderGroup:GetSize()
+end
+self:F({DefenderCount=DefenderCount,DefendersMissing=DefendersMissing})
+DefenderCount=DefendersMissing
+local ClosestDistance=0
+local ClosestDefenderSquadronName=nil
+local BreakLoop=false
+while(DefenderCount>0 and not BreakLoop)do
+self:F({DefenderSquadrons=self.DefenderSquadrons})
+for SquadronName,DefenderSquadron in pairs(self.DefenderSquadrons or{})do
+self:F({GCI=DefenderSquadron.Gci})
+for InterceptID,Intercept in pairs(DefenderSquadron.Gci or{})do
+self:F({DefenderSquadron})
+local SpawnCoord=DefenderSquadron.Airbase:GetCoordinate()
+local AttackerCoord=AttackerUnit:GetCoordinate()
+local InterceptCoord=AttackerDetection.InterceptCoord
+self:F({InterceptCoord=InterceptCoord})
+if InterceptCoord then
+local InterceptDistance=SpawnCoord:Get2DDistance(InterceptCoord)
+local AirbaseDistance=SpawnCoord:Get2DDistance(AttackerCoord)
+self:F({InterceptDistance=InterceptDistance,AirbaseDistance=AirbaseDistance,InterceptCoord=InterceptCoord})
+if ClosestDistance==0 or InterceptDistanceDefenderSquadron.Resources then
+DefendersNeeded=DefenderSquadron.Resources
+BreakLoop=true
+end
+while(DefendersNeeded>0)do
+local Spawn=DefenderSquadron.Spawn[math.random(1,#DefenderSquadron.Spawn)]
+local DefenderGrouping=(DefenderGrouping0 then
+for PlayerName,PlayerType in pairs(PlayerTypes)do
+PlayerTypesReport:Add(string.format('"%s" in %s',PlayerName,PlayerType))
+end
+else
+PlayerTypesReport:Add("-")
+end
+return PlayersCount,PlayerTypesReport
+end
+function AI_A2A_DISPATCHER:GetFriendliesNearBy(Target)
+local DetectedSet=Target.Set
+local FriendlyUnitsNearBy=self.Detection:GetFriendliesNearBy(Target)
+local FriendlyTypes={}
+local FriendliesCount=0
+if FriendlyUnitsNearBy then
+local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G()
+for FriendlyUnitName,FriendlyUnitData in pairs(FriendlyUnitsNearBy)do
+local FriendlyUnit=FriendlyUnitData
+if FriendlyUnit:IsAirPlane()then
+local FriendlyUnitThreatLevel=FriendlyUnit:GetThreatLevel()
+FriendliesCount=FriendliesCount+1
+local FriendlyType=FriendlyUnit:GetTypeName()
+FriendlyTypes[FriendlyType]=FriendlyTypes[FriendlyType]and(FriendlyTypes[FriendlyType]+1)or 1
+if DetectedTreatLevel0 then
+for FriendlyType,FriendlyTypeCount in pairs(FriendlyTypes)do
+FriendlyTypesReport:Add(string.format("%d of %s",FriendlyTypeCount,FriendlyType))
+end
+else
+FriendlyTypesReport:Add("-")
+end
+return FriendliesCount,FriendlyTypesReport
+end
+function AI_A2A_DISPATCHER:SchedulerCAP(SquadronName)
+self:CAP(SquadronName)
+end
+end
+do
+AI_A2A_GCICAP={
+ClassName="AI_A2A_GCICAP",
+Detection=nil,
+}
+function AI_A2A_GCICAP:New(EWRPrefixes,TemplatePrefixes,CapPrefixes,CapLimit,GroupingRadius,EngageRadius,GciRadius,Resources)
+local EWRSetGroup=SET_GROUP:New()
+EWRSetGroup:FilterPrefixes(EWRPrefixes)
+EWRSetGroup:FilterStart()
+local Detection=DETECTION_AREAS:New(EWRSetGroup,GroupingRadius or 30000)
+local self=BASE:Inherit(self,AI_A2A_DISPATCHER:New(Detection))
+self:SetEngageRadius(EngageRadius)
+self:SetGciRadius(GciRadius)
+local EWRFirst=EWRSetGroup:GetFirst()
+local EWRCoalition=EWRFirst:GetCoalition()
+local AirbaseNames={}
+for AirbaseID,AirbaseData in pairs(_DATABASE.AIRBASES)do
+local Airbase=AirbaseData
+local AirbaseName=Airbase:GetName()
+if Airbase:GetCoalition()==EWRCoalition then
+table.insert(AirbaseNames,AirbaseName)
+end
+end
+self.Templates=SET_GROUP
+:New()
+:FilterPrefixes(TemplatePrefixes)
+:FilterOnce()
+self:F({Airbases=AirbaseNames})
+self.Templates:Flush()
+for AirbaseID,AirbaseName in pairs(AirbaseNames)do
+local Airbase=_DATABASE:FindAirbase(AirbaseName)
+local AirbaseName=Airbase:GetName()
+local AirbaseCoord=Airbase:GetCoordinate()
+local AirbaseZone=ZONE_RADIUS:New("Airbase",AirbaseCoord:GetVec2(),3000)
+local Templates=nil
+for TemplateID,Template in pairs(self.Templates:GetSet())do
+local Template=Template
+self:F({Template=Template:GetName()})
+local TemplateCoord=Template:GetCoordinate()
+if AirbaseZone:IsVec2InZone(TemplateCoord:GetVec2())then
+Templates=Templates or{}
+table.insert(Templates,Template:GetName())
+end
+end
+if Templates then
+self:SetSquadron(AirbaseName,AirbaseName,Templates,Resources)
+end
+end
+self.CAPTemplates=SET_GROUP:New()
+self.CAPTemplates:FilterPrefixes(CapPrefixes)
+self.CAPTemplates:FilterOnce()
+for CAPID,CAPTemplate in pairs(self.CAPTemplates:GetSet())do
+local CAPZone=ZONE_POLYGON:New(CAPTemplate:GetName(),CAPTemplate)
+local AirbaseDistance=99999999
+local AirbaseClosest=nil
+for AirbaseID,AirbaseName in pairs(AirbaseNames)do
+local Airbase=_DATABASE:FindAirbase(AirbaseName)
+local AirbaseName=Airbase:GetName()
+local AirbaseCoord=Airbase:GetCoordinate()
+local Squadron=self.DefenderSquadrons[AirbaseName]
+if Squadron then
+local Distance=AirbaseCoord:Get2DDistance(CAPZone:GetCoordinate())
+if Distance Engaging')
+self:__Engage(1)
+end
+end
+end
+function AI_CAP_ZONE:onafterAbort(Controllable,From,Event,To)
+Controllable:ClearTasks()
+self:__Route(1)
+end
+function AI_CAP_ZONE:onafterEngage(Controllable,From,Event,To)
+if Controllable:IsAlive()then
+local EngageRoute={}
+local CurrentVec2=self.Controllable:GetVec2()
+local CurrentAltitude=self.Controllable:GetUnit(1):GetAltitude()
+local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
+local ToEngageZoneSpeed=self.PatrolMaxSpeed
+local CurrentRoutePoint=CurrentPointVec3:WaypointAir(
+self.PatrolAltType,
+POINT_VEC3.RoutePointType.TurningPoint,
+POINT_VEC3.RoutePointAction.TurningPoint,
+ToEngageZoneSpeed,
+true
+)
+EngageRoute[#EngageRoute+1]=CurrentRoutePoint
+local ToTargetVec2=self.PatrolZone:GetRandomVec2()
+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 ToPatrolRoutePoint=ToTargetPointVec3:WaypointAir(
+self.PatrolAltType,
+POINT_VEC3.RoutePointType.TurningPoint,
+POINT_VEC3.RoutePointAction.TurningPoint,
+ToTargetSpeed,
+true
+)
+EngageRoute[#EngageRoute+1]=ToPatrolRoutePoint
+Controllable:OptionROEOpenFire()
+Controllable:OptionROTEvadeFire()
+local AttackTasks={}
+for DetectedUnit,Detected in pairs(self.DetectedUnits)do
+local DetectedUnit=DetectedUnit
+self:T({DetectedUnit,DetectedUnit:IsAlive(),DetectedUnit:IsAir()})
+if DetectedUnit:IsAlive()and DetectedUnit:IsAir()then
+if self.EngageZone then
+if DetectedUnit:IsInZone(self.EngageZone)then
+self:F({"Within Zone and Engaging ",DetectedUnit})
+AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit)
+end
+else
+if self.EngageRange then
+if DetectedUnit:GetPointVec3():Get2DDistance(Controllable:GetPointVec3())<=self.EngageRange then
+self:F({"Within Range and Engaging",DetectedUnit})
+AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit)
+end
+else
+AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit)
+end
+end
+else
+self.DetectedUnits[DetectedUnit]=nil
+end
+end
+if#AttackTasks==0 then
+self:F("No targets found -> Going back to Patrolling")
+self:__Abort(1)
+self:__Route(1)
+self:SetDetectionActivated()
+else
+AttackTasks[#AttackTasks+1]=Controllable:TaskFunction("AI_CAP_ZONE.EngageRoute",self)
+EngageRoute[1].task=Controllable:TaskCombo(AttackTasks)
+self:SetDetectionDeactivated()
+end
+Controllable:Route(EngageRoute,0.5)
+end
+end
+function AI_CAP_ZONE:onafterAccomplish(Controllable,From,Event,To)
+self.Accomplished=true
+self:SetDetectionOff()
+end
+function AI_CAP_ZONE:onafterDestroy(Controllable,From,Event,To,EventData)
+if EventData.IniUnit then
+self.DetectedUnits[EventData.IniUnit]=nil
+end
+end
+function AI_CAP_ZONE:OnEventDead(EventData)
+self:F({"EventDead",EventData})
+if EventData.IniDCSUnit then
+if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit]then
+self:__Destroy(1,EventData)
+end
+end
+end
+AI_CAS_ZONE={
+ClassName="AI_CAS_ZONE",
+}
+function AI_CAS_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageZone,PatrolAltType)
+local self=BASE:Inherit(self,AI_PATROL_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType))
+self.EngageZone=EngageZone
+self.Accomplished=false
+self:SetDetectionZone(self.EngageZone)
+self:AddTransition({"Patrolling","Engaging"},"Engage","Engaging")
+self:AddTransition("Engaging","Target","Engaging")
+self:AddTransition("Engaging","Fired","Engaging")
+self:AddTransition("*","Destroy","*")
+self:AddTransition("Engaging","Abort","Patrolling")
+self:AddTransition("Engaging","Accomplish","Patrolling")
+return self
+end
+function AI_CAS_ZONE:SetEngageZone(EngageZone)
+self:F2()
+if EngageZone then
+self.EngageZone=EngageZone
+else
+self.EngageZone=nil
+end
+end
+function AI_CAS_ZONE:onafterStart(Controllable,From,Event,To)
+self:GetParent(self).onafterStart(self,Controllable,From,Event,To)
+self:HandleEvent(EVENTS.Dead)
+self:SetDetectionDeactivated()
+end
+function AI_CAS_ZONE.EngageRoute(EngageGroup,Fsm)
+EngageGroup:F({"AI_CAS_ZONE.EngageRoute:",EngageGroup:GetName()})
+if EngageGroup:IsAlive()then
+Fsm:__Engage(1,Fsm.EngageSpeed,Fsm.EngageAltitude,Fsm.EngageWeaponExpend,Fsm.EngageAttackQty,Fsm.EngageDirection)
+end
+end
+function AI_CAS_ZONE:onbeforeEngage(Controllable,From,Event,To)
+if self.Accomplished==true then
+return false
+end
+end
+function AI_CAS_ZONE:onafterTarget(Controllable,From,Event,To)
+self:E("onafterTarget")
+if Controllable:IsAlive()then
+local AttackTasks={}
+for DetectedUnit,Detected in pairs(self.DetectedUnits)do
+local DetectedUnit=DetectedUnit
+if DetectedUnit:IsAlive()then
+if DetectedUnit:IsInZone(self.EngageZone)then
+if Detected==true then
+self:E({"Target: ",DetectedUnit})
+self.DetectedUnits[DetectedUnit]=false
+local AttackTask=Controllable:TaskAttackUnit(DetectedUnit,false,self.EngageWeaponExpend,self.EngageAttackQty,self.EngageDirection,self.EngageAltitude,nil)
+self.Controllable:PushTask(AttackTask,1)
+end
+end
+else
+self.DetectedUnits[DetectedUnit]=nil
+end
+end
+self:__Target(-10)
+end
+end
+function AI_CAS_ZONE:onafterAbort(Controllable,From,Event,To)
+Controllable:ClearTasks()
+self:__Route(1)
+end
+function AI_CAS_ZONE:onafterEngage(Controllable,From,Event,To,
+EngageSpeed,
+EngageAltitude,
+EngageWeaponExpend,
+EngageAttackQty,
+EngageDirection)
+self:F("onafterEngage")
+self.EngageSpeed=EngageSpeed or 400
+self.EngageAltitude=EngageAltitude or 2000
+self.EngageWeaponExpend=EngageWeaponExpend
+self.EngageAttackQty=EngageAttackQty
+self.EngageDirection=EngageDirection
+if Controllable:IsAlive()then
+Controllable:OptionROEOpenFire()
+Controllable:OptionROTVertical()
+local EngageRoute={}
+local CurrentVec2=self.Controllable:GetVec2()
+local CurrentAltitude=self.Controllable:GetUnit(1):GetAltitude()
+local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
+local ToEngageZoneSpeed=self.PatrolMaxSpeed
+local CurrentRoutePoint=CurrentPointVec3:WaypointAir(
+self.PatrolAltType,
+POINT_VEC3.RoutePointType.TurningPoint,
+POINT_VEC3.RoutePointAction.TurningPoint,
+self.EngageSpeed,
+true
+)
+EngageRoute[#EngageRoute+1]=CurrentRoutePoint
+local AttackTasks={}
+for DetectedUnit,Detected in pairs(self.DetectedUnits)do
+local DetectedUnit=DetectedUnit
+self:T(DetectedUnit)
+if DetectedUnit:IsAlive()then
+if DetectedUnit:IsInZone(self.EngageZone)then
+self:E({"Engaging ",DetectedUnit})
+AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit,
+true,
+EngageWeaponExpend,
+EngageAttackQty,
+EngageDirection
+)
+end
+else
+self.DetectedUnits[DetectedUnit]=nil
+end
+end
+AttackTasks[#AttackTasks+1]=Controllable:TaskFunction("AI_CAS_ZONE.EngageRoute",self)
+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 ToTargetRoutePoint=ToTargetPointVec3:WaypointAir(
+self.PatrolAltType,
+POINT_VEC3.RoutePointType.TurningPoint,
+POINT_VEC3.RoutePointAction.TurningPoint,
+self.EngageSpeed,
+true
+)
+EngageRoute[#EngageRoute+1]=ToTargetRoutePoint
+Controllable:Route(EngageRoute,0.5)
+self:SetRefreshTimeInterval(2)
+self:SetDetectionActivated()
+self:__Target(-2)
+end
+end
+function AI_CAS_ZONE:onafterAccomplish(Controllable,From,Event,To)
+self.Accomplished=true
+self:SetDetectionDeactivated()
+end
+function AI_CAS_ZONE:onafterDestroy(Controllable,From,Event,To,EventData)
+if EventData.IniUnit then
+self.DetectedUnits[EventData.IniUnit]=nil
+end
+end
+function AI_CAS_ZONE:OnEventDead(EventData)
+self:F({"EventDead",EventData})
+if EventData.IniDCSUnit then
+if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit]then
+self:__Destroy(1,EventData)
+end
+end
+end
+AI_BAI_ZONE={
+ClassName="AI_BAI_ZONE",
+}
+function AI_BAI_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageZone,PatrolAltType)
+local self=BASE:Inherit(self,AI_PATROL_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType))
+self.EngageZone=EngageZone
+self.Accomplished=false
+self:SetDetectionZone(self.EngageZone)
+self:SearchOn()
+self:AddTransition({"Patrolling","Engaging"},"Engage","Engaging")
+self:AddTransition("Engaging","Target","Engaging")
+self:AddTransition("Engaging","Fired","Engaging")
+self:AddTransition("*","Destroy","*")
+self:AddTransition("Engaging","Abort","Patrolling")
+self:AddTransition("Engaging","Accomplish","Patrolling")
+return self
+end
+function AI_BAI_ZONE:SetEngageZone(EngageZone)
+self:F2()
+if EngageZone then
+self.EngageZone=EngageZone
+else
+self.EngageZone=nil
+end
+end
+function AI_BAI_ZONE:SearchOnOff(Search)
+self.Search=Search
+return self
+end
+function AI_BAI_ZONE:SearchOff()
+self:SearchOnOff(false)
+return self
+end
+function AI_BAI_ZONE:SearchOn()
+self:SearchOnOff(true)
+return self
+end
+function AI_BAI_ZONE:onafterStart(Controllable,From,Event,To)
+self:GetParent(self).onafterStart(self,Controllable,From,Event,To)
+self:HandleEvent(EVENTS.Dead)
+self:SetDetectionDeactivated()
+end
+function _NewEngageRoute(AIControllable)
+AIControllable:T("NewEngageRoute")
+local EngageZone=AIControllable:GetState(AIControllable,"EngageZone")
+EngageZone:__Engage(1,EngageZone.EngageSpeed,EngageZone.EngageAltitude,EngageZone.EngageWeaponExpend,EngageZone.EngageAttackQty,EngageZone.EngageDirection)
+end
+function AI_BAI_ZONE:onbeforeEngage(Controllable,From,Event,To)
+if self.Accomplished==true then
+return false
+end
+end
+function AI_BAI_ZONE:onafterTarget(Controllable,From,Event,To)
+self:F({"onafterTarget",self.Search,Controllable:IsAlive()})
+if Controllable:IsAlive()then
+local AttackTasks={}
+if self.Search==true then
+for DetectedUnit,Detected in pairs(self.DetectedUnits)do
+local DetectedUnit=DetectedUnit
+if DetectedUnit:IsAlive()then
+if DetectedUnit:IsInZone(self.EngageZone)then
+if Detected==true then
+self:F({"Target: ",DetectedUnit})
+self.DetectedUnits[DetectedUnit]=false
+local AttackTask=Controllable:TaskAttackUnit(DetectedUnit,false,self.EngageWeaponExpend,self.EngageAttackQty,self.EngageDirection,self.EngageAltitude,nil)
+self.Controllable:PushTask(AttackTask,1)
+end
+end
+else
+self.DetectedUnits[DetectedUnit]=nil
+end
+end
+else
+self:F("Attack zone")
+local AttackTask=Controllable:TaskAttackMapObject(
+self.EngageZone:GetPointVec2():GetVec2(),
+true,
+self.EngageWeaponExpend,
+self.EngageAttackQty,
+self.EngageDirection,
+self.EngageAltitude
+)
+self.Controllable:PushTask(AttackTask,1)
+end
+self:__Target(-10)
+end
+end
+function AI_BAI_ZONE:onafterAbort(Controllable,From,Event,To)
+Controllable:ClearTasks()
+self:__Route(1)
+end
+function AI_BAI_ZONE:onafterEngage(Controllable,From,Event,To,
+EngageSpeed,
+EngageAltitude,
+EngageWeaponExpend,
+EngageAttackQty,
+EngageDirection)
+self:F("onafterEngage")
+self.EngageSpeed=EngageSpeed or 400
+self.EngageAltitude=EngageAltitude or 2000
+self.EngageWeaponExpend=EngageWeaponExpend
+self.EngageAttackQty=EngageAttackQty
+self.EngageDirection=EngageDirection
+if Controllable:IsAlive()then
+local EngageRoute={}
+local CurrentVec2=self.Controllable:GetVec2()
+local CurrentAltitude=self.Controllable:GetUnit(1):GetAltitude()
+local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
+local ToEngageZoneSpeed=self.PatrolMaxSpeed
+local CurrentRoutePoint=CurrentPointVec3:WaypointAir(
+self.PatrolAltType,
+POINT_VEC3.RoutePointType.TurningPoint,
+POINT_VEC3.RoutePointAction.TurningPoint,
+self.EngageSpeed,
+true
+)
+EngageRoute[#EngageRoute+1]=CurrentRoutePoint
+local AttackTasks={}
+if self.Search==true then
+for DetectedUnitID,DetectedUnitData in pairs(self.DetectedUnits)do
+local DetectedUnit=DetectedUnitData
+self:T(DetectedUnit)
+if DetectedUnit:IsAlive()then
+if DetectedUnit:IsInZone(self.EngageZone)then
+self:F({"Engaging ",DetectedUnit})
+AttackTasks[#AttackTasks+1]=Controllable:TaskBombing(
+DetectedUnit:GetPointVec2():GetVec2(),
+true,
+EngageWeaponExpend,
+EngageAttackQty,
+EngageDirection,
+EngageAltitude
+)
+end
+else
+self.DetectedUnits[DetectedUnit]=nil
+end
+end
+else
+self:F("Attack zone")
+AttackTasks[#AttackTasks+1]=Controllable:TaskAttackMapObject(
+self.EngageZone:GetPointVec2():GetVec2(),
+true,
+EngageWeaponExpend,
+EngageAttackQty,
+EngageDirection,
+EngageAltitude
+)
+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 ToTargetRoutePoint=ToTargetPointVec3:WaypointAir(
+self.PatrolAltType,
+POINT_VEC3.RoutePointType.TurningPoint,
+POINT_VEC3.RoutePointAction.TurningPoint,
+self.EngageSpeed,
+true
+)
+EngageRoute[#EngageRoute+1]=ToTargetRoutePoint
+Controllable:OptionROEOpenFire()
+Controllable:OptionROTVertical()
+Controllable:WayPointInitialize(EngageRoute)
+Controllable:SetState(Controllable,"EngageZone",self)
+Controllable:WayPointFunction(#EngageRoute,1,"_NewEngageRoute")
+Controllable:WayPointExecute(1)
+self:SetRefreshTimeInterval(2)
+self:SetDetectionActivated()
+self:__Target(-2)
+end
+end
+function AI_BAI_ZONE:onafterAccomplish(Controllable,From,Event,To)
+self.Accomplished=true
+self:SetDetectionDeactivated()
+end
+function AI_BAI_ZONE:onafterDestroy(Controllable,From,Event,To,EventData)
+if EventData.IniUnit then
+self.DetectedUnits[EventData.IniUnit]=nil
+end
+end
+function AI_BAI_ZONE:OnEventDead(EventData)
+self:F({"EventDead",EventData})
+if EventData.IniDCSUnit then
+if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit]then
+self:__Destroy(1,EventData)
+end
+end
+end
+AI_FORMATION={
+ClassName="AI_FORMATION",
+FollowName=nil,
+FollowUnit=nil,
+FollowGroupSet=nil,
+FollowMode=1,
+MODE={
+FOLLOW=1,
+MISSION=2,
+},
+FollowScheduler=nil,
+OptionROE=AI.Option.Air.val.ROE.OPEN_FIRE,
+OptionReactionOnThreat=AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION,
+}
+function AI_FORMATION:New(FollowUnit,FollowGroupSet,FollowName,FollowBriefing)
+local self=BASE:Inherit(self,FSM_SET:New(FollowGroupSet))
+self:F({FollowUnit,FollowGroupSet,FollowName})
+self.FollowUnit=FollowUnit
+self.FollowGroupSet=FollowGroupSet
+self:SetFlightRandomization(2)
+self:SetStartState("None")
+self:AddTransition("*","Stop","Stopped")
+self:AddTransition("None","Start","Following")
+self:AddTransition("*","FormationLine","*")
+self:AddTransition("*","FormationTrail","*")
+self:AddTransition("*","FormationStack","*")
+self:AddTransition("*","FormationLeftLine","*")
+self:AddTransition("*","FormationRightLine","*")
+self:AddTransition("*","FormationLeftWing","*")
+self:AddTransition("*","FormationRightWing","*")
+self:AddTransition("*","FormationCenterWing","*")
+self:AddTransition("*","FormationVic","*")
+self:AddTransition("*","FormationBox","*")
+self:AddTransition("*","Follow","Following")
+self:FormationLeftLine(500,0,250,250)
+self.FollowName=FollowName
+self.FollowBriefing=FollowBriefing
+self.CT1=0
+self.GT1=0
+self.FollowMode=AI_FORMATION.MODE.MISSION
+return self
+end
+function AI_FORMATION:TestSmokeDirectionVector(SmokeDirection)
+self.SmokeDirectionVector=(SmokeDirection==true)and true or false
+return self
+end
+function AI_FORMATION:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace)
+self:F({FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace})
+FollowGroupSet:Flush()
+local FollowSet=FollowGroupSet:GetSet()
+local i=0
+for FollowID,FollowGroup in pairs(FollowSet)do
+local PointVec3=POINT_VEC3:New()
+PointVec3:SetX(XStart+i*XSpace)
+PointVec3:SetY(YStart+i*YSpace)
+PointVec3:SetZ(ZStart+i*ZSpace)
+local Vec3=PointVec3:GetVec3()
+FollowGroup:SetState(self,"FormationVec3",Vec3)
+i=i+1
+end
+return self
+end
+function AI_FORMATION:onafterFormationTrail(FollowGroupSet,From,Event,To,XStart,XSpace,YStart)
+self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,0,0)
+return self
+end
+function AI_FORMATION:onafterFormationStack(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace)
+self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,0,0)
+return self
+end
+function AI_FORMATION:onafterFormationLeftLine(FollowGroupSet,From,Event,To,XStart,YStart,ZStart,ZSpace)
+self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,ZStart,ZSpace)
+return self
+end
+function AI_FORMATION:onafterFormationRightLine(FollowGroupSet,From,Event,To,XStart,YStart,ZStart,ZSpace)
+self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,-ZStart,-ZSpace)
+return self
+end
+function AI_FORMATION:onafterFormationLeftWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,ZStart,ZSpace)
+self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,ZStart,ZSpace)
+return self
+end
+function AI_FORMATION:onafterFormationRightWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,ZStart,ZSpace)
+self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,-ZStart,-ZSpace)
+return self
+end
+function AI_FORMATION:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace)
+local FollowSet=FollowGroupSet:GetSet()
+local i=0
+for FollowID,FollowGroup in pairs(FollowSet)do
+local PointVec3=POINT_VEC3:New()
+local Side=(i%2==0)and 1 or-1
+local Row=i/2+1
+PointVec3:SetX(XStart+Row*XSpace)
+PointVec3:SetY(YStart)
+PointVec3:SetZ(Side*(ZStart+i*ZSpace))
+local Vec3=PointVec3:GetVec3()
+FollowGroup:SetState(self,"FormationVec3",Vec3)
+i=i+1
+end
+return self
+end
+function AI_FORMATION:onafterFormationVic(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace)
+self:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace)
+return self
+end
+function AI_FORMATION:onafterFormationBox(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels)
+local FollowSet=FollowGroupSet:GetSet()
+local i=0
+for FollowID,FollowGroup in pairs(FollowSet)do
+local PointVec3=POINT_VEC3:New()
+local ZIndex=i%ZLevels
+local XIndex=math.floor(i/ZLevels)
+local YIndex=math.floor(i/ZLevels)
+PointVec3:SetX(XStart+XIndex*XSpace)
+PointVec3:SetY(YStart+YIndex*YSpace)
+PointVec3:SetZ(-ZStart-(ZSpace*ZLevels/2)+ZSpace*ZIndex)
+local Vec3=PointVec3:GetVec3()
+FollowGroup:SetState(self,"FormationVec3",Vec3)
+i=i+1
+end
+return self
+end
+function AI_FORMATION:SetFlightRandomization(FlightRandomization)
+self.FlightRandomization=FlightRandomization
+return self
+end
+function AI_FORMATION:onenterFollowing(FollowGroupSet)
+self:F()
+self:T({self.FollowUnit.UnitName,self.FollowUnit:IsAlive()})
+if self.FollowUnit:IsAlive()then
+local ClientUnit=self.FollowUnit
+self:T({ClientUnit.UnitName})
+local CT1,CT2,CV1,CV2
+CT1=ClientUnit:GetState(self,"CT1")
+if CT1==nil or CT1==0 then
+ClientUnit:SetState(self,"CV1",ClientUnit:GetPointVec3())
+ClientUnit:SetState(self,"CT1",timer.getTime())
+else
+CT1=ClientUnit:GetState(self,"CT1")
+CT2=timer.getTime()
+CV1=ClientUnit:GetState(self,"CV1")
+CV2=ClientUnit:GetPointVec3()
+ClientUnit:SetState(self,"CT1",CT2)
+ClientUnit:SetState(self,"CV1",CV2)
+end
+FollowGroupSet:ForEachGroup(
+function(FollowGroup,Formation,ClientUnit,CT1,CV1,CT2,CV2)
+FollowGroup:OptionROTEvadeFire()
+FollowGroup:OptionROEReturnFire()
+local GroupUnit=FollowGroup:GetUnit(1)
+local FollowFormation=FollowGroup:GetState(self,"FormationVec3")
+if FollowFormation then
+local FollowDistance=FollowFormation.x
+local GT1=GroupUnit:GetState(self,"GT1")
+if CT1==nil or CT1==0 or GT1==nil or GT1==0 then
+GroupUnit:SetState(self,"GV1",GroupUnit:GetPointVec3())
+GroupUnit:SetState(self,"GT1",timer.getTime())
+else
+local CD=((CV2.x-CV1.x)^2+(CV2.y-CV1.y)^2+(CV2.z-CV1.z)^2)^0.5
+local CT=CT2-CT1
+local CS=(3600/CT)*(CD/1000)/3.6
+local CDv={x=CV2.x-CV1.x,y=CV2.y-CV1.y,z=CV2.z-CV1.z}
+local Ca=math.atan2(CDv.x,CDv.z)
+local GT1=GroupUnit:GetState(self,"GT1")
+local GT2=timer.getTime()
+local GV1=GroupUnit:GetState(self,"GV1")
+local GV2=GroupUnit:GetPointVec3()
+GV2:AddX(math.random(-Formation.FlightRandomization/2,Formation.FlightRandomization/2))
+GV2:AddY(math.random(-Formation.FlightRandomization/2,Formation.FlightRandomization/2))
+GV2:AddZ(math.random(-Formation.FlightRandomization/2,Formation.FlightRandomization/2))
+GroupUnit:SetState(self,"GT1",GT2)
+GroupUnit:SetState(self,"GV1",GV2)
+local GD=((GV2.x-GV1.x)^2+(GV2.y-GV1.y)^2+(GV2.z-GV1.z)^2)^0.5
+local GT=GT2-GT1
+local GDv={x=GV2.x-CV1.x,y=GV2.y-CV1.y,z=GV2.z-CV1.z}
+local Alpha_T=math.atan2(GDv.x,GDv.z)-math.atan2(CDv.x,CDv.z)
+local Alpha_R=(Alpha_T<0)and Alpha_T+2*math.pi or Alpha_T
+local Position=math.cos(Alpha_R)
+local GD=((GDv.x)^2+(GDv.z)^2)^0.5
+local Distance=GD*Position+-CS*0,5
+local GV={x=GV2.x-CV2.x,y=GV2.y-CV2.y,z=GV2.z-CV2.z}
+local GH2={x=GV2.x,y=CV2.y+FollowFormation.y,z=GV2.z}
+local alpha=math.atan2(GV.x,GV.z)
+local GVx=FollowFormation.z*math.cos(Ca)+FollowFormation.x*math.sin(Ca)
+local GVz=FollowFormation.x*math.cos(Ca)-FollowFormation.z*math.sin(Ca)
+local CVI={x=CV2.x+CS*10*math.sin(Ca),
+y=GH2.y-(Distance+FollowFormation.x)/5,
+z=CV2.z+CS*10*math.cos(Ca),
+}
+local DV={x=CV2.x-CVI.x,y=CV2.y-CVI.y,z=CV2.z-CVI.z}
+local DVu={x=DV.x/FollowDistance,y=DV.y,z=DV.z/FollowDistance}
+local GDV={x=CVI.x,y=CVI.y,z=CVI.z}
+local ADDx=FollowFormation.x*math.cos(alpha)-FollowFormation.z*math.sin(alpha)
+local ADDz=FollowFormation.z*math.cos(alpha)+FollowFormation.x*math.sin(alpha)
+local GDV_Formation={
+x=GDV.x-GVx,
+y=GDV.y,
+z=GDV.z-GVz
+}
+if self.SmokeDirectionVector==true then
+trigger.action.smoke(GDV,trigger.smokeColor.Green)
+trigger.action.smoke(GDV_Formation,trigger.smokeColor.White)
+end
+local Time=60
+local Speed=-(Distance+FollowFormation.x)/Time
+local GS=Speed+CS
+if Speed<0 then
+Speed=0
+end
+FollowGroup:RouteToVec3(GDV_Formation,GS)
+end
+end
+end,
+self,ClientUnit,CT1,CV1,CT2,CV2
+)
+self:__Follow(-0.5)
+end
+end
+do
+ACT_ASSIGN={
+ClassName="ACT_ASSIGN",
+}
+function ACT_ASSIGN:New()
+local self=BASE:Inherit(self,FSM_PROCESS:New("ACT_ASSIGN"))
+self:AddTransition("UnAssigned","Start","Waiting")
+self:AddTransition("Waiting","Assign","Assigned")
+self:AddTransition("Waiting","Reject","Rejected")
+self:AddTransition("*","Fail","Failed")
+self:AddEndState("Assigned")
+self:AddEndState("Rejected")
+self:AddEndState("Failed")
+self:SetStartState("UnAssigned")
+return self
+end
+end
+do
+ACT_ASSIGN_ACCEPT={
+ClassName="ACT_ASSIGN_ACCEPT",
+}
+function ACT_ASSIGN_ACCEPT:New(TaskBriefing)
+local self=BASE:Inherit(self,ACT_ASSIGN:New())
+self.TaskBriefing=TaskBriefing
+return self
+end
+function ACT_ASSIGN_ACCEPT:Init(FsmAssign)
+self.TaskBriefing=FsmAssign.TaskBriefing
+end
+function ACT_ASSIGN_ACCEPT:onafterStart(ProcessUnit,From,Event,To)
+self:E({ProcessUnit,From,Event,To})
+self:__Assign(1)
+end
+function ACT_ASSIGN_ACCEPT:onenterAssigned(ProcessUnit,From,Event,To)
+env.info("in here")
+self:E({ProcessUnit,From,Event,To})
+local ProcessGroup=ProcessUnit:GetGroup()
+self.Task:Assign(ProcessUnit,ProcessUnit:GetPlayerName())
+end
+end
+do
+ACT_ASSIGN_MENU_ACCEPT={
+ClassName="ACT_ASSIGN_MENU_ACCEPT",
+}
+function ACT_ASSIGN_MENU_ACCEPT:New(TaskName,TaskBriefing)
+local self=BASE:Inherit(self,ACT_ASSIGN:New())
+self.TaskName=TaskName
+self.TaskBriefing=TaskBriefing
+return self
+end
+function ACT_ASSIGN_MENU_ACCEPT:Init(FsmAssign)
+self.TaskName=FsmAssign.TaskName
+self.TaskBriefing=FsmAssign.TaskBriefing
+end
+function ACT_ASSIGN_MENU_ACCEPT:Init(TaskName,TaskBriefing)
+self.TaskBriefing=TaskBriefing
+self.TaskName=TaskName
+return self
+end
+function ACT_ASSIGN_MENU_ACCEPT:onafterStart(ProcessUnit,From,Event,To)
+self:E({ProcessUnit,From,Event,To})
+self:GetCommandCenter():MessageTypeToGroup("Access the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled.",ProcessUnit:GetGroup(),MESSAGE.Type.Information)
+local ProcessGroup=ProcessUnit:GetGroup()
+self.Menu=MENU_GROUP:New(ProcessGroup,"Task "..self.TaskName.." acceptance")
+self.MenuAcceptTask=MENU_GROUP_COMMAND:New(ProcessGroup,"Accept task "..self.TaskName,self.Menu,self.MenuAssign,self)
+self.MenuRejectTask=MENU_GROUP_COMMAND:New(ProcessGroup,"Reject task "..self.TaskName,self.Menu,self.MenuReject,self)
+end
+function ACT_ASSIGN_MENU_ACCEPT:MenuAssign()
+self:E()
+self:__Assign(1)
+end
+function ACT_ASSIGN_MENU_ACCEPT:MenuReject()
+self:E()
+self:__Reject(1)
+end
+function ACT_ASSIGN_MENU_ACCEPT:onafterAssign(ProcessUnit,From,Event,To)
+self:E({ProcessUnit.UnitNameFrom,Event,To})
+self.Menu:Remove()
+end
+function ACT_ASSIGN_MENU_ACCEPT:onafterReject(ProcessUnit,From,Event,To)
+self:E({ProcessUnit.UnitName,From,Event,To})
+self.Menu:Remove()
+ProcessUnit:Destroy()
+end
+end
+do
+ACT_ROUTE={
+ClassName="ACT_ROUTE",
+}
+function ACT_ROUTE:New()
+local self=BASE:Inherit(self,FSM_PROCESS:New("ACT_ROUTE"))
+self:AddTransition("*","Reset","None")
+self:AddTransition("None","Start","Routing")
+self:AddTransition("*","Report","*")
+self:AddTransition("Routing","Route","Routing")
+self:AddTransition("Routing","Pause","Pausing")
+self:AddTransition("Routing","Arrive","Arrived")
+self:AddTransition("*","Cancel","Cancelled")
+self:AddTransition("Arrived","Success","Success")
+self:AddTransition("*","Fail","Failed")
+self:AddTransition("","","")
+self:AddTransition("","","")
+self:AddEndState("Arrived")
+self:AddEndState("Failed")
+self:AddEndState("Cancelled")
+self:SetStartState("None")
+self:SetRouteMode("C")
+return self
+end
+function ACT_ROUTE:SetMenuCancel(MenuGroup,MenuText,ParentMenu,MenuTime)
+MENU_GROUP_COMMAND:New(
+MenuGroup,
+MenuText,
+ParentMenu,
+self.MenuCancel,
+self
+):SetTime(MenuTime)
+return self
+end
+function ACT_ROUTE:SetRouteMode(RouteMode)
+self.RouteMode=RouteMode
+return self
+end
+function ACT_ROUTE:GetRouteText(Controllable)
+self:E()
+local RouteText=""
+local Coordinate=nil
+if self.Coordinate then
+Coordinate=self.Coordinate
+end
+if self.Zone then
+Coordinate=self.Zone:GetPointVec3(self.Altitude)
+Coordinate:SetHeading(self.Heading)
+end
+local Task=self:GetTask()
+local CC=self:GetTask():GetMission():GetCommandCenter()
+if CC then
+if CC:IsModeWWII()then
+local ShortestDistance=0
+local ShortestReferencePoint=nil
+local ShortestReferenceName=""
+self:E({CC.ReferencePoints})
+for ZoneName,Zone in pairs(CC.ReferencePoints)do
+self:E({ZoneName=ZoneName})
+local Zone=Zone
+local ZoneCoord=Zone:GetCoordinate()
+local ZoneDistance=ZoneCoord:Get2DDistance(self.Coordinate)
+self:E({ShortestDistance,ShortestReferenceName})
+if ShortestDistance==0 or ZoneDistance=self.DisplayInterval then
+self:T({HasArrived=HasArrived})
+if not HasArrived then
+self:Report()
+end
+self.DisplayCount=1
+else
+self.DisplayCount=self.DisplayCount+1
+end
+self:T({DisplayCount=self.DisplayCount})
+if HasArrived then
+self:__Arrive(1)
+else
+self:__Route(1)
+end
+return HasArrived
+end
+return false
+end
+end
+do
+ACT_ROUTE_POINT={
+ClassName="ACT_ROUTE_POINT",
+}
+function ACT_ROUTE_POINT:New(Coordinate,Range)
+local self=BASE:Inherit(self,ACT_ROUTE:New())
+self.Coordinate=Coordinate
+self.Range=Range or 0
+self.DisplayInterval=30
+self.DisplayCount=30
+self.DisplayMessage=true
+self.DisplayTime=10
+return self
+end
+function ACT_ROUTE_POINT:Init(FsmRoute)
+self.Coordinate=FsmRoute.Coordinate
+self.Range=FsmRoute.Range or 0
+self.DisplayInterval=30
+self.DisplayCount=30
+self.DisplayMessage=true
+self.DisplayTime=10
+self:SetStartState("None")
+end
+function ACT_ROUTE_POINT:SetCoordinate(Coordinate)
+self:F2({Coordinate})
+self.Coordinate=Coordinate
+end
+function ACT_ROUTE_POINT:GetCoordinate()
+self:F2({self.Coordinate})
+return self.Coordinate
+end
+function ACT_ROUTE_POINT:SetRange(Range)
+self:F2({self.Range})
+self.Range=Range or 10000
+end
+function ACT_ROUTE_POINT:GetRange()
+return self.Range
+end
+function ACT_ROUTE_POINT:onfuncHasArrived(ProcessUnit)
+if ProcessUnit:IsAlive()then
+local Distance=self.Coordinate:Get2DDistance(ProcessUnit:GetCoordinate())
+if Distance<=self.Range then
+local RouteText="You have arrived."
+self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Information)
+return true
+end
+end
+return false
+end
+function ACT_ROUTE_POINT:onafterReport(ProcessUnit,From,Event,To)
+local RouteText=self:GetRouteText(ProcessUnit)
+self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Update)
+end
+end
+do
+ACT_ROUTE_ZONE={
+ClassName="ACT_ROUTE_ZONE",
+}
+function ACT_ROUTE_ZONE:New(Zone)
+local self=BASE:Inherit(self,ACT_ROUTE:New())
+self.Zone=Zone
+self.DisplayInterval=30
+self.DisplayCount=30
+self.DisplayMessage=true
+self.DisplayTime=10
+return self
+end
+function ACT_ROUTE_ZONE:Init(FsmRoute)
+self.Zone=FsmRoute.Zone
+self.DisplayInterval=30
+self.DisplayCount=30
+self.DisplayMessage=true
+self.DisplayTime=10
+end
+function ACT_ROUTE_ZONE:SetZone(Zone,Altitude,Heading)
+self.Zone=Zone
+self.Altitude=Altitude
+self.Heading=Heading
+end
+function ACT_ROUTE_ZONE:GetZone()
+return self.Zone
+end
+function ACT_ROUTE_ZONE:onfuncHasArrived(ProcessUnit)
+if ProcessUnit:IsInZone(self.Zone)then
+local RouteText="You have arrived within the zone."
+self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Information)
+end
+return ProcessUnit:IsInZone(self.Zone)
+end
+function ACT_ROUTE_ZONE:onafterReport(ProcessUnit,From,Event,To)
+self:E({ProcessUnit=ProcessUnit})
+local RouteText=self:GetRouteText(ProcessUnit)
+self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Update)
+end
+end
+do
+ACT_ACCOUNT={
+ClassName="ACT_ACCOUNT",
+TargetSetUnit=nil,
+}
+function ACT_ACCOUNT:New()
+local self=BASE:Inherit(self,FSM_PROCESS:New())
+self:AddTransition("Assigned","Start","Waiting")
+self:AddTransition("*","Wait","Waiting")
+self:AddTransition("*","Report","Report")
+self:AddTransition("*","Event","Account")
+self:AddTransition("Account","Player","AccountForPlayer")
+self:AddTransition("Account","Other","AccountForOther")
+self:AddTransition({"Account","AccountForPlayer","AccountForOther"},"More","Wait")
+self:AddTransition({"Account","AccountForPlayer","AccountForOther"},"NoMore","Accounted")
+self:AddTransition("*","Fail","Failed")
+self:AddEndState("Failed")
+self:SetStartState("Assigned")
+return self
+end
+function ACT_ACCOUNT:onafterStart(ProcessUnit,From,Event,To)
+self:HandleEvent(EVENTS.Dead,self.onfuncEventDead)
+self:HandleEvent(EVENTS.Crash,self.onfuncEventCrash)
+self:HandleEvent(EVENTS.Hit)
+self:__Wait(1)
+end
+function ACT_ACCOUNT:onenterWaiting(ProcessUnit,From,Event,To)
+if self.DisplayCount>=self.DisplayInterval then
+self:Report()
+self.DisplayCount=1
+else
+self.DisplayCount=self.DisplayCount+1
+end
+return true
+end
+function ACT_ACCOUNT:onafterEvent(ProcessUnit,From,Event,To,Event)
+self:__NoMore(1)
+end
+end
+do
+ACT_ACCOUNT_DEADS={
+ClassName="ACT_ACCOUNT_DEADS",
+}
+function ACT_ACCOUNT_DEADS:New()
+local self=BASE:Inherit(self,ACT_ACCOUNT:New())
+self.DisplayInterval=30
+self.DisplayCount=30
+self.DisplayMessage=true
+self.DisplayTime=10
+self.DisplayCategory="HQ"
+return self
+end
+function ACT_ACCOUNT_DEADS:Init(FsmAccount)
+self.Task=self:GetTask()
+self.TaskName=self.Task:GetName()
+end
+function ACT_ACCOUNT_DEADS:onenterReport(ProcessUnit,Task,From,Event,To)
+self:E({ProcessUnit,From,Event,To})
+local MessageText="Your group with assigned "..self.TaskName.." task has "..Task.TargetSetUnit:GetUnitTypesText().." targets left to be destroyed."
+self:GetCommandCenter():MessageTypeToGroup(MessageText,ProcessUnit:GetGroup(),MESSAGE.Type.Information)
+end
+function ACT_ACCOUNT_DEADS:onafterEvent(ProcessUnit,Task,From,Event,To,EventData)
+self:T({ProcessUnit:GetName(),Task:GetName(),From,Event,To,EventData})
+if Task.TargetSetUnit:FindUnit(EventData.IniUnitName)then
+local PlayerName=ProcessUnit:GetPlayerName()
+local PlayerHit=self.PlayerHits and self.PlayerHits[EventData.IniUnitName]
+if PlayerHit==PlayerName then
+self:Player(EventData)
+else
+self:Other(EventData)
+end
+end
+end
+function ACT_ACCOUNT_DEADS:onenterAccountForPlayer(ProcessUnit,Task,From,Event,To,EventData)
+self:T({ProcessUnit:GetName(),Task:GetName(),From,Event,To,EventData})
+local TaskGroup=ProcessUnit:GetGroup()
+Task.TargetSetUnit:Remove(EventData.IniUnitName)
+local MessageText="You have destroyed a target.\nYour group assigned with task "..self.TaskName.." has\n"..Task.TargetSetUnit:Count().." targets ( "..Task.TargetSetUnit:GetUnitTypesText().." ) left to be destroyed."
+self:GetCommandCenter():MessageTypeToGroup(MessageText,ProcessUnit:GetGroup(),MESSAGE.Type.Information)
+local PlayerName=ProcessUnit:GetPlayerName()
+Task:AddProgress(PlayerName,"Destroyed "..EventData.IniTypeName,timer.getTime(),1)
+if Task.TargetSetUnit:Count()>0 then
+self:__More(1)
+else
+self:__NoMore(1)
+end
+end
+function ACT_ACCOUNT_DEADS:onenterAccountForOther(ProcessUnit,Task,From,Event,To,EventData)
+self:T({ProcessUnit:GetName(),Task:GetName(),From,Event,To,EventData})
+local TaskGroup=ProcessUnit:GetGroup()
+Task.TargetSetUnit:Remove(EventData.IniUnitName)
+local MessageText="One of the task targets has been destroyed.\nYour group assigned with task "..self.TaskName.." has\n"..Task.TargetSetUnit:Count().." targets ( "..Task.TargetSetUnit:GetUnitTypesText().." ) left to be destroyed."
+self:GetCommandCenter():MessageTypeToGroup(MessageText,ProcessUnit:GetGroup(),MESSAGE.Type.Information)
+if Task.TargetSetUnit:Count()>0 then
+self:__More(1)
+else
+self:__NoMore(1)
+end
+end
+function ACT_ACCOUNT_DEADS:OnEventHit(EventData)
+self:T({"EventDead",EventData})
+if EventData.IniPlayerName and EventData.TgtDCSUnitName then
+self.PlayerHits=self.PlayerHits or{}
+self.PlayerHits[EventData.TgtDCSUnitName]=EventData.IniPlayerName
+end
+end
+end
+do
+ACT_ASSIST={
+ClassName="ACT_ASSIST",
+}
+function ACT_ASSIST:New()
+local self=BASE:Inherit(self,FSM_PROCESS:New("ACT_ASSIST"))
+self:AddTransition("None","Start","AwaitSmoke")
+self:AddTransition("AwaitSmoke","Next","Smoking")
+self:AddTransition("Smoking","Next","AwaitSmoke")
+self:AddTransition("*","Stop","Success")
+self:AddTransition("*","Fail","Failed")
+self:AddEndState("Failed")
+self:AddEndState("Success")
+self:SetStartState("None")
+return self
+end
+function ACT_ASSIST:onafterStart(ProcessUnit,From,Event,To)
+local ProcessGroup=ProcessUnit:GetGroup()
+local MissionMenu=self:GetMission():GetMenu(ProcessGroup)
+local function MenuSmoke(MenuParam)
+self:E(MenuParam)
+local self=MenuParam.self
+local SmokeColor=MenuParam.SmokeColor
+self.SmokeColor=SmokeColor
+self:__Next(1)
+end
+self.Menu=MENU_GROUP:New(ProcessGroup,"Target acquisition",MissionMenu)
+self.MenuSmokeBlue=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop blue smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Blue})
+self.MenuSmokeGreen=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop green smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Green})
+self.MenuSmokeOrange=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop Orange smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Orange})
+self.MenuSmokeRed=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop Red smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Red})
+self.MenuSmokeWhite=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop White smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.White})
+end
+function ACT_ASSIST:onafterStop(ProcessUnit,From,Event,To)
+self.Menu:Remove()
+end
+end
+do
+ACT_ASSIST_SMOKE_TARGETS_ZONE={
+ClassName="ACT_ASSIST_SMOKE_TARGETS_ZONE",
+}
+function ACT_ASSIST_SMOKE_TARGETS_ZONE:New(TargetSetUnit,TargetZone)
+local self=BASE:Inherit(self,ACT_ASSIST:New())
+self.TargetSetUnit=TargetSetUnit
+self.TargetZone=TargetZone
+return self
+end
+function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init(FsmSmoke)
+self.TargetSetUnit=FsmSmoke.TargetSetUnit
+self.TargetZone=FsmSmoke.TargetZone
+end
+function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init(TargetSetUnit,TargetZone)
+self.TargetSetUnit=TargetSetUnit
+self.TargetZone=TargetZone
+return self
+end
+function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking(ProcessUnit,From,Event,To)
+self.TargetSetUnit:ForEachUnit(
+function(SmokeUnit)
+if math.random(1,(100*self.TargetSetUnit:Count())/4)<=100 then
+SCHEDULER:New(self,
+function()
+if SmokeUnit:IsAlive()then
+SmokeUnit:Smoke(self.SmokeColor,150)
+end
+end,{},math.random(10,60)
+)
+end
+end
+)
+end
+end
+COMMANDCENTER={
+ClassName="COMMANDCENTER",
+CommandCenterName="",
+CommandCenterCoalition=nil,
+CommandCenterPositionable=nil,
+Name="",
+ReferencePoints={},
+ReferenceNames={},
+CommunicationMode="80",
+}
+function COMMANDCENTER:New(CommandCenterPositionable,CommandCenterName)
+local self=BASE:Inherit(self,BASE:New())
+self.CommandCenterPositionable=CommandCenterPositionable
+self.CommandCenterName=CommandCenterName or CommandCenterPositionable:GetName()
+self.CommandCenterCoalition=CommandCenterPositionable:GetCoalition()
+self.Missions={}
+self:HandleEvent(EVENTS.Birth,
+function(self,EventData)
+if EventData.IniObjectCategory==1 then
+local EventGroup=GROUP:Find(EventData.IniDCSGroup)
+if EventGroup and self:HasGroup(EventGroup)then
+local MenuReporting=MENU_GROUP:New(EventGroup,"Missions Reports",self.CommandCenterMenu)
+local MenuMissionsSummary=MENU_GROUP_COMMAND:New(EventGroup,"Missions Status Report",MenuReporting,self.ReportMissionsStatus,self,EventGroup)
+local MenuMissionsDetails=MENU_GROUP_COMMAND:New(EventGroup,"Missions Players Report",MenuReporting,self.ReportMissionsPlayers,self,EventGroup)
+self:ReportSummary(EventGroup)
+local PlayerUnit=EventData.IniUnit
+for MissionID,Mission in pairs(self:GetMissions())do
+local Mission=Mission
+local PlayerGroup=EventData.IniGroup
+Mission:JoinUnit(PlayerUnit,PlayerGroup)
+end
+self:SetMenu()
+_DATABASE:PlayerSettingsMenu(PlayerUnit)
+end
+end
+end
+)
+self:HandleEvent(EVENTS.PlayerEnterUnit,
+function(self,EventData)
+local PlayerUnit=EventData.IniUnit
+for MissionID,Mission in pairs(self:GetMissions())do
+local Mission=Mission
+local PlayerGroup=EventData.IniGroup
+Mission:JoinUnit(PlayerUnit,PlayerGroup)
+end
+self:SetMenu()
+end
+)
+self:HandleEvent(EVENTS.MissionEnd,
+function(self,EventData)
+local PlayerUnit=EventData.IniUnit
+for MissionID,Mission in pairs(self:GetMissions())do
+local Mission=Mission
+Mission:Stop()
+end
+end
+)
+self:HandleEvent(EVENTS.PlayerLeaveUnit,
+function(self,EventData)
+local PlayerUnit=EventData.IniUnit
+for MissionID,Mission in pairs(self:GetMissions())do
+local Mission=Mission
+if Mission:IsENGAGED()then
+Mission:AbortUnit(PlayerUnit)
+end
+end
+end
+)
+self:HandleEvent(EVENTS.Crash,
+function(self,EventData)
+local PlayerUnit=EventData.IniUnit
+for MissionID,Mission in pairs(self:GetMissions())do
+local Mission=Mission
+if Mission:IsENGAGED()then
+Mission:CrashUnit(PlayerUnit)
+end
+end
+end
+)
+self:SetMenu()
+_SETTINGS:SetSystemMenu(CommandCenterPositionable)
+return self
+end
+function COMMANDCENTER:GetName()
+return self.CommandCenterName
+end
+function COMMANDCENTER:GetPositionable()
+return self.CommandCenterPositionable
+end
+function COMMANDCENTER:GetMissions()
+return self.Missions
+end
+function COMMANDCENTER:AddMission(Mission)
+self.Missions[Mission]=Mission
+return Mission
+end
+function COMMANDCENTER:RemoveMission(Mission)
+self.Missions[Mission]=nil
+return Mission
+end
+function COMMANDCENTER:SetReferenceZones(ReferenceZonePrefix)
+local MatchPattern="(.*)#(.*)"
+self:F({MatchPattern=MatchPattern})
+for ReferenceZoneName in pairs(_DATABASE.ZONENAMES)do
+local ZoneName,ReferenceName=string.match(ReferenceZoneName,MatchPattern)
+self:F({ZoneName=ZoneName,ReferenceName=ReferenceName})
+if ZoneName and ReferenceName and ZoneName==ReferenceZonePrefix then
+self.ReferencePoints[ReferenceZoneName]=ZONE:New(ReferenceZoneName)
+self.ReferenceNames[ReferenceZoneName]=ReferenceName
+end
+end
+return self
+end
+function COMMANDCENTER:SetModeWWII()
+self.CommunicationMode="WWII"
+return self
+end
+function COMMANDCENTER:IsModeWWII()
+return self.CommunicationMode=="WWII"
+end
+function COMMANDCENTER:SetMenu()
+self:F()
+self.CommandCenterMenu=self.CommandCenterMenu or MENU_COALITION:New(self.CommandCenterCoalition,"Command Center ("..self:GetName()..")")
+local MenuTime=timer.getTime()
+for MissionID,Mission in pairs(self:GetMissions()or{})do
+local Mission=Mission
+Mission:SetMenu(MenuTime)
+end
+for MissionID,Mission in pairs(self:GetMissions()or{})do
+local Mission=Mission
+Mission:RemoveMenu(MenuTime)
+end
+end
+function COMMANDCENTER:GetMenu()
+return self.CommandCenterMenu
+end
+function COMMANDCENTER:HasGroup(MissionGroup)
+local Has=false
+for MissionID,Mission in pairs(self.Missions)do
+local Mission=Mission
+if Mission:HasGroup(MissionGroup)then
+Has=true
+break
+end
+end
+return Has
+end
+function COMMANDCENTER:MessageToAll(Message)
+self:GetPositionable():MessageToAll(Message,20,self:GetName())
+end
+function COMMANDCENTER:MessageToGroup(Message,TaskGroup)
+self:GetPositionable():MessageToGroup(Message,15,TaskGroup,self:GetName())
+end
+function COMMANDCENTER:MessageTypeToGroup(Message,TaskGroup,MessageType)
+self:GetPositionable():MessageTypeToGroup(Message,MessageType,TaskGroup,self:GetName())
+end
+function COMMANDCENTER:MessageToCoalition(Message)
+local CCCoalition=self:GetPositionable():GetCoalition()
+self:GetPositionable():MessageToCoalition(Message,15,CCCoalition)
+end
+function COMMANDCENTER:MessageTypeToCoalition(Message,MessageType)
+local CCCoalition=self:GetPositionable():GetCoalition()
+self:GetPositionable():MessageTypeToCoalition(Message,MessageType,CCCoalition)
+end
+function COMMANDCENTER:ReportMissionsStatus(ReportGroup)
+self:E(ReportGroup)
+local Report=REPORT:New()
+Report:Add("Status report of all missions.")
+for MissionID,Mission in pairs(self.Missions)do
+local Mission=Mission
+Report:Add(" - "..Mission:ReportStatus())
+end
+self:MessageToGroup(Report:Text(),ReportGroup)
+end
+function COMMANDCENTER:ReportMissionsPlayers(ReportGroup)
+self:E(ReportGroup)
+local Report=REPORT:New()
+Report:Add("Players active in all missions.")
+for MissionID,Mission in pairs(self.Missions)do
+local Mission=Mission
+Report:Add(" - "..Mission:ReportPlayers())
+end
+self:MessageToGroup(Report:Text(),ReportGroup)
+end
+function COMMANDCENTER:ReportDetails(ReportGroup,Task)
+self:E(ReportGroup)
+local Report=REPORT:New()
+for MissionID,Mission in pairs(self.Missions)do
+local Mission=Mission
+Report:Add(" - "..Mission:ReportDetails())
+end
+self:MessageToGroup(Report:Text(),ReportGroup)
+end
+MISSION={
+ClassName="MISSION",
+Name="",
+MissionStatus="PENDING",
+AssignedGroups={},
+}
+function MISSION:New(CommandCenter,MissionName,MissionPriority,MissionBriefing,MissionCoalition)
+local self=BASE:Inherit(self,FSM:New())
+self:T({MissionName,MissionPriority,MissionBriefing,MissionCoalition})
+self.CommandCenter=CommandCenter
+CommandCenter:AddMission(self)
+self.Name=MissionName
+self.MissionPriority=MissionPriority
+self.MissionBriefing=MissionBriefing
+self.MissionCoalition=MissionCoalition
+self.Tasks={}
+self.PlayerNames={}
+self:SetStartState("IDLE")
+self:AddTransition("IDLE","Start","ENGAGED")
+self:AddTransition("ENGAGED","Stop","IDLE")
+self:AddTransition("ENGAGED","Complete","COMPLETED")
+self:AddTransition("*","Fail","FAILED")
+self:AddTransition("*","MissionGoals","*")
+CommandCenter:SetMenu()
+return self
+end
+function MISSION:onenterCOMPLETED(From,Event,To)
+self:GetCommandCenter():MessageTypeToCoalition(self:GetName().." has been completed! Good job guys!",MESSAGE.Type.Information)
+end
+function MISSION:GetName()
+return string.format('Mission "%s (%s)"',self.Name,self.MissionPriority)
+end
+function MISSION:JoinUnit(PlayerUnit,PlayerGroup)
+self:F({PlayerUnit=PlayerUnit,PlayerGroup=PlayerGroup})
+local PlayerUnitAdded=false
+for TaskID,Task in pairs(self:GetTasks())do
+local Task=Task
+if Task:JoinUnit(PlayerUnit,PlayerGroup)then
+PlayerUnitAdded=true
+end
+end
+return PlayerUnitAdded
+end
+function MISSION:AbortUnit(PlayerUnit)
+self:F({PlayerUnit=PlayerUnit})
+for TaskID,Task in pairs(self:GetTasks())do
+local Task=Task
+local PlayerGroup=PlayerUnit:GetGroup()
+Task:AbortGroup(PlayerGroup)
+end
+return self
+end
+function MISSION:CrashUnit(PlayerUnit)
+self:F({PlayerUnit=PlayerUnit})
+for TaskID,Task in pairs(self:GetTasks())do
+local Task=Task
+local PlayerGroup=PlayerUnit:GetGroup()
+Task:CrashGroup(PlayerGroup)
+end
+return self
+end
+function MISSION:AddScoring(Scoring)
+self.Scoring=Scoring
+return self
+end
+function MISSION:GetScoring()
+return self.Scoring
+end
+function MISSION:GetGroups()
+local SetGroup=SET_GROUP:New()
+for TaskID,Task in pairs(self:GetTasks())do
+local Task=Task
+local GroupSet=Task:GetGroups()
+GroupSet:ForEachGroup(
+function(TaskGroup)
+SetGroup:Add(TaskGroup,TaskGroup)
+end
+)
+end
+return SetGroup
+end
+function MISSION:SetMenu(MenuTime)
+self:F({self:GetName(),MenuTime})
+for _,TaskData in pairs(self:GetTasks())do
+local Task=TaskData
+Task:SetMenu(MenuTime)
+end
+end
+function MISSION:RemoveMenu(MenuTime)
+self:F({self:GetName(),MenuTime})
+for _,Task in pairs(self:GetTasks())do
+local Task=Task
+Task:RemoveMenu(MenuTime)
+end
+end
+do
+function MISSION:IsGroupAssigned(MissionGroup)
+local MissionGroupName=MissionGroup:GetName()
+if self.AssignedGroups[MissionGroupName]==MissionGroup then
+self:T({"Mission is assigned to:",MissionGroup:GetName()})
+return true
+end
+self:T({"Mission is not assigned to:",MissionGroup:GetName()})
+return false
+end
+function MISSION:SetGroupAssigned(MissionGroup)
+local MissionName=self:GetName()
+local MissionGroupName=MissionGroup:GetName()
+self.AssignedGroups[MissionGroupName]=MissionGroup
+self:E(string.format("Mission %s is assigned to %s",MissionName,MissionGroupName))
+return self
+end
+function MISSION:ClearGroupAssignment(MissionGroup)
+local MissionName=self:GetName()
+local MissionGroupName=MissionGroup:GetName()
+self.AssignedGroups[MissionGroupName]=nil
+return self
+end
+end
+function MISSION:GetCommandCenter()
+return self.CommandCenter
+end
+function MISSION:RemoveTaskMenu(Task)
+Task:RemoveMenu()
+end
+function MISSION:GetRootMenu(TaskGroup)
+local CommandCenter=self:GetCommandCenter()
+local CommandCenterMenu=CommandCenter:GetMenu()
+local MissionName=self:GetName()
+self.MissionMenu=self.MissionMenu or MENU_COALITION:New(self.MissionCoalition,self:GetName(),CommandCenterMenu)
+return self.MissionMenu
+end
+function MISSION:GetMenu(TaskGroup)
+local CommandCenter=self:GetCommandCenter()
+local CommandCenterMenu=CommandCenter:GetMenu()
+local MissionName=self:GetName()
+self.MissionGroupMenu=self.MissionGroupMenu or{}
+self.MissionGroupMenu[TaskGroup]=self.MissionGroupMenu[TaskGroup]or{}
+local GroupMenu=self.MissionGroupMenu[TaskGroup]
+self.MissionMenu=self.MissionMenu or MENU_COALITION:New(self.MissionCoalition,self:GetName(),CommandCenterMenu)
+GroupMenu.BriefingMenu=GroupMenu.BriefingMenu or MENU_GROUP_COMMAND:New(TaskGroup,"Mission Briefing",self.MissionMenu,self.MenuReportBriefing,self,TaskGroup)
+GroupMenu.TaskReportsMenu=GroupMenu.TaskReportsMenu or MENU_GROUP:New(TaskGroup,"Task Reports",self.MissionMenu)
+GroupMenu.ReportTasksMenu=GroupMenu.ReportTasksMenu or MENU_GROUP_COMMAND:New(TaskGroup,"Report Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksSummary,self,TaskGroup)
+GroupMenu.ReportPlannedTasksMenu=GroupMenu.ReportPlannedTasksMenu or MENU_GROUP_COMMAND:New(TaskGroup,"Report Planned Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksPerStatus,self,TaskGroup,"Planned")
+GroupMenu.ReportAssignedTasksMenu=GroupMenu.ReportAssignedTasksMenu or MENU_GROUP_COMMAND:New(TaskGroup,"Report Assigned Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksPerStatus,self,TaskGroup,"Assigned")
+GroupMenu.ReportSuccessTasksMenu=GroupMenu.ReportSuccessTasksMenu or MENU_GROUP_COMMAND:New(TaskGroup,"Report Successful Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksPerStatus,self,TaskGroup,"Success")
+GroupMenu.ReportFailedTasksMenu=GroupMenu.ReportFailedTasksMenu or MENU_GROUP_COMMAND:New(TaskGroup,"Report Failed Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksPerStatus,self,TaskGroup,"Failed")
+GroupMenu.ReportHeldTasksMenu=GroupMenu.ReportHeldTasksMenu or MENU_GROUP_COMMAND:New(TaskGroup,"Report Held Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksPerStatus,self,TaskGroup,"Hold")
+GroupMenu.PlayerReportsMenu=GroupMenu.PlayerReportsMenu or MENU_GROUP:New(TaskGroup,"Statistics Reports",self.MissionMenu)
+GroupMenu.ReportMissionHistory=GroupMenu.ReportPlayersHistory or MENU_GROUP_COMMAND:New(TaskGroup,"Report Mission Progress",GroupMenu.PlayerReportsMenu,self.MenuReportPlayersProgress,self,TaskGroup)
+GroupMenu.ReportPlayersPerTaskMenu=GroupMenu.ReportPlayersPerTaskMenu or MENU_GROUP_COMMAND:New(TaskGroup,"Report Players per Task",GroupMenu.PlayerReportsMenu,self.MenuReportPlayersPerTask,self,TaskGroup)
+return self.MissionMenu
+end
+function MISSION:GetTask(TaskName)
+self:F({TaskName})
+return self.Tasks[TaskName]
+end
+function MISSION:AddTask(Task)
+local TaskName=Task:GetTaskName()
+self:F(TaskName)
+self.Tasks[TaskName]=self.Tasks[TaskName]or{n=0}
+self.Tasks[TaskName]=Task
+self:GetCommandCenter():SetMenu()
+return Task
+end
+function MISSION:RemoveTask(Task)
+local TaskName=Task:GetTaskName()
+self:F(TaskName)
+self.Tasks[TaskName]=self.Tasks[TaskName]or{n=0}
+self.Tasks[TaskName]=nil
+Task=nil
+collectgarbage()
+self:GetCommandCenter():SetMenu()
+return nil
+end
+function MISSION:GetNextTaskID(Task)
+local TaskName=Task:GetTaskName()
+self:F(TaskName)
+self.Tasks[TaskName]=self.Tasks[TaskName]or{n=0}
+self.Tasks[TaskName].n=self.Tasks[TaskName].n+1
+return self.Tasks[TaskName].n
+end
+function MISSION:IsCOMPLETED()
+return self:Is("COMPLETED")
+end
+function MISSION:IsIDLE()
+return self:Is("IDLE")
+end
+function MISSION:IsENGAGED()
+return self:Is("ENGAGED")
+end
+function MISSION:IsFAILED()
+return self:Is("FAILED")
+end
+function MISSION:IsHOLD()
+return self:Is("HOLD")
+end
+function MISSION:HasGroup(TaskGroup)
+local Has=false
+for TaskID,Task in pairs(self:GetTasks())do
+local Task=Task
+if Task:HasGroup(TaskGroup)then
+Has=true
+break
+end
+end
+return Has
+end
+function MISSION:GetTasksRemaining()
+local TasksRemaining=0
+for TaskID,Task in pairs(self:GetTasks())do
+local Task=Task
+if Task:IsStateSuccess()or Task:IsStateFailed()then
+else
+TasksRemaining=TasksRemaining+1
+end
+end
+return TasksRemaining
+end
+function MISSION:GetTaskTypes()
+local TaskTypeList={}
+local TasksRemaining=0
+for TaskID,Task in pairs(self:GetTasks())do
+local Task=Task
+local TaskType=Task:GetType()
+TaskTypeList[TaskType]=TaskType
+end
+return TaskTypeList
+end
+function MISSION:AddPlayerName(PlayerName)
+self.PlayerNames=self.PlayerNames or{}
+self.PlayerNames[PlayerName]=PlayerName
+return self
+end
+function MISSION:GetPlayerNames()
+return self.PlayerNames
+end
+function MISSION:ReportBriefing()
+local Report=REPORT:New()
+local Name=self:GetName()
+local Status="<"..self:GetState()..">"
+Report:Add(string.format('%s - %s - Mission Briefing Report',Name,Status))
+Report:Add(self.MissionBriefing)
+return Report:Text()
+end
+function MISSION:ReportStatus()
+local Report=REPORT:New()
+local Name=self:GetName()
+local Status="<"..self:GetState()..">"
+Report:Add(string.format('%s - Status "%s"',Name,Status))
+local TaskTypes=self:GetTaskTypes()
+Report:Add(string.format(" - Task Types: %s",table.concat(TaskTypes,", ")))
+local TaskStatusList={"Planned","Assigned","Success","Hold","Cancelled","Aborted","Failed"}
+for TaskStatusID,TaskStatus in pairs(TaskStatusList)do
+local TaskCount=0
+local TaskPlayerCount=0
+for TaskID,Task in pairs(self:GetTasks())do
+local Task=Task
+if Task:Is(TaskStatus)then
+TaskCount=TaskCount+1
+TaskPlayerCount=TaskPlayerCount+Task:GetPlayerCount()
+end
+end
+if TaskCount>0 then
+Report:Add(string.format(" - %02d %s Tasks (%dp)",TaskCount,TaskStatus,TaskPlayerCount))
+end
+end
+return Report:Text()
+end
+function MISSION:ReportPlayersPerTask(ReportGroup)
+local Report=REPORT:New()
+local Name=self:GetName()
+local Status="<"..self:GetState()..">"
+Report:Add(string.format('%s - %s - Players per Task Report',Name,Status))
+local PlayerList={}
+for TaskID,Task in pairs(self:GetTasks())do
+local Task=Task
+local PlayerNames=Task:GetPlayerNames()
+for PlayerName,PlayerGroup in pairs(PlayerNames)do
+PlayerList[PlayerName]=Task:GetName()
+end
+end
+for PlayerName,TaskName in pairs(PlayerList)do
+Report:Add(string.format(' - Player (%s): Task "%s"',PlayerName,TaskName))
+end
+return Report:Text()
+end
+function MISSION:ReportPlayersProgress(ReportGroup)
+local Report=REPORT:New()
+local Name=self:GetName()
+local Status="<"..self:GetState()..">"
+Report:Add(string.format('%s - %s - Players per Task Progress Report',Name,Status))
+local PlayerList={}
+for TaskID,Task in pairs(self:GetTasks())do
+local Task=Task
+local TaskGoalTotal=Task:GetGoalTotal()or 0
+local TaskName=Task:GetName()
+PlayerList[TaskName]=PlayerList[TaskName]or{}
+if TaskGoalTotal~=0 then
+local PlayerNames=self:GetPlayerNames()
+for PlayerName,PlayerData in pairs(PlayerNames)do
+PlayerList[TaskName][PlayerName]=string.format('Player (%s): Task "%s": %d%%',PlayerName,TaskName,Task:GetPlayerProgress(PlayerName)*100/TaskGoalTotal)
+end
+else
+PlayerList[TaskName]["_"]=string.format('Player (---): Task "%s": %d%%',TaskName,0)
+end
+end
+for TaskName,TaskData in pairs(PlayerList)do
+for PlayerName,TaskText in pairs(TaskData)do
+Report:Add(string.format(' - %s',TaskText))
+end
+end
+return Report:Text()
+end
+function MISSION:ReportSummary(ReportGroup)
+local Report=REPORT:New()
+local Name=self:GetName()
+local Status="<"..self:GetState()..">"
+Report:Add(string.format('%s - %s - Task Overview Report',Name,Status))
+for TaskID,Task in UTILS.spairs(self:GetTasks(),function(t,a,b)return t[a]:ReportOrder(ReportGroup)"
+Report:Add(string.format('%s - %s - %s Tasks Report',Name,Status,TaskStatus))
+local Tasks=0
+for TaskID,Task in UTILS.spairs(self:GetTasks(),function(t,a,b)return t[a]:ReportOrder(ReportGroup)=8 then
+break
+end
+end
+return Report:Text()
+end
+function MISSION:ReportDetails(ReportGroup)
+local Report=REPORT:New()
+local Name=self:GetName()
+local Status="<"..self:GetState()..">"
+Report:Add(string.format('%s - %s - Task Detailed Report',Name,Status))
+local TasksRemaining=0
+for TaskID,Task in pairs(self:GetTasks())do
+local Task=Task
+Report:Add(Task:ReportDetails(ReportGroup))
+end
+return Report:Text()
+end
+function MISSION:GetTasks()
+return self.Tasks
+end
+function MISSION:MenuReportBriefing(ReportGroup)
+local Report=self:ReportBriefing()
+self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Briefing)
+end
+function MISSION:MenuReportTasksSummary(ReportGroup)
+local Report=self:ReportSummary(ReportGroup)
+self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview)
+end
+function MISSION:MenuReportTasksPerStatus(ReportGroup,TaskStatus)
+local Report=self:ReportOverview(ReportGroup,TaskStatus)
+self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview)
+end
+function MISSION:MenuReportPlayersPerTask(ReportGroup)
+local Report=self:ReportPlayersPerTask()
+self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview)
+end
+function MISSION:MenuReportPlayersProgress(ReportGroup)
+local Report=self:ReportPlayersProgress()
+self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview)
+end
+TASK={
+ClassName="TASK",
+TaskScheduler=nil,
+ProcessClasses={},
+Processes={},
+Players=nil,
+Scores={},
+Menu={},
+SetGroup=nil,
+FsmTemplate=nil,
+Mission=nil,
+CommandCenter=nil,
+TimeOut=0,
+AssignedGroups={},
+}
+function TASK:New(Mission,SetGroupAssign,TaskName,TaskType,TaskBriefing)
+local self=BASE:Inherit(self,FSM_TASK:New())
+self:SetStartState("Planned")
+self:AddTransition("Planned","Assign","Assigned")
+self:AddTransition("Assigned","AssignUnit","Assigned")
+self:AddTransition("Assigned","Success","Success")
+self:AddTransition("Assigned","Hold","Hold")
+self:AddTransition("Assigned","Fail","Failed")
+self:AddTransition("Assigned","Abort","Aborted")
+self:AddTransition("Assigned","Cancel","Cancelled")
+self:AddTransition("Assigned","Goal","*")
+self:AddTransition("*","PlayerCrashed","*")
+self:AddTransition("*","PlayerAborted","*")
+self:AddTransition("*","PlayerDead","*")
+self:AddTransition({"Failed","Aborted","Cancelled"},"Replan","Planned")
+self:AddTransition("*","TimeOut","Cancelled")
+self:E("New TASK "..TaskName)
+self.Processes={}
+self.Fsm={}
+self.Mission=Mission
+self.CommandCenter=Mission:GetCommandCenter()
+self.SetGroup=SetGroupAssign
+self:SetType(TaskType)
+self:SetName(TaskName)
+self:SetID(Mission:GetNextTaskID(self))
+self:SetBriefing(TaskBriefing)
+self.FsmTemplate=self.FsmTemplate or FSM_PROCESS:New()
+self.TaskInfo={}
+self.TaskProgress={}
+return self
+end
+function TASK:GetUnitProcess(TaskUnit)
+if TaskUnit then
+return self:GetStateMachine(TaskUnit)
+else
+return self.FsmTemplate
+end
+end
+function TASK:SetUnitProcess(FsmTemplate)
+self.FsmTemplate=FsmTemplate
+end
+function TASK:JoinUnit(PlayerUnit,PlayerGroup)
+self:F({PlayerUnit=PlayerUnit,PlayerGroup=PlayerGroup})
+local PlayerUnitAdded=false
+local PlayerGroups=self:GetGroups()
+if PlayerGroups:IsIncludeObject(PlayerGroup)then
+if self:IsStatePlanned()or self:IsStateReplanned()then
+end
+if self:IsStateAssigned()then
+local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup)
+self:E({IsGroupAssigned=IsGroupAssigned})
+if IsGroupAssigned then
+self:AssignToUnit(PlayerUnit)
+self:MessageToGroups(PlayerUnit:GetPlayerName().." joined Task "..self:GetName())
+end
+end
+end
+return PlayerUnitAdded
+end
+function TASK:AbortGroup(PlayerGroup)
+self:F({PlayerGroup=PlayerGroup})
+local PlayerGroups=self:GetGroups()
+if PlayerGroups:IsIncludeObject(PlayerGroup)then
+if self:IsStateAssigned()then
+local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup)
+self:E({IsGroupAssigned=IsGroupAssigned})
+if IsGroupAssigned then
+local PlayerName=PlayerGroup:GetUnit(1):GetPlayerName()
+self:UnAssignFromGroup(PlayerGroup)
+PlayerGroups:Flush()
+local IsRemaining=false
+for GroupName,AssignedGroup in pairs(PlayerGroups:GetSet()or{})do
+if self:IsGroupAssigned(AssignedGroup)==true then
+IsRemaining=true
+self:F({Task=self:GetName(),IsRemaining=IsRemaining})
+break
+end
+end
+self:F({Task=self:GetName(),IsRemaining=IsRemaining})
+if IsRemaining==false then
+self:Abort()
+end
+self:PlayerAborted(PlayerGroup:GetUnit(1))
+end
+end
+end
+return self
+end
+function TASK:CrashGroup(PlayerGroup)
+self:F({PlayerGroup=PlayerGroup})
+local PlayerGroups=self:GetGroups()
+if PlayerGroups:IsIncludeObject(PlayerGroup)then
+if self:IsStateAssigned()then
+local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup)
+self:E({IsGroupAssigned=IsGroupAssigned})
+if IsGroupAssigned then
+local PlayerName=PlayerGroup:GetUnit(1):GetPlayerName()
+self:MessageToGroups(PlayerName.." crashed! ")
+self:UnAssignFromGroup(PlayerGroup)
+PlayerGroups:Flush()
+local IsRemaining=false
+for GroupName,AssignedGroup in pairs(PlayerGroups:GetSet()or{})do
+if self:IsGroupAssigned(AssignedGroup)==true then
+IsRemaining=true
+self:F({Task=self:GetName(),IsRemaining=IsRemaining})
+break
+end
+end
+self:F({Task=self:GetName(),IsRemaining=IsRemaining})
+if IsRemaining==false then
+self:Abort()
+end
+self:PlayerCrashed(PlayerGroup:GetUnit(1))
+end
+end
+end
+return self
+end
+function TASK:GetMission()
+return self.Mission
+end
+function TASK:GetGroups()
+return self.SetGroup
+end
+do
+function TASK:IsGroupAssigned(TaskGroup)
+local TaskGroupName=TaskGroup:GetName()
+if self.AssignedGroups[TaskGroupName]then
+self:T({"Task is assigned to:",TaskGroup:GetName()})
+return true
+end
+self:T({"Task is not assigned to:",TaskGroup:GetName()})
+return false
+end
+function TASK:SetGroupAssigned(TaskGroup)
+local TaskName=self:GetName()
+local TaskGroupName=TaskGroup:GetName()
+self.AssignedGroups[TaskGroupName]=TaskGroup
+self:E(string.format("Task %s is assigned to %s",TaskName,TaskGroupName))
+self:GetMission():SetGroupAssigned(TaskGroup)
+local SetAssignedGroups=self:GetGroups()
+return self
+end
+function TASK:ClearGroupAssignment(TaskGroup)
+local TaskName=self:GetName()
+local TaskGroupName=TaskGroup:GetName()
+self.AssignedGroups[TaskGroupName]=nil
+self:GetMission():ClearGroupAssignment(TaskGroup)
+local SetAssignedGroups=self:GetGroups()
+SetAssignedGroups:ForEachGroup(
+function(AssignedGroup)
+if self:IsGroupAssigned(AssignedGroup)then
+else
+end
+end
+)
+return self
+end
+end
+do
+function TASK:AssignToGroup(TaskGroup)
+self:F(TaskGroup:GetName())
+local TaskGroupName=TaskGroup:GetName()
+local Mission=self:GetMission()
+local CommandCenter=Mission:GetCommandCenter()
+self:SetGroupAssigned(TaskGroup)
+local TaskUnits=TaskGroup:GetUnits()
+for UnitID,UnitData in pairs(TaskUnits)do
+local TaskUnit=UnitData
+local PlayerName=TaskUnit:GetPlayerName()
+self:E(PlayerName)
+if PlayerName~=nil and PlayerName~=""then
+self:AssignToUnit(TaskUnit)
+CommandCenter:MessageToGroup(
+string.format('Task "%s": Briefing for player (%s):\n%s',
+self:GetName(),
+PlayerName,
+self:GetBriefing()
+),TaskGroup
+)
+end
+end
+CommandCenter:SetMenu()
+return self
+end
+function TASK:UnAssignFromGroup(TaskGroup)
+self:F2({TaskGroup=TaskGroup:GetName()})
+self:ClearGroupAssignment(TaskGroup)
+local TaskUnits=TaskGroup:GetUnits()
+for UnitID,UnitData in pairs(TaskUnits)do
+local TaskUnit=UnitData
+local PlayerName=TaskUnit:GetPlayerName()
+if PlayerName~=nil and PlayerName~=""then
+self:UnAssignFromUnit(TaskUnit)
+end
+end
+local Mission=self:GetMission()
+local CommandCenter=Mission:GetCommandCenter()
+CommandCenter:SetMenu()
+end
+end
+function TASK:HasGroup(FindGroup)
+local SetAttackGroup=self:GetGroups()
+return SetAttackGroup:FindGroup(FindGroup)
+end
+function TASK:AssignToUnit(TaskUnit)
+self:F(TaskUnit:GetName())
+local FsmTemplate=self:GetUnitProcess()
+local FsmUnit=self:SetStateMachine(TaskUnit,FsmTemplate:Copy(TaskUnit,self))
+FsmUnit:SetStartState("Planned")
+FsmUnit:Accept()
+return self
+end
+function TASK:UnAssignFromUnit(TaskUnit)
+self:F(TaskUnit:GetName())
+self:RemoveStateMachine(TaskUnit)
+return self
+end
+function TASK:SetTimeOut(Timer)
+self:F(Timer)
+self.TimeOut=Timer
+self:__TimeOut(self.TimeOut)
+return self
+end
+function TASK:MessageToGroups(Message)
+self:F({Message=Message})
+local Mission=self:GetMission()
+local CC=Mission:GetCommandCenter()
+for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do
+local TaskGroup=TaskGroup
+CC:MessageToGroup(Message,TaskGroup,TaskGroup:GetName())
+end
+end
+function TASK:SendBriefingToAssignedGroups()
+self:F2()
+for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do
+if self:IsGroupAssigned(TaskGroup)then
+TaskGroup:Message(self.TaskBriefing,60)
+end
+end
+end
+function TASK:UnAssignFromGroups()
+self:F2()
+for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do
+if self:IsGroupAssigned(TaskGroup)then
+self:UnAssignFromGroup(TaskGroup)
+end
+end
+end
+function TASK:HasAliveUnits()
+self:F()
+for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
+if self:IsStateAssigned()then
+if self:IsGroupAssigned(TaskGroup)then
+for TaskUnitID,TaskUnit in pairs(TaskGroup:GetUnits())do
+if TaskUnit:IsAlive()then
+self:T({HasAliveUnits=true})
+return true
+end
+end
+end
+end
+end
+self:T({HasAliveUnits=false})
+return false
+end
+function TASK:SetMenu(MenuTime)
+self:F({self:GetName(),MenuTime})
+for TaskGroupID,TaskGroupData in pairs(self.SetGroup:GetSet())do
+local TaskGroup=TaskGroupData
+if TaskGroup:IsAlive()and TaskGroup:GetPlayerNames()then
+local Mission=self:GetMission()
+local MissionMenu=Mission:GetMenu(TaskGroup)
+if MissionMenu then
+self:SetMenuForGroup(TaskGroup,MenuTime)
+end
+end
+end
+end
+function TASK:SetMenuForGroup(TaskGroup,MenuTime)
+if self:IsStatePlanned()or self:IsStateAssigned()then
+self:SetPlannedMenuForGroup(TaskGroup,MenuTime)
+if self:IsGroupAssigned(TaskGroup)then
+self:SetAssignedMenuForGroup(TaskGroup,MenuTime)
+end
+end
+end
+function TASK:SetPlannedMenuForGroup(TaskGroup,MenuTime)
+self:F(TaskGroup:GetName())
+local Mission=self:GetMission()
+local MissionName=Mission:GetName()
+local CommandCenter=Mission:GetCommandCenter()
+local CommandCenterMenu=CommandCenter:GetMenu()
+local TaskType=self:GetType()
+local TaskPlayerCount=self:GetPlayerCount()
+local TaskPlayerString=string.format(" (%dp)",TaskPlayerCount)
+local TaskText=string.format("%s%s",self:GetName(),TaskPlayerString)
+local TaskName=string.format("%s",self:GetName())
+local MissionMenu=Mission:GetMenu(TaskGroup)
+self.MenuPlanned=self.MenuPlanned or{}
+self.MenuPlanned[TaskGroup]=MENU_GROUP:New(TaskGroup,"Join Planned Task",MissionMenu,Mission.MenuReportTasksPerStatus,Mission,TaskGroup,"Planned"):SetTime(MenuTime):SetTag("Tasking")
+local TaskTypeMenu=MENU_GROUP:New(TaskGroup,TaskType,self.MenuPlanned[TaskGroup]):SetTime(MenuTime):SetTag("Tasking"):SetRemoveParent(true)
+local TaskTypeMenu=MENU_GROUP:New(TaskGroup,TaskText,TaskTypeMenu):SetTime(MenuTime):SetTag("Tasking"):SetRemoveParent(true)
+local ReportTaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Report Task Status"),TaskTypeMenu,self.MenuTaskStatus,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking"):SetRemoveParent(true)
+if not Mission:IsGroupAssigned(TaskGroup)then
+self:F({"Replacing Join Task menu"})
+local JoinTaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Join Task"),TaskTypeMenu,self.MenuAssignToGroup,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking"):SetRemoveParent(true)
+local MarkTaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Mark Task on Map"),TaskTypeMenu,self.MenuMarkToGroup,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking"):SetRemoveParent(true)
+end
+return self
+end
+function TASK:SetAssignedMenuForGroup(TaskGroup,MenuTime)
+self:F({TaskGroup:GetName(),MenuTime})
+local Mission=self:GetMission()
+local MissionName=Mission:GetName()
+local CommandCenter=Mission:GetCommandCenter()
+local CommandCenterMenu=CommandCenter:GetMenu()
+local TaskType=self:GetType()
+local TaskPlayerCount=self:GetPlayerCount()
+local TaskPlayerString=string.format(" (%dp)",TaskPlayerCount)
+local TaskText=string.format("%s%s",self:GetName(),TaskPlayerString)
+local TaskName=string.format("%s",self:GetName())
+local MissionMenu=Mission:GetMenu(TaskGroup)
+self.MenuAssigned=self.MenuAssigned or{}
+self.MenuAssigned[TaskGroup]=MENU_GROUP:New(TaskGroup,string.format("Assigned Task %s",TaskName),MissionMenu):SetTime(MenuTime):SetTag("Tasking")
+local TaskTypeMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Report Task Status"),self.MenuAssigned[TaskGroup],self.MenuTaskStatus,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking"):SetRemoveParent(true)
+local TaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Abort Group from Task"),self.MenuAssigned[TaskGroup],self.MenuTaskAbort,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking"):SetRemoveParent(true)
+return self
+end
+function TASK:RemoveMenu(MenuTime)
+self:F({self:GetName(),MenuTime})
+for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
+local TaskGroup=TaskGroup
+self:RefreshMenus(TaskGroup,MenuTime)
+end
+end
+function TASK:RefreshMenus(TaskGroup,MenuTime)
+self:F({TaskGroup:GetName(),MenuTime})
+local Mission=self:GetMission()
+local MissionName=Mission:GetName()
+local CommandCenter=Mission:GetCommandCenter()
+local CommandCenterMenu=CommandCenter:GetMenu()
+local MissionMenu=Mission:GetMenu(TaskGroup)
+local TaskName=self:GetName()
+self.MenuPlanned=self.MenuPlanned or{}
+local PlannedMenu=self.MenuPlanned[TaskGroup]
+self.MenuAssigned=self.MenuAssigned or{}
+local AssignedMenu=self.MenuAssigned[TaskGroup]
+if PlannedMenu then
+PlannedMenu:Remove(MenuTime,"Tasking")
+end
+if AssignedMenu then
+AssignedMenu:Remove(MenuTime,"Tasking")
+end
+end
+function TASK:RemoveAssignedMenuForGroup(TaskGroup)
+self:F()
+local Mission=self:GetMission()
+local MissionName=Mission:GetName()
+local MissionMenu=Mission:GetMenu(TaskGroup)
+if MissionMenu then
+MissionMenu:RemoveSubMenus()
+end
+end
+function TASK:MenuAssignToGroup(TaskGroup)
+self:E("Join Task menu selected")
+self:AssignToGroup(TaskGroup)
+end
+function TASK:MenuMarkToGroup(TaskGroup)
+self:E("Mark Task menu selected")
+self:UpdateTaskInfo()
+local Report=REPORT:New():SetIndent(0)
+local Name=self:GetName()
+Report:Add("Task "..Name..": "..self:GetTaskBriefing().."\n")
+for TaskInfoID,TaskInfo in pairs(self.TaskInfo,function(t,a,b)return t[a].TaskInfoOrder")
+if self.TaskInfo["Coordinate"]then
+local TaskInfoIDText=string.format("%s: ","Coordinate")
+local TaskCoord=self.TaskInfo["Coordinate"].TaskInfoText
+Report:Add(TaskInfoIDText..TaskCoord:ToString(ReportGroup,nil,self))
+end
+return Report:Text(', ')
+end
+function TASK:ReportOverview(ReportGroup)
+self:UpdateTaskInfo()
+local TaskName=self:GetName()
+local Report=REPORT:New()
+local Line=0
+local LineReport=REPORT:New()
+for TaskInfoID,TaskInfo in UTILS.spairs(self.TaskInfo,function(t,a,b)return t[a].TaskInfoOrder"
+Report:Add("Task "..Name.." - "..Status.." - Detailed Report")
+local PlayerNames=self:GetPlayerNames()
+local PlayerReport=REPORT:New()
+for PlayerName,PlayerGroup in pairs(PlayerNames)do
+PlayerReport:Add("Group "..PlayerGroup:GetCallsign()..": "..PlayerName)
+end
+local Players=PlayerReport:Text()
+if Players~=""then
+Report:Add(" - Players assigned:")
+Report:AddIndent(Players)
+end
+for TaskInfoID,TaskInfo in pairs(self.TaskInfo,function(t,a,b)return t[a].TaskInfoOrder0 then
+local TargetSetUnit=SET_UNIT:New()
+TargetSetUnit:SetDatabase(DetectedSet)
+TargetSetUnit:FilterHasSEAD()
+TargetSetUnit:FilterOnce()
+return TargetSetUnit
+end
+return nil
+end
+function TASK_A2G_DISPATCHER:EvaluateCAS(DetectedItem)
+self:F({DetectedItem.ItemID})
+local DetectedSet=DetectedItem.Set
+local DetectedZone=DetectedItem.Zone
+local GroundUnitCount=DetectedSet:HasGroundUnits()
+local FriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem)
+local RadarCount=DetectedSet:HasSEAD()
+if RadarCount==0 and GroundUnitCount>0 and FriendliesNearBy==true then
+local TargetSetUnit=SET_UNIT:New()
+TargetSetUnit:SetDatabase(DetectedSet)
+TargetSetUnit:FilterOnce()
+return TargetSetUnit
+end
+return nil
+end
+function TASK_A2G_DISPATCHER:EvaluateBAI(DetectedItem,FriendlyCoalition)
+self:F({DetectedItem.ItemID})
+local DetectedSet=DetectedItem.Set
+local DetectedZone=DetectedItem.Zone
+local GroundUnitCount=DetectedSet:HasGroundUnits()
+local FriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem)
+local RadarCount=DetectedSet:HasSEAD()
+if RadarCount==0 and GroundUnitCount>0 and FriendliesNearBy==false then
+local TargetSetUnit=SET_UNIT:New()
+TargetSetUnit:SetDatabase(DetectedSet)
+TargetSetUnit:FilterOnce()
+return TargetSetUnit
+end
+return nil
+end
+function TASK_A2G_DISPATCHER:RemoveTask(TaskIndex)
+self.Mission:RemoveTask(self.Tasks[TaskIndex])
+self.Tasks[TaskIndex]=nil
+end
+function TASK_A2G_DISPATCHER:EvaluateRemoveTask(Mission,Task,TaskIndex,DetectedItemChanged)
+if Task then
+if(Task:IsStatePlanned()and DetectedItemChanged==true)or Task:IsStateCancelled()then
+self:RemoveTask(TaskIndex)
+end
+end
+return Task
+end
+function TASK_A2G_DISPATCHER:ProcessDetected(Detection)
+self:E()
+local AreaMsg={}
+local TaskMsg={}
+local ChangeMsg={}
+local Mission=self.Mission
+if Mission:IsIDLE()or Mission:IsENGAGED()then
+local TaskReport=REPORT:New()
+for TaskIndex,TaskData in pairs(self.Tasks)do
+local Task=TaskData
+if Task:IsStatePlanned()then
+local DetectedItem=Detection:GetDetectedItem(TaskIndex)
+if not DetectedItem then
+local TaskText=Task:GetName()
+for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
+Mission:GetCommandCenter():MessageToGroup(string.format("Obsolete A2G task %s for %s removed.",TaskText,Mission:GetName()),TaskGroup)
+end
+Task=self:RemoveTask(TaskIndex)
+Mission:RemoveTask(Task)
+self.Tasks[TaskIndex]=nil
+end
+end
+end
+for DetectedItemID,DetectedItem in pairs(Detection:GetDetectedItems())do
+local DetectedItem=DetectedItem
+local DetectedSet=DetectedItem.Set
+local DetectedZone=DetectedItem.Zone
+local DetectedItemID=DetectedItem.ID
+local TaskIndex=DetectedItem.Index
+local DetectedItemChanged=DetectedItem.Changed
+self:E({DetectedItemChanged=DetectedItemChanged,DetectedItemID=DetectedItemID,TaskIndex=TaskIndex})
+local Task=self.Tasks[TaskIndex]
+if Task then
+if Task:IsStateAssigned()then
+if DetectedItemChanged==true then
+local TargetsReport=REPORT:New()
+local TargetSetUnit=self:EvaluateSEAD(DetectedItem)
+if TargetSetUnit then
+if Task:IsInstanceOf(TASK_A2G_SEAD)then
+Task:SetTargetSetUnit(TargetSetUnit)
+Task:UpdateTaskInfo()
+TargetsReport:Add(Detection:GetChangeText(DetectedItem))
+else
+Task:Cancel()
+end
+else
+local TargetSetUnit=self:EvaluateCAS(DetectedItem)
+if TargetSetUnit then
+if Task:IsInstanceOf(TASK_A2G_CAS)then
+Task:SetTargetSetUnit(TargetSetUnit)
+Task:SetDetection(Detection,TaskIndex)
+Task:UpdateTaskInfo()
+TargetsReport:Add(Detection:GetChangeText(DetectedItem))
+else
+Task:Cancel()
+Task=self:RemoveTask(TaskIndex)
+end
+else
+local TargetSetUnit=self:EvaluateBAI(DetectedItem)
+if TargetSetUnit then
+if Task:IsInstanceOf(TASK_A2G_BAI)then
+Task:SetTargetSetUnit(TargetSetUnit)
+Task:SetDetection(Detection,TaskIndex)
+Task:UpdateTaskInfo()
+TargetsReport:Add(Detection:GetChangeText(DetectedItem))
+else
+Task:Cancel()
+Task=self:RemoveTask(TaskIndex)
+end
+end
+end
+end
+for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
+local TargetsText=TargetsReport:Text(", ")
+if(Mission:IsGroupAssigned(TaskGroup))and TargetsText~=""then
+Mission:GetCommandCenter():MessageToGroup(string.format("Task %s has change of targets:\n %s",Task:GetName(),TargetsText),TaskGroup)
+end
+end
+end
+end
+end
+if Task then
+if Task:IsStatePlanned()then
+if DetectedItemChanged==true then
+if Task:IsInstanceOf(TASK_A2G_SEAD)then
+local TargetSetUnit=self:EvaluateSEAD(DetectedItem)
+if TargetSetUnit then
+Task:SetTargetSetUnit(TargetSetUnit)
+Task:UpdateTaskInfo()
+else
+Task:Cancel()
+Task=self:RemoveTask(TaskIndex)
+end
+else
+if Task:IsInstanceOf(TASK_A2G_CAS)then
+local TargetSetUnit=self:EvaluateCAS(DetectedItem)
+if TargetSetUnit then
+Task:SetTargetSetUnit(TargetSetUnit)
+Task:SetDetection(Detection,TaskIndex)
+Task:UpdateTaskInfo()
+else
+Task:Cancel()
+Task=self:RemoveTask(TaskIndex)
+end
+else
+if Task:IsInstanceOf(TASK_A2G_BAI)then
+local TargetSetUnit=self:EvaluateBAI(DetectedItem)
+if TargetSetUnit then
+Task:SetTargetSetUnit(TargetSetUnit)
+Task:SetDetection(Detection,TaskIndex)
+Task:UpdateTaskInfo()
+else
+Task:Cancel()
+Task=self:RemoveTask(TaskIndex)
+end
+else
+Task:Cancel()
+Task=self:RemoveTask(TaskIndex)
+end
+end
+end
+end
+end
+end
+if not Task then
+local TargetSetUnit=self:EvaluateSEAD(DetectedItem)
+if TargetSetUnit then
+Task=TASK_A2G_SEAD:New(Mission,self.SetGroup,string.format("SEAD.%03d",DetectedItemID),TargetSetUnit)
+Task:SetDetection(Detection,TaskIndex)
+end
+if not Task then
+local TargetSetUnit=self:EvaluateCAS(DetectedItem)
+if TargetSetUnit then
+Task=TASK_A2G_CAS:New(Mission,self.SetGroup,string.format("CAS.%03d",DetectedItemID),TargetSetUnit)
+Task:SetDetection(Detection,TaskIndex)
+end
+if not Task then
+local TargetSetUnit=self:EvaluateBAI(DetectedItem,self.Mission:GetCommandCenter():GetPositionable():GetCoalition())
+if TargetSetUnit then
+Task=TASK_A2G_BAI:New(Mission,self.SetGroup,string.format("BAI.%03d",DetectedItemID),TargetSetUnit)
+Task:SetDetection(Detection,TaskIndex)
+end
+end
+end
+if Task then
+self.Tasks[TaskIndex]=Task
+Task:SetTargetZone(DetectedZone)
+Task:SetDispatcher(self)
+Task:UpdateTaskInfo()
+Mission:AddTask(Task)
+TaskReport:Add(Task:GetName())
+else
+self:E("This should not happen")
+end
+end
+Detection:AcceptChanges(DetectedItem)
+end
+Mission:GetCommandCenter():SetMenu()
+local TaskText=TaskReport:Text(", ")
+for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
+if(not Mission:IsGroupAssigned(TaskGroup))and TaskText~=""then
+Mission:GetCommandCenter():MessageToGroup(string.format("%s has tasks %s. Subscribe to a task using the radio menu.",Mission:GetName(),TaskText),TaskGroup)
+end
+end
+end
+return true
+end
+end
+do
+TASK_A2G={
+ClassName="TASK_A2G",
+}
+function TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskType,TaskBriefing)
+local self=BASE:Inherit(self,TASK:New(Mission,SetGroup,TaskName,TaskType,TaskBriefing))
+self:F()
+self.TargetSetUnit=TargetSetUnit
+self.TaskType=TaskType
+local Fsm=self:GetUnitProcess()
+Fsm:AddProcess("Planned","Accept",ACT_ASSIGN_ACCEPT:New(self.TaskBriefing),{Assigned="RouteToRendezVous",Rejected="Reject"})
+Fsm:AddTransition("Assigned","RouteToRendezVous","RoutingToRendezVous")
+Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousPoint",ACT_ROUTE_POINT:New(),{Arrived="ArriveAtRendezVous"})
+Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousZone",ACT_ROUTE_ZONE:New(),{Arrived="ArriveAtRendezVous"})
+Fsm:AddTransition({"Arrived","RoutingToRendezVous"},"ArriveAtRendezVous","ArrivedAtRendezVous")
+Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"Engage","Engaging")
+Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"HoldAtRendezVous","HoldingAtRendezVous")
+Fsm:AddProcess("Engaging","Account",ACT_ACCOUNT_DEADS:New(),{})
+Fsm:AddTransition("Engaging","RouteToTarget","Engaging")
+Fsm:AddProcess("Engaging","RouteToTargetZone",ACT_ROUTE_ZONE:New(),{})
+Fsm:AddProcess("Engaging","RouteToTargetPoint",ACT_ROUTE_POINT:New(),{})
+Fsm:AddTransition("Engaging","RouteToTargets","Engaging")
+Fsm:AddTransition("Rejected","Reject","Aborted")
+Fsm:AddTransition("Failed","Fail","Failed")
+function Fsm:onafterRouteToRendezVous(TaskUnit,Task)
+self:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
+if Task:GetRendezVousZone(TaskUnit)then
+self:__RouteToRendezVousZone(0.1)
+else
+if Task:GetRendezVousCoordinate(TaskUnit)then
+self:__RouteToRendezVousPoint(0.1)
+else
+self:__ArriveAtRendezVous(0.1)
+end
+end
+end
+function Fsm:OnAfterArriveAtRendezVous(TaskUnit,Task)
+self:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
+self:__Engage(0.1)
+end
+function Fsm:onafterEngage(TaskUnit,Task)
+self:E({self})
+self:__Account(0.1)
+self:__RouteToTarget(0.1)
+self:__RouteToTargets(-10)
+end
+function Fsm:onafterRouteToTarget(TaskUnit,Task)
+self:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
+if Task:GetTargetZone(TaskUnit)then
+self:__RouteToTargetZone(0.1)
+else
+local TargetUnit=Task.TargetSetUnit:GetFirst()
+if TargetUnit then
+local Coordinate=TargetUnit:GetPointVec3()
+self:T({TargetCoordinate=Coordinate,Coordinate:GetX(),Coordinate:GetY(),Coordinate:GetZ()})
+Task:SetTargetCoordinate(Coordinate,TaskUnit)
+end
+self:__RouteToTargetPoint(0.1)
+end
+end
+function Fsm:onafterRouteToTargets(TaskUnit,Task)
+self:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
+local TargetUnit=Task.TargetSetUnit:GetFirst()
+if TargetUnit then
+Task:SetTargetCoordinate(TargetUnit:GetCoordinate(),TaskUnit)
+end
+self:__RouteToTargets(-10)
+end
+return self
+end
+function TASK_A2G:SetTargetSetUnit(TargetSetUnit)
+self.TargetSetUnit=TargetSetUnit
+end
+function TASK_A2G:GetPlannedMenuText()
+return self:GetStateString().." - "..self:GetTaskName().." ( "..self.TargetSetUnit:GetUnitTypesText().." )"
+end
+function TASK_A2G:SetRendezVousCoordinate(RendezVousCoordinate,RendezVousRange,TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint")
+ActRouteRendezVous:SetCoordinate(RendezVousCoordinate)
+ActRouteRendezVous:SetRange(RendezVousRange)
+end
+function TASK_A2G:GetRendezVousCoordinate(TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint")
+return ActRouteRendezVous:GetCoordinate(),ActRouteRendezVous:GetRange()
+end
+function TASK_A2G:SetRendezVousZone(RendezVousZone,TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone")
+ActRouteRendezVous:SetZone(RendezVousZone)
+end
+function TASK_A2G:GetRendezVousZone(TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone")
+return ActRouteRendezVous:GetZone()
+end
+function TASK_A2G:SetTargetCoordinate(TargetCoordinate,TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint")
+ActRouteTarget:SetCoordinate(TargetCoordinate)
+end
+function TASK_A2G:GetTargetCoordinate(TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint")
+return ActRouteTarget:GetCoordinate()
+end
+function TASK_A2G:SetTargetZone(TargetZone,TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone")
+ActRouteTarget:SetZone(TargetZone)
+end
+function TASK_A2G:GetTargetZone(TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone")
+return ActRouteTarget:GetZone()
+end
+function TASK_A2G:SetGoalTotal()
+self.GoalTotal=self.TargetSetUnit:Count()
+end
+function TASK_A2G:GetGoalTotal()
+return self.GoalTotal
+end
+function TASK_A2G:GetMarkInfo(TaskInfoID,TaskInfo)
+if type(TaskInfo.TaskInfoText)=="string"then
+if TaskInfoID=="Targets"then
+else
+return string.format("%s: %s",TaskInfoID,TaskInfo.TaskInfoText)
+end
+elseif type(TaskInfo)=="table"then
+if TaskInfoID=="Coordinate"then
+end
+end
+return nil
+end
+function TASK_A2G:GetReportDetail(ReportGroup,TaskInfoID,TaskInfo)
+if type(TaskInfo.TaskInfoText)=="string"then
+return string.format("%s: %s",TaskInfoID,TaskInfo.TaskInfoText)
+elseif type(TaskInfo)=="table"then
+if TaskInfoID=="Coordinate"then
+local FromCoordinate=ReportGroup:GetUnit(1):GetCoordinate()
+local ToCoordinate=TaskInfo.TaskInfoText
+return string.format(" - %s: %s",TaskInfoID,ToCoordinate:ToString(ReportGroup:GetUnit(1),nil,self))
+else
+end
+end
+end
+end
+do
+TASK_A2G_SEAD={
+ClassName="TASK_A2G_SEAD",
+}
+function TASK_A2G_SEAD:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing)
+local self=BASE:Inherit(self,TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,"SEAD",TaskBriefing))
+self:F()
+Mission:AddTask(self)
+self:SetBriefing(
+TaskBriefing or
+"Execute a Suppression of Enemy Air Defenses."
+)
+return self
+end
+function TASK_A2G_SEAD:UpdateTaskInfo()
+local TargetCoordinate=self.Detection and self.Detection:GetDetectedItemCoordinate(self.DetectedItemIndex)or self.TargetSetUnit:GetFirst():GetCoordinate()
+self:SetInfo("Coordinate",TargetCoordinate,0)
+local ThreatLevel,ThreatText
+if self.Detection then
+ThreatLevel,ThreatText=self.Detection:GetDetectedItemThreatLevel(self.DetectedItemIndex)
+else
+ThreatLevel,ThreatText=self.TargetSetUnit:CalculateThreatLevelA2G()
+end
+self:SetInfo("Threat",ThreatText.." ["..string.rep("â– ",ThreatLevel).."]",11)
+if self.Detection then
+local DetectedItemsCount=self.TargetSetUnit:Count()
+local ReportTypes=REPORT:New()
+local TargetTypes={}
+for TargetUnitName,TargetUnit in pairs(self.TargetSetUnit:GetSet())do
+local TargetType=self.Detection:GetDetectedUnitTypeName(TargetUnit)
+if not TargetTypes[TargetType]then
+TargetTypes[TargetType]=TargetType
+ReportTypes:Add(TargetType)
+end
+end
+self:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,ReportTypes:Text(", ")),10)
+else
+local DetectedItemsCount=self.TargetSetUnit:Count()
+local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames()
+self:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,DetectedItemsTypes),10)
+end
+end
+function TASK_A2G_SEAD:ReportOrder(ReportGroup)
+local Coordinate=self:GetInfo("Coordinate")
+local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate)
+return Distance
+end
+function TASK_A2G_SEAD:onafterGoal(TaskUnit,From,Event,To)
+local TargetSetUnit=self.TargetSetUnit
+if TargetSetUnit:Count()==0 then
+self:Success()
+end
+self:__Goal(-10)
+end
+function TASK_A2G_SEAD:SetScoreOnProgress(PlayerName,Score,TaskUnit)
+self:F({PlayerName,Score,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has SEADed a target.",Score)
+return self
+end
+function TASK_A2G_SEAD:SetScoreOnSuccess(PlayerName,Score,TaskUnit)
+self:F({PlayerName,Score,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScore("Success","All radar emitting targets have been successfully SEADed!",Score)
+return self
+end
+function TASK_A2G_SEAD:SetScoreOnFail(PlayerName,Penalty,TaskUnit)
+self:F({PlayerName,Penalty,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScore("Failed","The SEADing has failed!",Penalty)
+return self
+end
+end
+do
+TASK_A2G_BAI={
+ClassName="TASK_A2G_BAI",
+}
+function TASK_A2G_BAI:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing)
+local self=BASE:Inherit(self,TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,"BAI",TaskBriefing))
+self:F()
+Mission:AddTask(self)
+self:SetBriefing(
+TaskBriefing or
+"Execute a Battlefield Air Interdiction of a group of enemy targets."
+)
+return self
+end
+function TASK_A2G_BAI:UpdateTaskInfo()
+self:E({self.Detection,self.DetectedItemIndex})
+local TargetCoordinate=self.Detection and self.Detection:GetDetectedItemCoordinate(self.DetectedItemIndex)or self.TargetSetUnit:GetFirst():GetCoordinate()
+self:SetInfo("Coordinate",TargetCoordinate,0)
+local ThreatLevel,ThreatText
+if self.Detection then
+ThreatLevel,ThreatText=self.Detection:GetDetectedItemThreatLevel(self.DetectedItemIndex)
+else
+ThreatLevel,ThreatText=self.TargetSetUnit:CalculateThreatLevelA2G()
+end
+self:SetInfo("Threat",ThreatText.." ["..string.rep("â– ",ThreatLevel).."]",11)
+if self.Detection then
+local DetectedItemsCount=self.TargetSetUnit:Count()
+local ReportTypes=REPORT:New()
+local TargetTypes={}
+for TargetUnitName,TargetUnit in pairs(self.TargetSetUnit:GetSet())do
+local TargetType=self.Detection:GetDetectedUnitTypeName(TargetUnit)
+if not TargetTypes[TargetType]then
+TargetTypes[TargetType]=TargetType
+ReportTypes:Add(TargetType)
+end
+end
+self:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,ReportTypes:Text(", ")),10)
+else
+local DetectedItemsCount=self.TargetSetUnit:Count()
+local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames()
+self:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,DetectedItemsTypes),10)
+end
+local TargetCoordinate=self:GetInfo("Coordinate")
+local Velocity=self.TargetSetUnit:GetVelocity()
+local Heading=self.TargetSetUnit:GetHeading()
+TargetCoordinate:SetHeading(Heading)
+TargetCoordinate:SetVelocity(Velocity)
+self:SetInfo("Position","Targets are"..TargetCoordinate:GetMovingText()..".",12)
+end
+function TASK_A2G_BAI:ReportOrder(ReportGroup)
+local Coordinate=self:GetInfo("Coordinate")
+local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate)
+return Distance
+end
+function TASK_A2G_BAI:onafterGoal(TaskUnit,From,Event,To)
+local TargetSetUnit=self.TargetSetUnit
+if TargetSetUnit:Count()==0 then
+self:Success()
+end
+self:__Goal(-10)
+end
+function TASK_A2G_BAI:SetScoreOnProgress(PlayerName,Score,TaskUnit)
+self:F({PlayerName,Score,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has destroyed a target in Battlefield Air Interdiction (BAI).",Score)
+return self
+end
+function TASK_A2G_BAI:SetScoreOnSuccess(PlayerName,Score,TaskUnit)
+self:F({PlayerName,Score,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScore("Success","All targets have been successfully destroyed! The Battlefield Air Interdiction (BAI) is a success!",Score)
+return self
+end
+function TASK_A2G_BAI:SetScoreOnFail(PlayerName,Penalty,TaskUnit)
+self:F({PlayerName,Penalty,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScore("Failed","The Battlefield Air Interdiction (BAI) has failed!",Penalty)
+return self
+end
+end
+do
+TASK_A2G_CAS={
+ClassName="TASK_A2G_CAS",
+}
+function TASK_A2G_CAS:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing)
+local self=BASE:Inherit(self,TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,"CAS",TaskBriefing))
+self:F()
+Mission:AddTask(self)
+self:SetBriefing(
+TaskBriefing or
+"Execute a Close Air Support for a group of enemy targets. "..
+"Beware of friendlies at the vicinity! "
+)
+return self
+end
+function TASK_A2G_CAS:UpdateTaskInfo()
+local TargetCoordinate=(self.Detection and self.Detection:GetDetectedItemCoordinate(self.DetectedItemIndex))or self.TargetSetUnit:GetFirst():GetCoordinate()
+self:SetInfo("Coordinate",TargetCoordinate,0)
+local ThreatLevel,ThreatText
+if self.Detection then
+ThreatLevel,ThreatText=self.Detection:GetDetectedItemThreatLevel(self.DetectedItemIndex)
+else
+ThreatLevel,ThreatText=self.TargetSetUnit:CalculateThreatLevelA2G()
+end
+self:SetInfo("Threat",ThreatText.." ["..string.rep("â– ",ThreatLevel).."]",11)
+if self.Detection then
+local DetectedItemsCount=self.TargetSetUnit:Count()
+local ReportTypes=REPORT:New()
+local TargetTypes={}
+for TargetUnitName,TargetUnit in pairs(self.TargetSetUnit:GetSet())do
+local TargetType=self.Detection:GetDetectedUnitTypeName(TargetUnit)
+if not TargetTypes[TargetType]then
+TargetTypes[TargetType]=TargetType
+ReportTypes:Add(TargetType)
+end
+end
+self:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,ReportTypes:Text(", ")),10)
+else
+local DetectedItemsCount=self.TargetSetUnit:Count()
+local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames()
+self:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,DetectedItemsTypes),10)
+end
+end
+function TASK_A2G_CAS:ReportOrder(ReportGroup)
+local Coordinate=self:GetInfo("Coordinate")
+local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate)
+return Distance
+end
+function TASK_A2G_CAS:onafterGoal(TaskUnit,From,Event,To)
+local TargetSetUnit=self.TargetSetUnit
+if TargetSetUnit:Count()==0 then
+self:Success()
+end
+self:__Goal(-10)
+end
+function TASK_A2G_CAS:SetScoreOnProgress(PlayerName,Score,TaskUnit)
+self:F({PlayerName,Score,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has destroyed a target in Close Air Support (CAS).",Score)
+return self
+end
+function TASK_A2G_CAS:SetScoreOnSuccess(PlayerName,Score,TaskUnit)
+self:F({PlayerName,Score,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScore("Success","All targets have been successfully destroyed! The Close Air Support (CAS) was a success!",Score)
+return self
+end
+function TASK_A2G_CAS:SetScoreOnFail(PlayerName,Penalty,TaskUnit)
+self:F({PlayerName,Penalty,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScore("Failed","The Close Air Support (CAS) has failed!",Penalty)
+return self
+end
+end
+do
+TASK_A2A_DISPATCHER={
+ClassName="TASK_A2A_DISPATCHER",
+Mission=nil,
+Detection=nil,
+Tasks={},
+SweepZones={},
+}
+function TASK_A2A_DISPATCHER:New(Mission,SetGroup,Detection)
+local self=BASE:Inherit(self,DETECTION_MANAGER:New(SetGroup,Detection))
+self.Detection=Detection
+self.Mission=Mission
+self.Detection:FilterCategories(Unit.Category.AIRPLANE,Unit.Category.HELICOPTER)
+self.Detection:InitDetectRadar(true)
+self.Detection:SetRefreshTimeInterval(30)
+self:AddTransition("Started","Assign","Started")
+self:__Start(5)
+return self
+end
+function TASK_A2A_DISPATCHER:SetEngageRadius(EngageRadius)
+self.Detection:SetFriendliesRange(EngageRadius or 100000)
+return self
+end
+function TASK_A2A_DISPATCHER:EvaluateINTERCEPT(DetectedItem)
+self:F({DetectedItem.ItemID})
+local DetectedSet=DetectedItem.Set
+local DetectedZone=DetectedItem.Zone
+if DetectedItem.IsDetected==true then
+local TargetSetUnit=SET_UNIT:New()
+TargetSetUnit:SetDatabase(DetectedSet)
+TargetSetUnit:FilterOnce()
+return TargetSetUnit
+end
+return nil
+end
+function TASK_A2A_DISPATCHER:EvaluateSWEEP(DetectedItem)
+self:F({DetectedItem.ItemID})
+local DetectedSet=DetectedItem.Set
+local DetectedZone=DetectedItem.Zone
+if DetectedItem.IsDetected==false then
+local TargetSetUnit=SET_UNIT:New()
+TargetSetUnit:SetDatabase(DetectedSet)
+TargetSetUnit:FilterOnce()
+return TargetSetUnit
+end
+return nil
+end
+function TASK_A2A_DISPATCHER:EvaluateENGAGE(DetectedItem)
+self:F({DetectedItem.ItemID})
+local DetectedSet=DetectedItem.Set
+local DetectedZone=DetectedItem.Zone
+local PlayersCount,PlayersReport=self:GetPlayerFriendliesNearBy(DetectedItem)
+if PlayersCount>0 and DetectedItem.IsDetected==true then
+local TargetSetUnit=SET_UNIT:New()
+TargetSetUnit:SetDatabase(DetectedSet)
+TargetSetUnit:FilterOnce()
+return TargetSetUnit
+end
+return nil
+end
+function TASK_A2A_DISPATCHER:EvaluateRemoveTask(Mission,Task,Detection,DetectedItem,DetectedItemIndex,DetectedItemChanged)
+if Task then
+if Task:IsStatePlanned()then
+local TaskName=Task:GetName()
+local TaskType=TaskName:match("(%u+)%.%d+")
+self:T2({TaskType=TaskType})
+local Remove=false
+local IsPlayers=Detection:IsPlayersNearBy(DetectedItem)
+if TaskType=="ENGAGE"then
+if IsPlayers==false then
+Remove=true
+end
+end
+if TaskType=="INTERCEPT"then
+if IsPlayers==true then
+Remove=true
+end
+if DetectedItem.IsDetected==false then
+Remove=true
+end
+end
+if TaskType=="SWEEP"then
+if DetectedItem.IsDetected==true then
+Remove=true
+end
+end
+local DetectedSet=DetectedItem.Set
+if DetectedSet:Count()==0 then
+Remove=true
+end
+if DetectedItemChanged==true or Remove then
+Task=self:RemoveTask(DetectedItemIndex)
+end
+end
+end
+return Task
+end
+function TASK_A2A_DISPATCHER:GetFriendliesNearBy(DetectedItem)
+local DetectedSet=DetectedItem.Set
+local FriendlyUnitsNearBy=self.Detection:GetFriendliesNearBy(DetectedItem)
+local FriendlyTypes={}
+local FriendliesCount=0
+if FriendlyUnitsNearBy then
+local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G()
+for FriendlyUnitName,FriendlyUnitData in pairs(FriendlyUnitsNearBy)do
+local FriendlyUnit=FriendlyUnitData
+if FriendlyUnit:IsAirPlane()then
+local FriendlyUnitThreatLevel=FriendlyUnit:GetThreatLevel()
+FriendliesCount=FriendliesCount+1
+local FriendlyType=FriendlyUnit:GetTypeName()
+FriendlyTypes[FriendlyType]=FriendlyTypes[FriendlyType]and(FriendlyTypes[FriendlyType]+1)or 1
+if DetectedTreatLevel0 then
+for FriendlyType,FriendlyTypeCount in pairs(FriendlyTypes)do
+FriendlyTypesReport:Add(string.format("%d of %s",FriendlyTypeCount,FriendlyType))
+end
+else
+FriendlyTypesReport:Add("-")
+end
+return FriendliesCount,FriendlyTypesReport
+end
+function TASK_A2A_DISPATCHER:GetPlayerFriendliesNearBy(DetectedItem)
+local DetectedSet=DetectedItem.Set
+local PlayersNearBy=self.Detection:GetPlayersNearBy(DetectedItem)
+local PlayerTypes={}
+local PlayersCount=0
+if PlayersNearBy then
+local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G()
+for PlayerUnitName,PlayerUnitData in pairs(PlayersNearBy)do
+local PlayerUnit=PlayerUnitData
+local PlayerName=PlayerUnit:GetPlayerName()
+if PlayerUnit:IsAirPlane()and PlayerName~=nil then
+local FriendlyUnitThreatLevel=PlayerUnit:GetThreatLevel()
+PlayersCount=PlayersCount+1
+local PlayerType=PlayerUnit:GetTypeName()
+PlayerTypes[PlayerName]=PlayerType
+if DetectedTreatLevel0 then
+for PlayerName,PlayerType in pairs(PlayerTypes)do
+PlayerTypesReport:Add(string.format('"%s" in %s',PlayerName,PlayerType))
+end
+else
+PlayerTypesReport:Add("-")
+end
+return PlayersCount,PlayerTypesReport
+end
+function TASK_A2A_DISPATCHER:RemoveTask(TaskIndex)
+self.Mission:RemoveTask(self.Tasks[TaskIndex])
+self.Tasks[TaskIndex]=nil
+end
+function TASK_A2A_DISPATCHER:ProcessDetected(Detection)
+self:E()
+local AreaMsg={}
+local TaskMsg={}
+local ChangeMsg={}
+local Mission=self.Mission
+if Mission:IsIDLE()or Mission:IsENGAGED()then
+local TaskReport=REPORT:New()
+for TaskIndex,TaskData in pairs(self.Tasks)do
+local Task=TaskData
+if Task:IsStatePlanned()then
+local DetectedItem=Detection:GetDetectedItem(TaskIndex)
+if not DetectedItem then
+local TaskText=Task:GetName()
+for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
+Mission:GetCommandCenter():MessageToGroup(string.format("Obsolete A2A task %s for %s removed.",TaskText,Mission:GetName()),TaskGroup)
+end
+Task=self:RemoveTask(TaskIndex)
+end
+end
+end
+for DetectedItemID,DetectedItem in pairs(Detection:GetDetectedItems())do
+local DetectedItem=DetectedItem
+local DetectedSet=DetectedItem.Set
+local DetectedCount=DetectedSet:Count()
+local DetectedZone=DetectedItem.Zone
+local DetectedID=DetectedItem.ID
+local TaskIndex=DetectedItem.Index
+local DetectedItemChanged=DetectedItem.Changed
+local Task=self.Tasks[TaskIndex]
+Task=self:EvaluateRemoveTask(Mission,Task,Detection,DetectedItem,TaskIndex,DetectedItemChanged)
+if not Task and DetectedCount>0 then
+local TargetSetUnit=self:EvaluateENGAGE(DetectedItem)
+if TargetSetUnit then
+Task=TASK_A2A_ENGAGE:New(Mission,self.SetGroup,string.format("ENGAGE.%03d",DetectedID),TargetSetUnit)
+Task:SetDetection(Detection,TaskIndex)
+else
+local TargetSetUnit=self:EvaluateINTERCEPT(DetectedItem)
+if TargetSetUnit then
+Task=TASK_A2A_INTERCEPT:New(Mission,self.SetGroup,string.format("INTERCEPT.%03d",DetectedID),TargetSetUnit)
+Task:SetDetection(Detection,TaskIndex)
+else
+local TargetSetUnit=self:EvaluateSWEEP(DetectedItem)
+if TargetSetUnit then
+Task=TASK_A2A_SWEEP:New(Mission,self.SetGroup,string.format("SWEEP.%03d",DetectedID),TargetSetUnit)
+Task:SetDetection(Detection,TaskIndex)
+end
+end
+end
+if Task then
+self.Tasks[TaskIndex]=Task
+Task:SetTargetZone(DetectedZone,DetectedItem.Coordinate.y,DetectedItem.Coordinate.Heading)
+Task:SetDispatcher(self)
+Mission:AddTask(Task)
+TaskReport:Add(Task:GetName())
+else
+self:E("This should not happen")
+end
+end
+if Task then
+local FriendliesCount,FriendliesReport=self:GetFriendliesNearBy(DetectedItem)
+Task:SetInfo("Friendlies",string.format("%d ( %s )",FriendliesCount,FriendliesReport:Text(",")),30)
+local PlayersCount,PlayersReport=self:GetPlayerFriendliesNearBy(DetectedItem)
+Task:SetInfo("Players",string.format("%d ( %s )",PlayersCount,PlayersReport:Text(",")),31)
+end
+Detection:AcceptChanges(DetectedItem)
+end
+Mission:GetCommandCenter():SetMenu()
+local TaskText=TaskReport:Text(", ")
+for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
+if(not Mission:IsGroupAssigned(TaskGroup))and TaskText~=""then
+Mission:GetCommandCenter():MessageToGroup(string.format("%s has tasks %s. Subscribe to a task using the radio menu.",Mission:GetName(),TaskText),TaskGroup)
+end
+end
+end
+return true
+end
+end
+do
+TASK_A2A={
+ClassName="TASK_A2A",
+}
+function TASK_A2A:New(Mission,SetAttack,TaskName,TargetSetUnit,TaskType,TaskBriefing)
+local self=BASE:Inherit(self,TASK:New(Mission,SetAttack,TaskName,TaskType,TaskBriefing))
+self:F()
+self.TargetSetUnit=TargetSetUnit
+self.TaskType=TaskType
+local Fsm=self:GetUnitProcess()
+Fsm:AddProcess("Planned","Accept",ACT_ASSIGN_ACCEPT:New(self.TaskBriefing),{Assigned="RouteToRendezVous",Rejected="Reject"})
+Fsm:AddTransition("Assigned","RouteToRendezVous","RoutingToRendezVous")
+Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousPoint",ACT_ROUTE_POINT:New(),{Arrived="ArriveAtRendezVous"})
+Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousZone",ACT_ROUTE_ZONE:New(),{Arrived="ArriveAtRendezVous"})
+Fsm:AddTransition({"Arrived","RoutingToRendezVous"},"ArriveAtRendezVous","ArrivedAtRendezVous")
+Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"Engage","Engaging")
+Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"HoldAtRendezVous","HoldingAtRendezVous")
+Fsm:AddProcess("Engaging","Account",ACT_ACCOUNT_DEADS:New(),{})
+Fsm:AddTransition("Engaging","RouteToTarget","Engaging")
+Fsm:AddProcess("Engaging","RouteToTargetZone",ACT_ROUTE_ZONE:New(),{})
+Fsm:AddProcess("Engaging","RouteToTargetPoint",ACT_ROUTE_POINT:New(),{})
+Fsm:AddTransition("Engaging","RouteToTargets","Engaging")
+Fsm:AddTransition("Rejected","Reject","Aborted")
+Fsm:AddTransition("Failed","Fail","Failed")
+function Fsm:onafterRouteToRendezVous(TaskUnit,Task)
+self:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
+if Task:GetRendezVousZone(TaskUnit)then
+self:__RouteToRendezVousZone(0.1)
+else
+if Task:GetRendezVousCoordinate(TaskUnit)then
+self:__RouteToRendezVousPoint(0.1)
+else
+self:__ArriveAtRendezVous(0.1)
+end
+end
+end
+function Fsm:OnAfterArriveAtRendezVous(TaskUnit,Task)
+self:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
+self:__Engage(0.1)
+end
+function Fsm:onafterEngage(TaskUnit,Task)
+self:E({self})
+self:__Account(0.1)
+self:__RouteToTarget(0.1)
+self:__RouteToTargets(-10)
+end
+function Fsm:onafterRouteToTarget(TaskUnit,Task)
+self:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
+if Task:GetTargetZone(TaskUnit)then
+self:__RouteToTargetZone(0.1)
+else
+local TargetUnit=Task.TargetSetUnit:GetFirst()
+if TargetUnit then
+local Coordinate=TargetUnit:GetCoordinate()
+self:T({TargetCoordinate=Coordinate,Coordinate:GetX(),Coordinate:GetAlt(),Coordinate:GetZ()})
+Task:SetTargetCoordinate(TargetUnit:GetCoordinate(),TaskUnit)
+end
+self:__RouteToTargetPoint(0.1)
+end
+end
+function Fsm:onafterRouteToTargets(TaskUnit,Task)
+self:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
+local TargetUnit=Task.TargetSetUnit:GetFirst()
+if TargetUnit then
+Task:SetTargetCoordinate(TargetUnit:GetCoordinate(),TaskUnit)
+end
+self:__RouteToTargets(-10)
+end
+return self
+end
+function TASK_A2A:GetPlannedMenuText()
+return self:GetStateString().." - "..self:GetTaskName().." ( "..self.TargetSetUnit:GetUnitTypesText().." )"
+end
+function TASK_A2A:SetRendezVousCoordinate(RendezVousCoordinate,RendezVousRange,TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint")
+ActRouteRendezVous:SetCoordinate(RendezVousCoordinate)
+ActRouteRendezVous:SetRange(RendezVousRange)
+end
+function TASK_A2A:GetRendezVousCoordinate(TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint")
+return ActRouteRendezVous:GetCoordinate(),ActRouteRendezVous:GetRange()
+end
+function TASK_A2A:SetRendezVousZone(RendezVousZone,TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone")
+ActRouteRendezVous:SetZone(RendezVousZone)
+end
+function TASK_A2A:GetRendezVousZone(TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone")
+return ActRouteRendezVous:GetZone()
+end
+function TASK_A2A:SetTargetCoordinate(TargetCoordinate,TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint")
+ActRouteTarget:SetCoordinate(TargetCoordinate)
+end
+function TASK_A2A:GetTargetCoordinate(TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint")
+return ActRouteTarget:GetCoordinate()
+end
+function TASK_A2A:SetTargetZone(TargetZone,Altitude,Heading,TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone")
+ActRouteTarget:SetZone(TargetZone,Altitude,Heading)
+end
+function TASK_A2A:GetTargetZone(TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone")
+return ActRouteTarget:GetZone()
+end
+function TASK_A2A:SetGoalTotal()
+self.GoalTotal=self.TargetSetUnit:Count()
+end
+function TASK_A2A:GetGoalTotal()
+return self.GoalTotal
+end
+function TASK_A2A:GetMarkInfo(TaskInfoID,TaskInfo)
+if type(TaskInfo.TaskInfoText)=="string"then
+if TaskInfoID=="Targets"then
+else
+return string.format("%s: %s",TaskInfoID,TaskInfo.TaskInfoText)
+end
+elseif type(TaskInfo)=="table"then
+if TaskInfoID=="Coordinate"then
+end
+end
+return nil
+end
+function TASK_A2A:GetReportDetail(ReportGroup,TaskInfoID,TaskInfo)
+if type(TaskInfo.TaskInfoText)=="string"then
+return string.format("%s: %s",TaskInfoID,TaskInfo.TaskInfoText)
+elseif type(TaskInfo)=="table"then
+if TaskInfoID=="Coordinate"then
+local FromCoordinate=ReportGroup:GetUnit(1):GetCoordinate()
+local ToCoordinate=TaskInfo.TaskInfoText
+return string.format(" - %s: %s",TaskInfoID,ToCoordinate:ToString(ReportGroup:GetUnit(1),nil,self))
+else
+end
+end
+end
+end
+do
+TASK_A2A_INTERCEPT={
+ClassName="TASK_A2A_INTERCEPT",
+}
+function TASK_A2A_INTERCEPT:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing)
+local self=BASE:Inherit(self,TASK_A2A:New(Mission,SetGroup,TaskName,TargetSetUnit,"INTERCEPT",TaskBriefing))
+self:F()
+Mission:AddTask(self)
+self:SetBriefing(
+TaskBriefing or
+"Intercept incoming intruders.\n"
+)
+self:UpdateTaskInfo()
+return self
+end
+function TASK_A2A_INTERCEPT:UpdateTaskInfo()
+local TargetCoordinate=self.Detection and self.Detection:GetDetectedItemCoordinate(self.DetectedItemIndex)or self.TargetSetUnit:GetFirst():GetCoordinate()
+self:SetInfo("Coordinate",TargetCoordinate,0)
+self:SetInfo("Threat","["..string.rep("â– ",self.Detection and self.Detection:GetDetectedItemThreatLevel(self.DetectedItemIndex)or self.TargetSetUnit:CalculateThreatLevelA2G()).."]",11)
+if self.Detection then
+local DetectedItemsCount=self.TargetSetUnit:Count()
+local ReportTypes=REPORT:New()
+local TargetTypes={}
+for TargetUnitName,TargetUnit in pairs(self.TargetSetUnit:GetSet())do
+local TargetType=self.Detection:GetDetectedUnitTypeName(TargetUnit)
+if not TargetTypes[TargetType]then
+TargetTypes[TargetType]=TargetType
+ReportTypes:Add(TargetType)
+end
+end
+self:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,ReportTypes:Text(", ")),10)
+else
+local DetectedItemsCount=self.TargetSetUnit:Count()
+local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames()
+self:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,DetectedItemsTypes),10)
+end
+end
+function TASK_A2A_INTERCEPT:ReportOrder(ReportGroup)
+self:F({TaskInfo=self.TaskInfo})
+local Coordinate=self.TaskInfo.Coordinates.TaskInfoText
+local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate)
+return Distance
+end
+function TASK_A2A_INTERCEPT:onafterGoal(TaskUnit,From,Event,To)
+local TargetSetUnit=self.TargetSetUnit
+if TargetSetUnit:Count()==0 then
+self:Success()
+end
+self:__Goal(-10)
+end
+function TASK_A2A_INTERCEPT:SetScoreOnProgress(PlayerName,Score,TaskUnit)
+self:F({PlayerName,Score,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has intercepted a target.",Score)
+return self
+end
+function TASK_A2A_INTERCEPT:SetScoreOnSuccess(PlayerName,Score,TaskUnit)
+self:F({PlayerName,Score,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScore("Success","All targets have been successfully intercepted!",Score)
+return self
+end
+function TASK_A2A_INTERCEPT:SetScoreOnFail(PlayerName,Penalty,TaskUnit)
+self:F({PlayerName,Penalty,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScore("Failed","The intercept has failed!",Penalty)
+return self
+end
+end
+do
+TASK_A2A_SWEEP={
+ClassName="TASK_A2A_SWEEP",
+}
+function TASK_A2A_SWEEP:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing)
+local self=BASE:Inherit(self,TASK_A2A:New(Mission,SetGroup,TaskName,TargetSetUnit,"SWEEP",TaskBriefing))
+self:F()
+Mission:AddTask(self)
+self:SetBriefing(
+TaskBriefing or
+"Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n"
+)
+self:UpdateTaskInfo()
+return self
+end
+function TASK_A2A_SWEEP:UpdateTaskInfo()
+local TargetCoordinate=self.Detection and self.Detection:GetDetectedItemCoordinate(self.DetectedItemIndex)or self.TargetSetUnit:GetFirst():GetCoordinate()
+self:SetInfo("Coordinate",TargetCoordinate,0)
+self:SetInfo("Assumed Threat","["..string.rep("â– ",self.Detection and self.Detection:GetDetectedItemThreatLevel(self.DetectedItemIndex)or self.TargetSetUnit:CalculateThreatLevelA2G()).."]",11)
+if self.Detection then
+local DetectedItemsCount=self.TargetSetUnit:Count()
+local ReportTypes=REPORT:New()
+local TargetTypes={}
+for TargetUnitName,TargetUnit in pairs(self.TargetSetUnit:GetSet())do
+local TargetType=self.Detection:GetDetectedUnitTypeName(TargetUnit)
+if not TargetTypes[TargetType]then
+TargetTypes[TargetType]=TargetType
+ReportTypes:Add(TargetType)
+end
+end
+self:SetInfo("Lost Targets",string.format("%d of %s",DetectedItemsCount,ReportTypes:Text(", ")),10)
+else
+local DetectedItemsCount=self.TargetSetUnit:Count()
+local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames()
+self:SetInfo("Lost Targets",string.format("%d of %s",DetectedItemsCount,DetectedItemsTypes),10)
+end
+end
+function TASK_A2A_SWEEP:ReportOrder(ReportGroup)
+local Coordinate=self.TaskInfo.Coordinates.TaskInfoText
+local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate)
+return Distance
+end
+function TASK_A2A_SWEEP:onafterGoal(TaskUnit,From,Event,To)
+local TargetSetUnit=self.TargetSetUnit
+if TargetSetUnit:Count()==0 then
+self:Success()
+end
+self:__Goal(-10)
+end
+function TASK_A2A_SWEEP:SetScoreOnProgress(PlayerName,Score,TaskUnit)
+self:F({PlayerName,Score,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has sweeped a target.",Score)
+return self
+end
+function TASK_A2A_SWEEP:SetScoreOnSuccess(PlayerName,Score,TaskUnit)
+self:F({PlayerName,Score,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScore("Success","All targets have been successfully sweeped!",Score)
+return self
+end
+function TASK_A2A_SWEEP:SetScoreOnFail(PlayerName,Penalty,TaskUnit)
+self:F({PlayerName,Penalty,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScore("Failed","The sweep has failed!",Penalty)
+return self
+end
+end
+do
+TASK_A2A_ENGAGE={
+ClassName="TASK_A2A_ENGAGE",
+}
+function TASK_A2A_ENGAGE:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing)
+local self=BASE:Inherit(self,TASK_A2A:New(Mission,SetGroup,TaskName,TargetSetUnit,"ENGAGE",TaskBriefing))
+self:F()
+Mission:AddTask(self)
+self:SetBriefing(
+TaskBriefing or
+"Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n"
+)
+self:UpdateTaskInfo()
+return self
+end
+function TASK_A2A_ENGAGE:UpdateTaskInfo()
+local TargetCoordinate=self.Detection and self.Detection:GetDetectedItemCoordinate(self.DetectedItemIndex)or self.TargetSetUnit:GetFirst():GetCoordinate()
+self:SetInfo("Coordinate",TargetCoordinate,0)
+self:SetInfo("Threat","["..string.rep("â– ",self.Detection and self.Detection:GetDetectedItemThreatLevel(self.DetectedItemIndex)or self.TargetSetUnit:CalculateThreatLevelA2G()).."]",11)
+if self.Detection then
+local DetectedItemsCount=self.TargetSetUnit:Count()
+local ReportTypes=REPORT:New()
+local TargetTypes={}
+for TargetUnitName,TargetUnit in pairs(self.TargetSetUnit:GetSet())do
+local TargetType=self.Detection:GetDetectedUnitTypeName(TargetUnit)
+if not TargetTypes[TargetType]then
+TargetTypes[TargetType]=TargetType
+ReportTypes:Add(TargetType)
+end
+end
+self:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,ReportTypes:Text(", ")),10)
+else
+local DetectedItemsCount=self.TargetSetUnit:Count()
+local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames()
+self:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,DetectedItemsTypes),10)
+end
+end
+function TASK_A2A_ENGAGE:ReportOrder(ReportGroup)
+local Coordinate=self.TaskInfo.Coordinates.TaskInfoText
+local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate)
+return Distance
+end
+function TASK_A2A_ENGAGE:onafterGoal(TaskUnit,From,Event,To)
+local TargetSetUnit=self.TargetSetUnit
+if TargetSetUnit:Count()==0 then
+self:Success()
+end
+self:__Goal(-10)
+end
+function TASK_A2A_ENGAGE:SetScoreOnProgress(PlayerName,Score,TaskUnit)
+self:F({PlayerName,Score,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has engaged and destroyed a target.",Score)
+return self
+end
+function TASK_A2A_ENGAGE:SetScoreOnSuccess(PlayerName,Score,TaskUnit)
+self:F({PlayerName,Score,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScore("Success","All targets have been successfully engaged!",Score)
+return self
+end
+function TASK_A2A_ENGAGE:SetScoreOnFail(PlayerName,Penalty,TaskUnit)
+self:F({PlayerName,Penalty,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScore("Failed","The target engagement has failed!",Penalty)
+return self
+end
+end
+do
+TASK_CARGO={
+ClassName="TASK_CARGO",
+}
+function TASK_CARGO:New(Mission,SetGroup,TaskName,SetCargo,TaskType,TaskBriefing)
+local self=BASE:Inherit(self,TASK:New(Mission,SetGroup,TaskName,TaskType,TaskBriefing))
+self:F({Mission,SetGroup,TaskName,SetCargo,TaskType})
+self.SetCargo=SetCargo
+self.TaskType=TaskType
+self.SmokeColor=SMOKECOLOR.Red
+self.CargoItemCount={}
+self.CargoLimit=2
+self.DeployZones={}
+local Fsm=self:GetUnitProcess()
+Fsm:SetStartState("Planned")
+Fsm:AddProcess("Planned","Accept",ACT_ASSIGN_ACCEPT:New(self.TaskBriefing),{Assigned="SelectAction",Rejected="Reject"})
+Fsm:AddTransition({"Assigned","WaitingForCommand","ArrivedAtPickup","ArrivedAtDeploy","Boarded","UnBoarded","Landed","Boarding"},"SelectAction","*")
+Fsm:AddTransition("*","RouteToPickup","RoutingToPickup")
+Fsm:AddProcess("RoutingToPickup","RouteToPickupPoint",ACT_ROUTE_POINT:New(),{Arrived="ArriveAtPickup",Cancelled="CancelRouteToPickup"})
+Fsm:AddTransition("Arrived","ArriveAtPickup","ArrivedAtPickup")
+Fsm:AddTransition("Cancelled","CancelRouteToPickup","WaitingForCommand")
+Fsm:AddTransition("*","RouteToDeploy","RoutingToDeploy")
+Fsm:AddProcess("RoutingToDeploy","RouteToDeployZone",ACT_ROUTE_ZONE:New(),{Arrived="ArriveAtDeploy",Cancelled="CancelRouteToDeploy"})
+Fsm:AddTransition("Arrived","ArriveAtDeploy","ArrivedAtDeploy")
+Fsm:AddTransition("Cancelled","CancelRouteToDeploy","WaitingForCommand")
+Fsm:AddTransition({"ArrivedAtPickup","ArrivedAtDeploy","Landing"},"Land","Landing")
+Fsm:AddTransition("Landing","Landed","Landed")
+Fsm:AddTransition("*","PrepareBoarding","AwaitBoarding")
+Fsm:AddTransition("AwaitBoarding","Board","Boarding")
+Fsm:AddTransition("Boarding","Boarded","Boarded")
+Fsm:AddTransition("*","PrepareUnBoarding","AwaitUnBoarding")
+Fsm:AddTransition("AwaitUnBoarding","UnBoard","UnBoarding")
+Fsm:AddTransition("UnBoarding","UnBoarded","UnBoarded")
+Fsm:AddTransition("Deployed","Success","Success")
+Fsm:AddTransition("Rejected","Reject","Aborted")
+Fsm:AddTransition("Failed","Fail","Failed")
+function Fsm:onafterSelectAction(TaskUnit,Task)
+local TaskUnitName=TaskUnit:GetName()
+self:E({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()})
+local MenuTime=timer.getTime()
+TaskUnit.Menu=MENU_GROUP:New(TaskUnit:GetGroup(),Task:GetName().." @ "..TaskUnit:GetName()):SetTime(MenuTime)
+local CargoItemCount=TaskUnit:CargoItemCount()
+Task.SetCargo:ForEachCargo(
+function(Cargo)
+if Cargo:IsAlive()then
+if Cargo:IsUnLoaded()then
+if CargoItemCount0 and SmokeColor<=5 then
+self.SmokeColor=SMOKECOLOR.SmokeColor
+end
+end
+end
+function TASK_CARGO:GetSmokeColor()
+return self.SmokeColor
+end
+function TASK_CARGO:GetPlannedMenuText()
+return self:GetStateString().." - "..self:GetTaskName().." ( "..self.TargetSetUnit:GetUnitTypesText().." )"
+end
+function TASK_CARGO:GetCargoSet()
+return self.SetCargo
+end
+function TASK_CARGO:GetDeployZones()
+return self.DeployZones
+end
+function TASK_CARGO:SetCargoPickup(Cargo,TaskUnit)
+self:F({Cargo,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteCargo=ProcessUnit:GetProcess("RoutingToPickup","RouteToPickupPoint")
+ActRouteCargo:Reset()
+ActRouteCargo:SetCoordinate(Cargo:GetCoordinate())
+ActRouteCargo:SetRange(Cargo:GetBoardingRange())
+ActRouteCargo:SetMenuCancel(TaskUnit:GetGroup(),"Cancel Routing to Cargo "..Cargo:GetName(),TaskUnit.Menu)
+ActRouteCargo:Start()
+return self
+end
+function TASK_CARGO:SetDeployZone(DeployZone,TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteDeployZone=ProcessUnit:GetProcess("RoutingToDeploy","RouteToDeployZone")
+ActRouteDeployZone:Reset()
+ActRouteDeployZone:SetZone(DeployZone)
+ActRouteDeployZone:SetMenuCancel(TaskUnit:GetGroup(),"Cancel Routing to Deploy Zone"..DeployZone:GetName(),TaskUnit.Menu)
+ActRouteDeployZone:Start()
+return self
+end
+function TASK_CARGO:AddDeployZone(DeployZone,TaskUnit)
+self.DeployZones[DeployZone:GetName()]=DeployZone
+return self
+end
+function TASK_CARGO:RemoveDeployZone(DeployZone,TaskUnit)
+self.DeployZones[DeployZone:GetName()]=nil
+return self
+end
+function TASK_CARGO:SetDeployZones(DeployZones,TaskUnit)
+for DeployZoneID,DeployZone in pairs(DeployZones)do
+self.DeployZones[DeployZone:GetName()]=DeployZone
+end
+return self
+end
+function TASK_CARGO:GetTargetZone(TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone")
+return ActRouteTarget:GetZone()
+end
+function TASK_CARGO:SetScoreOnProgress(Text,Score,TaskUnit)
+self:F({Text,Score,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScoreProcess("Engaging","Account","Account",Text,Score)
+return self
+end
+function TASK_CARGO:SetScoreOnSuccess(Text,Score,TaskUnit)
+self:F({Text,Score,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScore("Success",Text,Score)
+return self
+end
+function TASK_CARGO:SetScoreOnFail(Text,Penalty,TaskUnit)
+self:F({Text,Score,TaskUnit})
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+ProcessUnit:AddScore("Failed",Text,Penalty)
+return self
+end
+function TASK_CARGO:SetGoalTotal()
+self.GoalTotal=self.SetCargo:Count()
+end
+function TASK_CARGO:GetGoalTotal()
+return self.GoalTotal
+end
+end
+do
+TASK_CARGO_TRANSPORT={
+ClassName="TASK_CARGO_TRANSPORT",
+}
+function TASK_CARGO_TRANSPORT:New(Mission,SetGroup,TaskName,SetCargo,TaskBriefing)
+local self=BASE:Inherit(self,TASK_CARGO:New(Mission,SetGroup,TaskName,SetCargo,"Transport",TaskBriefing))
+self:F()
+Mission:AddTask(self)
+self:AddTransition("*","CargoPickedUp","*")
+self:AddTransition("*","CargoDeployed","*")
+self:E({CargoDeployed=self.CargoDeployed~=nil and"true"or"false"})
+local Fsm=self:GetUnitProcess()
+local CargoReport=REPORT:New("Transport Cargo. The following cargo needs to be transported including initial positions:")
+SetCargo:ForEachCargo(
+function(Cargo)
+local CargoType=Cargo:GetType()
+local CargoName=Cargo:GetName()
+local CargoCoordinate=Cargo:GetCoordinate()
+CargoReport:Add(string.format('- "%s" (%s) at %s',CargoName,CargoType,CargoCoordinate:ToStringMGRS()))
+end
+)
+self:SetBriefing(
+TaskBriefing or
+CargoReport:Text()
+)
+return self
+end
+function TASK_CARGO_TRANSPORT:ReportOrder(ReportGroup)
+return true
+end
+function TASK_CARGO_TRANSPORT:IsAllCargoTransported()
+local CargoSet=self:GetCargoSet()
+local Set=CargoSet:GetSet()
+local DeployZones=self:GetDeployZones()
+local CargoDeployed=true
+for CargoID,CargoData in pairs(Set)do
+local Cargo=CargoData
+if Cargo:IsDeployed()then
+for DeployZoneID,DeployZone in pairs(DeployZones)do
+self:T({Cargo.CargoObject})
+if Cargo:IsInZone(DeployZone)==false then
+CargoDeployed=false
+end
+end
+else
+CargoDeployed=false
+end
+end
+return CargoDeployed
+end
+function TASK_CARGO_TRANSPORT:onafterGoal(TaskUnit,From,Event,To)
+local CargoSet=self.CargoSet
+if self:IsAllCargoTransported()then
+self:Success()
+end
+self:__Goal(-10)
+end
+end
+do
+TASK_ZONE_GOAL={
+ClassName="TASK_ZONE_GOAL",
+}
+function TASK_ZONE_GOAL:New(Mission,SetGroup,TaskName,ZoneGoal,TaskType,TaskBriefing)
+local self=BASE:Inherit(self,TASK:New(Mission,SetGroup,TaskName,TaskType,TaskBriefing))
+self:F()
+self.ZoneGoal=ZoneGoal
+self.TaskType=TaskType
+local Fsm=self:GetUnitProcess()
+Fsm:AddProcess("Planned","Accept",ACT_ASSIGN_ACCEPT:New(self.TaskBriefing),{Assigned="StartMonitoring",Rejected="Reject"})
+Fsm:AddTransition("Assigned","StartMonitoring","Monitoring")
+Fsm:AddTransition("Monitoring","Monitor","Monitoring",{})
+Fsm:AddTransition("Monitoring","RouteTo","Monitoring")
+Fsm:AddProcess("Monitoring","RouteToZone",ACT_ROUTE_ZONE:New(),{})
+Fsm:AddTransition("Rejected","Reject","Aborted")
+Fsm:AddTransition("Failed","Fail","Failed")
+self:SetTargetZone(self.ZoneGoal:GetZone())
+function Fsm:onafterStartMonitoring(TaskUnit,Task)
+self:E({self})
+self:__Monitor(0.1)
+self:__RouteTo(0.1)
+end
+function Fsm:onafterMonitor(TaskUnit,Task)
+self:E({self})
+self:__Monitor(15)
+end
+function Fsm:onafterRouteTo(TaskUnit,Task)
+self:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
+if Task:GetTargetZone(TaskUnit)then
+self:__RouteTo(0.1)
+end
+end
+return self
+end
+function TASK_ZONE_GOAL:SetProtect(ZoneGoal)
+self.ZoneGoal=ZoneGoal
+end
+function TASK_ZONE_GOAL:GetPlannedMenuText()
+return self:GetStateString().." - "..self:GetTaskName().." ( "..self.ZoneGoal:GetZoneName().." )"
+end
+function TASK_ZONE_GOAL:SetTargetZone(TargetZone,TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteZone=ProcessUnit:GetProcess("Monitoring","RouteToZone")
+ActRouteZone:SetZone(TargetZone)
+end
+function TASK_ZONE_GOAL:GetTargetZone(TaskUnit)
+local ProcessUnit=self:GetUnitProcess(TaskUnit)
+local ActRouteZone=ProcessUnit:GetProcess("Monitoring","RouteToZone")
+return ActRouteZone:GetZone()
+end
+function TASK_ZONE_GOAL:SetGoalTotal(GoalTotal)
+self.GoalTotal=GoalTotal
+end
+function TASK_ZONE_GOAL:GetGoalTotal()
+return self.GoalTotal
+end
+function TASK_ZONE_GOAL:GetMarkInfo(TaskInfoID,TaskInfo)
+if type(TaskInfo.TaskInfoText)=="string"then
+return string.format("%s: %s",TaskInfoID,TaskInfo.TaskInfoText)
+elseif type(TaskInfo)=="table"then
+if TaskInfoID=="Coordinate"then
+end
+end
+return nil
+end
+function TASK_ZONE_GOAL:GetReportDetail(ReportGroup,TaskInfoID,TaskInfo)
+if type(TaskInfo.TaskInfoText)=="string"then
+return string.format(" - %s: %s",TaskInfoID,TaskInfo.TaskInfoText)
+elseif type(TaskInfo)=="table"then
+if TaskInfoID=="Coordinate"then
+local FromCoordinate=ReportGroup:GetUnit(1):GetCoordinate()
+local ToCoordinate=TaskInfo.TaskInfoText
+return string.format(" - %s: %s",TaskInfoID,ToCoordinate:ToString(ReportGroup:GetUnit(1),nil,self))
+else
+end
+end
+end
+end
+do
+TASK_ZONE_CAPTURE={
+ClassName="TASK_ZONE_CAPTURE",
+}
+function TASK_ZONE_CAPTURE:New(Mission,SetGroup,TaskName,ZoneGoalCoalition,TaskBriefing)
+local self=BASE:Inherit(self,TASK_ZONE_GOAL:New(Mission,SetGroup,TaskName,ZoneGoalCoalition,"CAPTURE",TaskBriefing))
+self:F()
+Mission:AddTask(self)
+self.TaskCoalition=ZoneGoalCoalition:GetCoalition()
+self.TaskCoalitionName=ZoneGoalCoalition:GetCoalitionName()
+self.TaskZoneName=ZoneGoalCoalition:GetZoneName()
+ZoneGoalCoalition:MonitorDestroyedUnits()
+self:SetBriefing(
+TaskBriefing or
+"Capture Zone "..self.TaskZoneName
+)
+self:UpdateTaskInfo()
+return self
+end
+function TASK_ZONE_CAPTURE:UpdateTaskInfo()
+local ZoneCoordinate=self.ZoneGoal:GetZone():GetCoordinate()
+self:SetInfo("Coordinate",ZoneCoordinate,0)
+self:SetInfo("Zone Name",self.ZoneGoal:GetZoneName(),10)
+self:SetInfo("Zone Coalition",self.ZoneGoal:GetCoalitionName(),11)
+end
+function TASK_ZONE_CAPTURE:ReportOrder(ReportGroup)
+local Coordinate=self:GetInfo("Coordinate")
+local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate)
+return Distance
+end
+function TASK_ZONE_CAPTURE:OnAfterGoal(From,Event,To,PlayerUnit,PlayerName)
+self:E({PlayerUnit=PlayerUnit})
+if self.ZoneGoal then
+if self.ZoneGoal.Goal:IsAchieved()then
+self:Success()
+local TotalContributions=self.ZoneGoal.Goal:GetTotalContributions()
+local PlayerContributions=self.ZoneGoal.Goal:GetPlayerContributions()
+self:E({TotalContributions=TotalContributions,PlayerContributions=PlayerContributions})
+for PlayerName,PlayerContribution in pairs(PlayerContributions)do
+local Scoring=self:GetScoring()
+if Scoring then
+Scoring:_AddMissionGoalScore(self.Mission,PlayerName,"Zone "..self.ZoneGoal:GetZoneName().." captured",PlayerContribution*200/TotalContributions)
+end
+end
+end
+end
+self:__Goal(-10,PlayerUnit,PlayerName)
+end
+end
+_EVENTDISPATCHER=EVENT:New()
+_SCHEDULEDISPATCHER=SCHEDULEDISPATCHER:New()
+_DATABASE=DATABASE:New()
+_SETTINGS=SETTINGS:Set()
+BASE:TraceOnOff(false)
env.info('*** MOOSE INCLUDE END *** ')
diff --git a/docs/Documentation/AI_A2A.html b/docs/Documentation/AI_A2A.html
index 80b5691bd..090470fc4 100644
--- a/docs/Documentation/AI_A2A.html
+++ b/docs/Documentation/AI_A2A.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/AI_A2A_Cap.html b/docs/Documentation/AI_A2A_Cap.html
index 9ac845f20..1c0934009 100644
--- a/docs/Documentation/AI_A2A_Cap.html
+++ b/docs/Documentation/AI_A2A_Cap.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
@@ -112,7 +118,7 @@
-AI CAP classes makes AI Controllables execute a Combat Air Patrol.
+AI CAP classes makes AI Groups execute a Combat Air Patrol.
@@ -146,7 +152,7 @@
-The AIA2A CAP class implements the core functions to patrol a Zone by an AI Controllable or Group
+
The AIA2A CAP class implements the core functions to patrol a Zone by an AI Group or Group
and automatically engage any airborne enemies that are within a certain range or within a certain zone.
@@ -172,7 +178,7 @@ and automatically engage any airborne enemies that are within a certain range or
- AI_A2A_CAP.AttackRoute(AIGroup, Fsm)
+ AI_A2A_CAP.AttackRoute(AICap, Fsm)
@@ -232,73 +238,73 @@ and automatically engage any airborne enemies that are within a certain range or
- AI_A2A_CAP:New(AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType)
+ AI_A2A_CAP:New(AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType)
Creates a new AIA2A CAP object
- AI_A2A_CAP:OnAfterAbort(Controllable, From, Event, To)
+ AI_A2A_CAP:OnAfterAbort(AICap, From, Event, To)
OnAfter Transition Handler for Event Abort.
- AI_A2A_CAP:OnAfterAccomplish(Controllable, From, Event, To)
+ AI_A2A_CAP:OnAfterAccomplish(AICap, From, Event, To)
OnAfter Transition Handler for Event Accomplish.
- AI_A2A_CAP:OnAfterDestroy(Controllable, From, Event, To)
+ AI_A2A_CAP:OnAfterDestroy(AICap, From, Event, To)
OnAfter Transition Handler for Event Destroy.
- AI_A2A_CAP:OnAfterEngage(Controllable, From, Event, To)
+ AI_A2A_CAP:OnAfterEngage(AICap, From, Event, To)
OnAfter Transition Handler for Event Engage.
- AI_A2A_CAP:OnAfterFired(Controllable, From, Event, To)
+ AI_A2A_CAP:OnAfterFired(AICap, From, Event, To)
OnAfter Transition Handler for Event Fired.
- AI_A2A_CAP:OnBeforeAbort(Controllable, From, Event, To)
+ AI_A2A_CAP:OnBeforeAbort(AICap, From, Event, To)
OnBefore Transition Handler for Event Abort.
- AI_A2A_CAP:OnBeforeAccomplish(Controllable, From, Event, To)
+ AI_A2A_CAP:OnBeforeAccomplish(AICap, From, Event, To)
OnBefore Transition Handler for Event Accomplish.
- AI_A2A_CAP:OnBeforeDestroy(Controllable, From, Event, To)
+ AI_A2A_CAP:OnBeforeDestroy(AICap, From, Event, To)
OnBefore Transition Handler for Event Destroy.
- AI_A2A_CAP:OnBeforeEngage(Controllable, From, Event, To)
+ AI_A2A_CAP:OnBeforeEngage(AICap, From, Event, To)
OnBefore Transition Handler for Event Engage.
- AI_A2A_CAP:OnBeforeFired(Controllable, From, Event, To)
+ AI_A2A_CAP:OnBeforeFired(AICap, From, Event, To)
OnBefore Transition Handler for Event Fired.
- AI_A2A_CAP:OnEnterEngaging(Controllable, From, Event, To)
+ AI_A2A_CAP:OnEnterEngaging(AICap, From, Event, To)
OnEnter Transition Handler for State Engaging.
@@ -310,13 +316,13 @@ and automatically engage any airborne enemies that are within a certain range or
- AI_A2A_CAP:OnLeaveEngaging(Controllable, From, Event, To)
+ AI_A2A_CAP:OnLeaveEngaging(AICap, From, Event, To)
OnLeave Transition Handler for State Engaging.
- AI_A2A_CAP.Resume(AIGroup)
+ AI_A2A_CAP.Resume(AICap)
@@ -364,43 +370,43 @@ and automatically engage any airborne enemies that are within a certain range or
- AI_A2A_CAP:onafterAbort(AIGroup, From, Event, To)
+ AI_A2A_CAP:onafterAbort(AICap, From, Event, To)
- AI_A2A_CAP:onafterAccomplish(Controllable, From, Event, To)
+ AI_A2A_CAP:onafterAccomplish(AICap, From, Event, To)
- AI_A2A_CAP:onafterDestroy(Controllable, From, Event, To, EventData)
+ AI_A2A_CAP:onafterDestroy(AICap, From, Event, To, EventData)
- AI_A2A_CAP:onafterEngage(AIGroup, From, Event, To, AttackSetUnit)
+ AI_A2A_CAP:onafterEngage(AICap, From, Event, To, AttackSetUnit)
- AI_A2A_CAP:onafterPatrol(AIGroup, From, Event, To)
+ AI_A2A_CAP:onafterPatrol(AICap, From, Event, To)
onafter State Transition for Event Patrol.
- AI_A2A_CAP:onafterStart(AIGroup, From, Event, To)
+ AI_A2A_CAP:onafterStart(AICap, From, Event, To)
onafter State Transition for Event Patrol.
- AI_A2A_CAP:onbeforeEngage(AIGroup, From, Event, To)
+ AI_A2A_CAP:onbeforeEngage(AICap, From, Event, To)
@@ -420,7 +426,7 @@ and automatically engage any airborne enemies that are within a certain range or
-The AIA2A CAP class implements the core functions to patrol a Zone by an AI Controllable or Group
+
The AIA2A CAP class implements the core functions to patrol a Zone by an AI Group or Group
and automatically engage any airborne enemies that are within a certain range or within a certain zone.
@@ -560,7 +566,7 @@ Use the method AICap#AI
-AI_A2A_CAP.AttackRoute(AIGroup, Fsm)
+AI_A2A_CAP.AttackRoute(AICap, Fsm)
@@ -571,7 +577,7 @@ Use the method AICap#AI
-Wrapper.Group#GROUP AIGroup :
+Wrapper.Group#GROUP AICap :
@@ -709,7 +715,7 @@ Use the method AICap#AI
-AI_A2A_CAP:New(AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType)
+AI_A2A_CAP:New(AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType)
@@ -720,7 +726,7 @@ Use the method AICap#AI
-Wrapper.Group#GROUP AIGroup :
+Wrapper.Group#GROUP AICap :
@@ -744,25 +750,25 @@ The highest altitude in meters where to execute the patrol.
Dcs.DCSTypes#Speed PatrolMinSpeed :
-The minimum speed of the Controllable in km/h.
+The minimum speed of the Group in km/h.
Dcs.DCSTypes#Speed PatrolMaxSpeed :
-The maximum speed of the Controllable in km/h.
+The maximum speed of the Group in km/h.
Dcs.DCSTypes#Speed EngageMinSpeed :
-The minimum speed of the Controllable in km/h when engaging a target.
+The minimum speed of the Group in km/h when engaging a target.
Dcs.DCSTypes#Speed EngageMaxSpeed :
-The maximum speed of the Controllable in km/h when engaging a target.
+The maximum speed of the Group in km/h when engaging a target.
@@ -783,7 +789,7 @@ The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-AI_A2A_CAP:OnAfterAbort(Controllable, From, Event, To)
+AI_A2A_CAP:OnAfterAbort(AICap, From, Event, To)
@@ -794,8 +800,8 @@ The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
- AI_A2A_GCI.InterceptRoute(AIControllable, AIGroup, Fsm)
+ AI_A2A_GCI.InterceptRoute(AIControllable, AIIntercept, Fsm)
- AI_A2A_GCI:New(AIGroup, EngageMinSpeed, EngageMaxSpeed)
+ AI_A2A_GCI:New(AIIntercept, EngageMinSpeed, EngageMaxSpeed)
Creates a new AIA2A GCI object
- AI_A2A_GCI:OnAfterAbort(AIGroup, From, Event, To)
+ AI_A2A_GCI:OnAfterAbort(AIIntercept, From, Event, To)
OnAfter Transition Handler for Event Abort.
- AI_A2A_GCI:OnAfterAccomplish(AIGroup, From, Event, To)
+ AI_A2A_GCI:OnAfterAccomplish(AIIntercept, From, Event, To)
OnAfter Transition Handler for Event Accomplish.
- AI_A2A_GCI:OnAfterDestroy(AIGroup, From, Event, To)
+ AI_A2A_GCI:OnAfterDestroy(AIIntercept, From, Event, To)
OnAfter Transition Handler for Event Destroy.
- AI_A2A_GCI:OnAfterEngage(AIGroup, From, Event, To)
+ AI_A2A_GCI:OnAfterEngage(AIIntercept, From, Event, To)
OnAfter Transition Handler for Event Engage.
- AI_A2A_GCI:OnAfterFired(AIGroup, From, Event, To)
+ AI_A2A_GCI:OnAfterFired(AIIntercept, From, Event, To)
OnAfter Transition Handler for Event Fired.
- AI_A2A_GCI:OnBeforeAbort(AIGroup, From, Event, To)
+ AI_A2A_GCI:OnBeforeAbort(AIIntercept, From, Event, To)
OnBefore Transition Handler for Event Abort.
- AI_A2A_GCI:OnBeforeAccomplish(AIGroup, From, Event, To)
+ AI_A2A_GCI:OnBeforeAccomplish(AIIntercept, From, Event, To)
OnBefore Transition Handler for Event Accomplish.
- AI_A2A_GCI:OnBeforeDestroy(AIGroup, From, Event, To)
+ AI_A2A_GCI:OnBeforeDestroy(AIIntercept, From, Event, To)
OnBefore Transition Handler for Event Destroy.
- AI_A2A_GCI:OnBeforeEngage(AIGroup, From, Event, To)
+ AI_A2A_GCI:OnBeforeEngage(AIIntercept, From, Event, To)
OnBefore Transition Handler for Event Engage.
- AI_A2A_GCI:OnBeforeFired(AIGroup, From, Event, To)
+ AI_A2A_GCI:OnBeforeFired(AIIntercept, From, Event, To)
OnBefore Transition Handler for Event Fired.
- AI_A2A_GCI:OnEnterEngaging(AIGroup, From, Event, To)
+ AI_A2A_GCI:OnEnterEngaging(AIIntercept, From, Event, To)
OnEnter Transition Handler for State Engaging.
@@ -289,7 +295,7 @@
- AI_A2A_GCI:OnLeaveEngaging(AIGroup, From, Event, To)
+ AI_A2A_GCI:OnLeaveEngaging(AIIntercept, From, Event, To)
OnLeave Transition Handler for State Engaging.
@@ -343,37 +349,37 @@
- AI_A2A_GCI:onafterAbort(AIGroup, From, Event, To)
+ AI_A2A_GCI:onafterAbort(AIIntercept, From, Event, To)
- AI_A2A_GCI:onafterAccomplish(AIGroup, From, Event, To)
+ AI_A2A_GCI:onafterAccomplish(AIIntercept, From, Event, To)
- AI_A2A_GCI:onafterDestroy(AIGroup, From, Event, To, EventData)
+ AI_A2A_GCI:onafterDestroy(AIIntercept, From, Event, To, EventData)
- AI_A2A_GCI:onafterEngage(AIGroup, From, Event, To)
+ AI_A2A_GCI:onafterEngage(AIIntercept, From, Event, To)
onafter State Transition for Event Patrol.
- AI_A2A_GCI:onafterStart(AIGroup, From, Event, To)
+ AI_A2A_GCI:onafterStart(AIIntercept, From, Event, To)
onafter State Transition for Event Patrol.
- AI_A2A_GCI:onbeforeEngage(AIGroup, From, Event, To)
+ AI_A2A_GCI:onbeforeEngage(AIIntercept, From, Event, To)
@@ -624,7 +630,7 @@ Use the method AICap#AI
-AI_A2A_GCI.InterceptRoute(AIControllable, AIGroup, Fsm)
+AI_A2A_GCI.InterceptRoute(AIControllable, AIIntercept, Fsm)
@@ -640,7 +646,7 @@ Use the method AICap#AI
- AIGroup :
+ AIIntercept :
@@ -655,7 +661,7 @@ Use the method AICap#AI
-AI_A2A_GCI:New(AIGroup, EngageMinSpeed, EngageMaxSpeed)
+AI_A2A_GCI:New(AIIntercept, EngageMinSpeed, EngageMaxSpeed)
@@ -666,7 +672,7 @@ Use the method AICap#AI
-Wrapper.Group#GROUP AIGroup :
+Wrapper.Group#GROUP AIIntercept :
@@ -691,7 +697,7 @@ Use the method AICap#AI
-AI_A2A_GCI:OnAfterAbort(AIGroup, From, Event, To)
+AI_A2A_GCI:OnAfterAbort(AIIntercept, From, Event, To)
@@ -702,8 +708,8 @@ Use the method AICap#AI
-Wrapper.Group#GROUP AIGroup :
-The AIGroup Object managed by the FSM.
+Wrapper.Group#GROUP AIIntercept :
+The Group Object managed by the FSM.
@@ -731,7 +737,7 @@ The To State string.
-AI_A2A_GCI:OnAfterAccomplish(AIGroup, From, Event, To)
+AI_A2A_GCI:OnAfterAccomplish(AIIntercept, From, Event, To)
@@ -742,8 +748,8 @@ The To State string.
@@ -921,48 +915,6 @@ Each successful dead will trigger an Account state transition that can be scored
-
-
-
-
-
-ACT_ACCOUNT_DEADS:onfuncEventCrash(EventData)
-
-
-
-
-
-
- Parameter
-
-
-
-
-
-
-
-ACT_ACCOUNT_DEADS:onfuncEventDead(EventData)
-
-
-
-
-
-
- Parameter
-
-
diff --git a/docs/Documentation/Airbase.html b/docs/Documentation/Airbase.html
index 8a7fde74a..674de623e 100644
--- a/docs/Documentation/Airbase.html
+++ b/docs/Documentation/Airbase.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/AirbasePolice.html b/docs/Documentation/AirbasePolice.html
index e8e042854..8ff5c6988 100644
--- a/docs/Documentation/AirbasePolice.html
+++ b/docs/Documentation/AirbasePolice.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Assign.html b/docs/Documentation/Assign.html
index d4389aedd..1929bba26 100644
--- a/docs/Documentation/Assign.html
+++ b/docs/Documentation/Assign.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Base.html b/docs/Documentation/Base.html
index 7659f64ad..592c22cf0 100644
--- a/docs/Documentation/Base.html
+++ b/docs/Documentation/Base.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
@@ -243,7 +249,7 @@
- BASE:GetParent(Child)
+ BASE:GetParent(Child, FromClass)
This is the worker method to retrieve the Parent class.
@@ -417,6 +423,30 @@ place: Object that the unit landed on.
BASE:OnEventTakeoff(EventData)
Occurs when an aircraft takes off from an airbase, farp, or ship.
+
+
+
+ BASE:ScheduleOnce(Start, SchedulerFunction, ...)
+
+Schedule a new time event.
+
+
+
+ BASE:ScheduleRepeat(Start, Repeat, RandomizeFactor, Stop, SchedulerFunction, ...)
+
+Schedule a new time event.
+
+
+
+ BASE:ScheduleStop(SchedulerFunction)
+
+Stops the Schedule.
+
+
+
+ BASE.SchedulerObject
+
+
@@ -485,6 +515,12 @@ When Moose is loaded statically, (as one file), tracing is switched off by defau
BASE:UnHandleEvent(Event)
UnSubscribe to a DCS event.
+
+
+
+ BASE._
+
+
@@ -1131,7 +1167,7 @@ The Event processing Priority.
-BASE:GetParent(Child)
+BASE:GetParent(Child, FromClass)
@@ -1146,13 +1182,18 @@ The Event processing Priority.
- Parameter
+ Parameters
Return value
@@ -1900,6 +1941,144 @@ The EventData structure.
+
+
+
+
+
+
+BASE:ScheduleOnce(Start, SchedulerFunction, ...)
+
+
+
+
+Schedule a new time event.
+
+
+Note that the schedule will only take place if the scheduler is started . Even for a single schedule event, the scheduler needs to be started also.
+
+ Parameters
+
+
+
+#number Start :
+Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
+
+
+
+
+#function SchedulerFunction :
+The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
+
+
+
+
+#table ... :
+Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
+
+
+
+ Return value
+
+#number:
+The ScheduleID of the planned schedule.
+
+
+
+
+
+
+
+BASE:ScheduleRepeat(Start, Repeat, RandomizeFactor, Stop, SchedulerFunction, ...)
+
+
+
+
+Schedule a new time event.
+
+
+Note that the schedule will only take place if the scheduler is started . Even for a single schedule event, the scheduler needs to be started also.
+
+ Parameters
+
+
+
+#number Start :
+Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
+
+
+
+
+#number Repeat :
+Specifies the interval in seconds when the scheduler will call the event function.
+
+
+
+
+#number RandomizeFactor :
+Specifies a randomization factor between 0 and 1 to randomize the Repeat.
+
+
+
+
+#number Stop :
+Specifies the amount of seconds when the scheduler will be stopped.
+
+
+
+
+#function SchedulerFunction :
+The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
+
+
+
+
+#table ... :
+Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
+
+
+
+ Return value
+
+#number:
+The ScheduleID of the planned schedule.
+
+
+
+
+
+
+
+BASE:ScheduleStop(SchedulerFunction)
+
+
+
+
+Stops the Schedule.
+
+ Parameter
+
+
+
+
+
+
+
+
+BASE.SchedulerObject
+
+
+
+
+
+
@@ -2209,6 +2388,20 @@ BASE:TraceOnOff( false )
#BASE :
+
+
+
+
+
+ #BASE._
+
+BASE._
+
+
+
+
+
+
@@ -2320,6 +2513,8 @@ A #table or any field.
+
+
diff --git a/docs/Documentation/Cargo.html b/docs/Documentation/Cargo.html
index e1ae98516..3dd45a78d 100644
--- a/docs/Documentation/Cargo.html
+++ b/docs/Documentation/Cargo.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/CleanUp.html b/docs/Documentation/CleanUp.html
index 4fd8e2211..75c7a78a0 100644
--- a/docs/Documentation/CleanUp.html
+++ b/docs/Documentation/CleanUp.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Client.html b/docs/Documentation/Client.html
index d32ac3da4..8f048a4e1 100644
--- a/docs/Documentation/Client.html
+++ b/docs/Documentation/Client.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/CommandCenter.html b/docs/Documentation/CommandCenter.html
index 16d49ac6b..59957f107 100644
--- a/docs/Documentation/CommandCenter.html
+++ b/docs/Documentation/CommandCenter.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Controllable.html b/docs/Documentation/Controllable.html
index 55cebf6b9..e9b65e4ac 100644
--- a/docs/Documentation/Controllable.html
+++ b/docs/Documentation/Controllable.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/DCSAirbase.html b/docs/Documentation/DCSAirbase.html
index 93e67e7ed..495771991 100644
--- a/docs/Documentation/DCSAirbase.html
+++ b/docs/Documentation/DCSAirbase.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/DCSCoalitionObject.html b/docs/Documentation/DCSCoalitionObject.html
index 2c94fdb31..84278d39e 100644
--- a/docs/Documentation/DCSCoalitionObject.html
+++ b/docs/Documentation/DCSCoalitionObject.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/DCSCommand.html b/docs/Documentation/DCSCommand.html
index d07008eea..24586b5b4 100644
--- a/docs/Documentation/DCSCommand.html
+++ b/docs/Documentation/DCSCommand.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/DCSController.html b/docs/Documentation/DCSController.html
index 890a42f82..255245afb 100644
--- a/docs/Documentation/DCSController.html
+++ b/docs/Documentation/DCSController.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/DCSGroup.html b/docs/Documentation/DCSGroup.html
index d3cba7a00..f71d07fea 100644
--- a/docs/Documentation/DCSGroup.html
+++ b/docs/Documentation/DCSGroup.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/DCSObject.html b/docs/Documentation/DCSObject.html
index 3bba8d8c8..39ebc862e 100644
--- a/docs/Documentation/DCSObject.html
+++ b/docs/Documentation/DCSObject.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/DCSTask.html b/docs/Documentation/DCSTask.html
index 6cfda94a4..54f519426 100644
--- a/docs/Documentation/DCSTask.html
+++ b/docs/Documentation/DCSTask.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/DCSTypes.html b/docs/Documentation/DCSTypes.html
index c66abe0ae..0c503127b 100644
--- a/docs/Documentation/DCSTypes.html
+++ b/docs/Documentation/DCSTypes.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/DCSUnit.html b/docs/Documentation/DCSUnit.html
index 22e5611cb..2af906b20 100644
--- a/docs/Documentation/DCSUnit.html
+++ b/docs/Documentation/DCSUnit.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/DCSVec3.html b/docs/Documentation/DCSVec3.html
index 64bf19193..35586cf3d 100644
--- a/docs/Documentation/DCSVec3.html
+++ b/docs/Documentation/DCSVec3.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/DCSWorld.html b/docs/Documentation/DCSWorld.html
index 06b71260d..15f8813c6 100644
--- a/docs/Documentation/DCSWorld.html
+++ b/docs/Documentation/DCSWorld.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/DCSZone.html b/docs/Documentation/DCSZone.html
index b836990d3..ad7a092d0 100644
--- a/docs/Documentation/DCSZone.html
+++ b/docs/Documentation/DCSZone.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/DCScountry.html b/docs/Documentation/DCScountry.html
index af9c6bd2a..127e37595 100644
--- a/docs/Documentation/DCScountry.html
+++ b/docs/Documentation/DCScountry.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/DCStimer.html b/docs/Documentation/DCStimer.html
index 26e194aff..28e5ae110 100644
--- a/docs/Documentation/DCStimer.html
+++ b/docs/Documentation/DCStimer.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/DCStrigger.html b/docs/Documentation/DCStrigger.html
index f665a8946..737cf0a04 100644
--- a/docs/Documentation/DCStrigger.html
+++ b/docs/Documentation/DCStrigger.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Database.html b/docs/Documentation/Database.html
index f8fc32284..bc7fdd980 100644
--- a/docs/Documentation/Database.html
+++ b/docs/Documentation/Database.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
@@ -174,6 +180,18 @@ The following iterator methods are currently available within the DATABASE:
DATABASE.AIRBASES
+
+
+
+ DATABASE:AccountDestroys(Event)
+
+Account the destroys.
+
+
+
+ DATABASE:AccountHits(Event)
+
+Account the Hits of the Players.
@@ -246,6 +264,12 @@ The following iterator methods are currently available within the DATABASE:
DATABASE.ClassName
+
+
+
+ DATABASE.DESTROYS
+
+
@@ -438,6 +462,12 @@ The following iterator methods are currently available within the DATABASE:
DATABASE:GetStatusGroup(GroupName)
Get a status to a Group within the Database, this to check crossing events for example.
+
+
+
+ DATABASE.HITS
+
+
@@ -670,6 +700,48 @@ The following iterator methods are currently available within the DATABASE:
+
+
+
+
+
+
+DATABASE:AccountDestroys(Event)
+
+
+
+
+Account the destroys.
+
+ Parameter
+
+
+
+
+
+
+
+DATABASE:AccountHits(Event)
+
+
+
+
+Account the Hits of the Players.
+
+ Parameter
+
@@ -899,6 +971,20 @@ The name of the airbase
+
+
+
+
+
+
+
+DATABASE.DESTROYS
+
+
+
+
+
+
@@ -1745,6 +1831,20 @@ self
+
+
+
+
+
+
+
+DATABASE.HITS
+
+
+
+
+
+
diff --git a/docs/Documentation/Designate.html b/docs/Documentation/Designate.html
index ff349e1b5..9372fa3d2 100644
--- a/docs/Documentation/Designate.html
+++ b/docs/Documentation/Designate.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
@@ -1151,6 +1157,7 @@ function below will use the range 1-7 just in case
+
DESIGNATE.LaserCodes
diff --git a/docs/Documentation/Detection.html b/docs/Documentation/Detection.html
index da950516a..844ba7dc2 100644
--- a/docs/Documentation/Detection.html
+++ b/docs/Documentation/Detection.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/DetectionManager.html b/docs/Documentation/DetectionManager.html
index fada6894a..a8497d2f1 100644
--- a/docs/Documentation/DetectionManager.html
+++ b/docs/Documentation/DetectionManager.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Escort.html b/docs/Documentation/Escort.html
index a3777ce98..624908b68 100644
--- a/docs/Documentation/Escort.html
+++ b/docs/Documentation/Escort.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Event.html b/docs/Documentation/Event.html
index 09473d377..237e7b036 100644
--- a/docs/Documentation/Event.html
+++ b/docs/Documentation/Event.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Fsm.html b/docs/Documentation/Fsm.html
index dc67c7520..a613fca6a 100644
--- a/docs/Documentation/Fsm.html
+++ b/docs/Documentation/Fsm.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
@@ -1599,7 +1605,7 @@ A string defining the start state.
-
+ #string
FSM._StartState
@@ -1898,7 +1904,6 @@ A string defining the start state.
-
FSM.current
diff --git a/docs/Documentation/Goal.html b/docs/Documentation/Goal.html
new file mode 100644
index 000000000..81c2aea93
--- /dev/null
+++ b/docs/Documentation/Goal.html
@@ -0,0 +1,585 @@
+
+
+
+
+
+
+
+
+
+
+
+
Module Goal
+
+
Core (WIP) -- Base class to allow the modeling of processes to achieve Goals.
+
+
+
+
+
+
GOAL models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized.
+
+
+
+
Author: Sven Van de Velde (FlightControl)
+
+
+
+
+
Global(s)
+
+
+ GOAL
+
+GOAL class, extends Fsm#FSM
+
+GOAL models processes that have an objective with a defined achievement.
+
+
+
+
+
+
+
Global(s)
+
+
+
+ #GOAL
+
+GOAL
+
+
+
+
+GOAL class, extends Fsm#FSM
+
+GOAL models processes that have an objective with a defined achievement.
+
+
+Derived classes implement the ways how the achievements can be realized.
+
+1. GOAL constructor
+
+
+ GOAL.New (): Creates a new GOAL object.
+
+
+2. GOAL is a finite state machine (FSM).
+
+2.1 GOAL States
+
+
+ Pending : The goal object is in progress.
+ Achieved : The goal objective is Achieved.
+
+
+2.2 GOAL Events
+
+
+ Achieved : Set the goal objective to Achieved.
+
+
+
+
+
+
+
+
+
Field(s)
+
+
+
+
+GOAL:Achieved()
+
+
+
+
+Achieved Trigger for GOAL
+
+
+
+
+
+
+
+GOAL:AddPlayerContribution(PlayerName)
+
+
+
+
+
+
+ Parameter
+
+
+
+#string PlayerName :
+
+
+
+
+
+
+
+
+
+GOAL:GetPlayerContribution(Player, PlayerName)
+
+
+
+
+
+
+ Parameters
+
+
+
+
+
+
+
+GOAL:GetPlayerContributions()
+
+
+
+
+
+
+
+
+
+
+
+
+GOAL:GetTotalContributions()
+
+
+
+
+
+
+
+
+
+
+
+
+GOAL:IsAchieved()
+
+
+
+
+
+
+ Return value
+
+#boolean:
+true if the goal is Achieved
+
+
+
+
+
+
+
+GOAL:New()
+
+
+
+
+GOAL Constructor.
+
+ Return value
+
+#GOAL :
+
+
+
+
+
+
+
+
+GOAL:OnAfterAchieved(From, Event, To)
+
+
+
+
+Achieved Handler OnAfter for GOAL
+
+ Parameters
+
+
+
+#string From :
+
+
+
+
+#string Event :
+
+
+
+
+#string To :
+
+
+
+
+
+
+
+
+
+GOAL:OnBeforeAchieved(From, Event, To)
+
+
+
+
+Achieved Handler OnBefore for GOAL
+
+ Parameters
+
+
+
+#string From :
+
+
+
+
+#string Event :
+
+
+
+
+#string To :
+
+
+
+ Return value
+
+#boolean:
+
+
+
+
+
+
+
+
+GOAL:OnEnterAchieved(From, Event, To)
+
+
+
+
+Achieved State Handler OnEnter for GOAL
+
+ Parameters
+
+
+
+#string From :
+
+
+
+
+#string Event :
+
+
+
+
+#string To :
+
+
+
+
+
+
+
+
+
+GOAL:OnLeaveAchieved(From, Event, To)
+
+
+
+
+Achieved State Handler OnLeave for GOAL
+
+ Parameters
+
+
+
+#string From :
+
+
+
+
+#string Event :
+
+
+
+
+#string To :
+
+
+
+ Return value
+
+#boolean:
+
+
+
+
+
+
+
+
+
+GOAL.Players
+
+
+
+
+
+
+
+
+
+
+
+
+GOAL.TotalContributions
+
+
+
+
+
+
+
+
+
+
+
+
+GOAL:__Achieved(Delay)
+
+
+
+
+Achieved Asynchronous Trigger for GOAL
+
+ Parameter
+
+
+
+#number Delay :
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/Documentation/Group.html b/docs/Documentation/Group.html
index ab3cd39d0..8b71f7b62 100644
--- a/docs/Documentation/Group.html
+++ b/docs/Documentation/Group.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+
Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+
Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+
TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+
ZoneCaptureCoalition
+
ZoneGoal
+
ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Identifiable.html b/docs/Documentation/Identifiable.html
index 9ff17c120..ee45da3f0 100644
--- a/docs/Documentation/Identifiable.html
+++ b/docs/Documentation/Identifiable.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+
Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+
Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+
TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+
ZoneCaptureCoalition
+
ZoneGoal
+
ZoneGoalCoalition
env
land
routines
@@ -158,6 +164,12 @@
IDENTIFIABLE:GetCoalition()
Returns coalition of the Identifiable.
+
+
+
+ IDENTIFIABLE:GetCoalitionName()
+
+Returns the name of the coalition of the Identifiable.
@@ -335,6 +347,34 @@ The DCS Identifiable is not existing or alive.
+
+IDENTIFIABLE:GetCoalitionName()
+
+
+
+
+Returns the name of the coalition of the Identifiable.
+
+ Return values
+
+
+
+#string:
+The name of the coalition.
+
+
+
+
+#nil:
+The DCS Identifiable is not existing or alive.
+
+
+
+
+
+
+
+
IDENTIFIABLE:GetCountry()
diff --git a/docs/Documentation/Menu.html b/docs/Documentation/Menu.html
index be629eda7..96f8d2d81 100644
--- a/docs/Documentation/Menu.html
+++ b/docs/Documentation/Menu.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Message.html b/docs/Documentation/Message.html
index ec6d862bc..574ed46bb 100644
--- a/docs/Documentation/Message.html
+++ b/docs/Documentation/Message.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
@@ -308,6 +314,7 @@ To send messages, you need to use the To functions.
+ #string
MESSAGE.MessageCategory
@@ -316,6 +323,9 @@ To send messages, you need to use the To functions.
+
+ self.MessageType .. ": "
+
diff --git a/docs/Documentation/MissileTrainer.html b/docs/Documentation/MissileTrainer.html
index 0b8a44d5b..3c30ef6df 100644
--- a/docs/Documentation/MissileTrainer.html
+++ b/docs/Documentation/MissileTrainer.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Mission.html b/docs/Documentation/Mission.html
index 9fc977939..1e6c12d45 100644
--- a/docs/Documentation/Mission.html
+++ b/docs/Documentation/Mission.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Movement.html b/docs/Documentation/Movement.html
index dfb86750e..434276722 100644
--- a/docs/Documentation/Movement.html
+++ b/docs/Documentation/Movement.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Object.html b/docs/Documentation/Object.html
index 24a71b22c..d4bdeee2f 100644
--- a/docs/Documentation/Object.html
+++ b/docs/Documentation/Object.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Point.html b/docs/Documentation/Point.html
index ff6a6fe3a..6237a6e0d 100644
--- a/docs/Documentation/Point.html
+++ b/docs/Documentation/Point.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
@@ -3486,6 +3492,7 @@ The y coordinate.
+
POINT_VEC2.z
diff --git a/docs/Documentation/Positionable.html b/docs/Documentation/Positionable.html
index e531312d4..ed5897722 100644
--- a/docs/Documentation/Positionable.html
+++ b/docs/Documentation/Positionable.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
@@ -1974,6 +1980,7 @@ The height in meters to add to the altitude of the positionable.
+ Core.Spot#SPOT
POSITIONABLE.Spot
diff --git a/docs/Documentation/Process_JTAC.html b/docs/Documentation/Process_JTAC.html
index 98ccc6058..e7e60d127 100644
--- a/docs/Documentation/Process_JTAC.html
+++ b/docs/Documentation/Process_JTAC.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Process_Pickup.html b/docs/Documentation/Process_Pickup.html
index 4b9a003fc..229b8a94b 100644
--- a/docs/Documentation/Process_Pickup.html
+++ b/docs/Documentation/Process_Pickup.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Protect.html b/docs/Documentation/Protect.html
new file mode 100644
index 000000000..368e20320
--- /dev/null
+++ b/docs/Documentation/Protect.html
@@ -0,0 +1,754 @@
+
+
+
+
+
+
+
+
+
+
+
+
Module Protect
+
+
Functional -- The PROTECT class handles the protection of objects, which can be zones, units, scenery.
+
+
+
+
+
+
Author: Sven Van de Velde (FlightControl)
+
Contributions: MillerTime
+
+
+
+
+
Global(s)
+
+
+
+
+
Global(s)
+
+
+
+ #PROTECT
+
+PROTECT
+
+
+
+
+
+
+
+
+
+
+
+
+
Field(s)
+
+
+
+
+PROTECT:AreProtectStaticsAlive()
+
+
+
+
+Check if the statics are still alive.
+
+
+
+
+
+
+
+PROTECT:AreProtectUnitsAlive()
+
+
+
+
+Check if the units are still alive.
+
+
+
+
+
+
+
+
+PROTECT.Coalition
+
+
+
+
+
+
+
+
+
+
+
+
+PROTECT:Flare(FlareColor)
+
+
+
+
+Flare.
+
+ Parameter
+
+
+
+
+
+
+
+PROTECT:GetCoalition()
+
+
+
+
+Get the owning coalition of the zone.
+
+ Return value
+
+DCSCoalition.DCSCoalition#coalition :
+Coalition.
+
+
+
+
+
+
+
+PROTECT:GetCoalitionName()
+
+
+
+
+Get the owning coalition name of the zone.
+
+ Return value
+
+#string:
+Coalition name.
+
+
+
+
+
+
+
+PROTECT:GetProtectZone()
+
+
+
+
+Get the ProtectZone
+
+ Return value
+
+Core.Zone#ZONE_BASE :
+
+
+
+
+
+
+
+
+PROTECT:GetProtectZoneName()
+
+
+
+
+Get the name of the ProtectZone
+
+ Return value
+
+#string:
+
+
+
+
+
+
+
+
+PROTECT:IsAttacked()
+
+
+
+
+
+
+
+
+
+
+
+
+PROTECT:IsCaptureUnitInZone()
+
+
+
+
+Check if there is a capture unit in the zone.
+
+
+
+
+
+
+
+PROTECT:IsCaptured()
+
+
+
+
+
+
+
+
+
+
+
+
+PROTECT:IsEmpty()
+
+
+
+
+
+
+
+
+
+
+
+
+PROTECT:IsGuarded()
+
+
+
+
+
+
+
+
+
+
+
+
+PROTECT:Mark()
+
+
+
+
+Mark.
+
+
+
+
+
+
+
+
+PROTECT.MarkBlue
+
+
+
+
+
+
+
+
+
+
+
+
+
+PROTECT.MarkRed
+
+
+
+
+
+
+
+
+
+
+
+
+PROTECT:SetCoalition(Coalition)
+
+
+
+
+Set the owning coalition of the zone.
+
+ Parameter
+
+
+
+
+
+
+
+PROTECT:Smoke(SmokeColor)
+
+
+
+
+Smoke.
+
+ Parameter
+
+
+
+
+
+
+
+
+PROTECT.SmokeColor
+
+
+
+
+
+
+
+
+
+
+
+
+
+PROTECT.SmokeTime
+
+
+
+
+
+
+
+self.SmokeColor = nil
+
+
+
+
+
+
+
+PROTECT:StatusCoalition()
+
+
+
+
+Check status Coalition ownership.
+
+
+
+
+
+
+
+PROTECT:StatusSmoke()
+
+
+
+
+Check status Smoke.
+
+
+
+
+
+
+
+PROTECT:StatusZone()
+
+
+
+
+Check status Zone.
+
+
+
+
+
+
+
+PROTECT:onafterStart()
+
+
+
+
+Bound.
+
+
+
+
+
+
+
+PROTECT:onenterAttacked()
+
+
+
+
+
+
+
+
+
+
+
+
+PROTECT:onenterCaptured()
+
+
+
+
+
+
+
+
+
+
+
+
+PROTECT:onenterEmpty()
+
+
+
+
+
+
+
+
+
+
+
+
+PROTECT:onenterGuarded()
+
+
+
+
+Bound.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/Documentation/Radio.html b/docs/Documentation/Radio.html
index 5fc5b4646..39e53f7cf 100644
--- a/docs/Documentation/Radio.html
+++ b/docs/Documentation/Radio.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+
Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+
Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+
TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+
ZoneCaptureCoalition
+
ZoneGoal
+
ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Rat.html b/docs/Documentation/Rat.html
index f055b5df2..07724fc38 100644
--- a/docs/Documentation/Rat.html
+++ b/docs/Documentation/Rat.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+
Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+
Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+
TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+
ZoneCaptureCoalition
+
ZoneGoal
+
ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Route.html b/docs/Documentation/Route.html
index 2874fa7d7..41222bef8 100644
--- a/docs/Documentation/Route.html
+++ b/docs/Documentation/Route.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+
Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+
Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+
TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+
ZoneCaptureCoalition
+
ZoneGoal
+
ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Scenery.html b/docs/Documentation/Scenery.html
index 4529c2db6..2da5eda56 100644
--- a/docs/Documentation/Scenery.html
+++ b/docs/Documentation/Scenery.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+
Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+
Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+
TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+
ZoneCaptureCoalition
+
ZoneGoal
+
ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/ScheduleDispatcher.html b/docs/Documentation/ScheduleDispatcher.html
index a9155213d..8bb0af947 100644
--- a/docs/Documentation/ScheduleDispatcher.html
+++ b/docs/Documentation/ScheduleDispatcher.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+
Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+
Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+
TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+
ZoneCaptureCoalition
+
ZoneGoal
+
ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Scheduler.html b/docs/Documentation/Scheduler.html
index ec5dfe8b4..8e01ae1ab 100644
--- a/docs/Documentation/Scheduler.html
+++ b/docs/Documentation/Scheduler.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+
Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+
Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+
TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+
ZoneCaptureCoalition
+
ZoneGoal
+
ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Scoring.html b/docs/Documentation/Scoring.html
index 91b76b720..6a9aeba87 100644
--- a/docs/Documentation/Scoring.html
+++ b/docs/Documentation/Scoring.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+
Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+
Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+
TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+
ZoneCaptureCoalition
+
ZoneGoal
+
ZoneGoalCoalition
env
land
routines
@@ -709,6 +715,12 @@ Various methods exist to configure:
SCORING:SetScaleDestroyScore(Scale)
Set the scale for scoring valid destroys (enemy destroys).
+
+
+
+ SCORING:_AddMissionGoalScore(Mission, PlayerName, Text, Score)
+
+Registers Scores the players completing a Mission Task.
@@ -2179,6 +2191,42 @@ The scale of the score given.
+
+SCORING:_AddMissionGoalScore(Mission, PlayerName, Text, Score)
+
+
+
+
+Registers Scores the players completing a Mission Task.
+
+ Parameters
+
+
+
+
+
+
SCORING:_AddMissionScore(Mission, PlayerUnit, Text, Score)
diff --git a/docs/Documentation/Sead.html b/docs/Documentation/Sead.html
index 409f76d29..3c6e56173 100644
--- a/docs/Documentation/Sead.html
+++ b/docs/Documentation/Sead.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Set.html b/docs/Documentation/Set.html
index 815665856..bf497968c 100644
--- a/docs/Documentation/Set.html
+++ b/docs/Documentation/Set.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
@@ -227,6 +233,22 @@
Countries
Starting with certain prefix strings.
+
+
+
+ SET_STATIC
+
+3) SET_STATIC class, extends Set#SET_BASE
+
+Mission designers can use the SET_STATIC class to build sets of Statics belonging to certain:
+
+
+ Coalitions
+ Categories
+ Countries
+ Static types
+ Starting with certain prefix strings.
+
@@ -914,6 +936,172 @@ mission designer to add a dedicated method
SET_GROUP:_EventOnDeadOrCrash(Event)
Handles the OnDead or OnCrash event for alive groups set.
+
+
+
+
+
+
+
+ SET_STATIC:AddInDatabase(Event)
+
+Handles the Database to check on an event (birth) that the Object was added in the Database.
+
+
+
+ SET_STATIC:AddStatic(AddStatic)
+
+Add STATIC(s) to SET_STATIC.
+
+
+
+ SET_STATIC:AddStaticsByName(AddStaticNames)
+
+Add STATIC(s) to SET_STATIC.
+
+
+
+ SET_STATIC:FilterCategories(Categories)
+
+Builds a set of units out of categories.
+
+
+
+ SET_STATIC:FilterCoalitions(Coalitions)
+
+Builds a set of units of coalitions.
+
+
+
+ SET_STATIC:FilterCountries(Countries)
+
+Builds a set of units of defined countries.
+
+
+
+ SET_STATIC:FilterPrefixes(Prefixes)
+
+Builds a set of units of defined unit prefixes.
+
+
+
+ SET_STATIC:FilterStart()
+
+Starts the filtering.
+
+
+
+ SET_STATIC:FilterTypes(Types)
+
+Builds a set of units of defined unit types.
+
+
+
+ SET_STATIC:FindInDatabase(Event)
+
+Handles the Database to check on any event that Object exists in the Database.
+
+
+
+ SET_STATIC:FindStatic(StaticName)
+
+Finds a Static based on the Static Name.
+
+
+
+ SET_STATIC:ForEachStatic(IteratorFunction, ...)
+
+Iterate the SET_STATIC and call an interator function for each alive STATIC, providing the STATIC and optional parameters.
+
+
+
+ SET_STATIC:ForEachStaticCompletelyInZone(ZoneObject, IteratorFunction, ...)
+
+Iterate the SET_STATIC and call an iterator function for each alive STATIC presence completely in a Zone , providing the STATIC and optional parameters to the called function.
+
+
+
+ SET_STATIC:ForEachStaticInZone(IteratorFunction, ...)
+
+Check if minimal one element of the SET_STATIC is in the Zone.
+
+
+
+ SET_STATIC:ForEachStaticNotInZone(ZoneObject, IteratorFunction, ...)
+
+Iterate the SET_STATIC and call an iterator function for each alive STATIC presence not in a Zone , providing the STATIC and optional parameters to the called function.
+
+
+
+ SET_STATIC:GetCoordinate()
+
+Get the center coordinate of the SET_STATIC.
+
+
+
+ SET_STATIC:GetFirst()
+
+Get the first unit from the set.
+
+
+
+ SET_STATIC:GetHeading()
+
+Get the average heading of the SET_STATIC.
+
+
+
+ SET_STATIC:GetStaticTypes()
+
+Returns map of unit types.
+
+
+
+ SET_STATIC:GetStaticTypesText()
+
+Returns a comma separated string of the unit types with a count in the Set .
+
+
+
+ SET_STATIC:GetTypeNames(Delimiter)
+
+Retrieve the type names of the Static s in the SET, delimited by an optional delimiter.
+
+
+
+ SET_STATIC:GetVelocity()
+
+Get the maximum velocity of the SET_STATIC.
+
+
+
+ SET_STATIC:IsIncludeObject(MStatic)
+
+
+
+
+
+ SET_STATIC:IsNotInZone(ZoneObject, Zone)
+
+Check if no element of the SET_STATIC is in the Zone.
+
+
+
+ SET_STATIC:IsPatriallyInZone(Zone)
+
+Check if minimal one element of the SET_STATIC is in the Zone.
+
+
+
+ SET_STATIC:New()
+
+Creates a new SET_STATIC object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names.
+
+
+
+ SET_STATIC:RemoveStaticsByName(RemoveStaticNames)
+
+Remove STATIC(s) from SET_STATIC.
@@ -1014,6 +1202,12 @@ mission designer to add a dedicated method
SET_UNIT:ForEachUnitCompletelyInZone(ZoneObject, IteratorFunction, ...)
Iterate the SET_UNIT and call an iterator function for each alive UNIT presence completely in a Zone , providing the UNIT and optional parameters to the called function.
+
+
+
+ SET_UNIT:ForEachUnitInZone(IteratorFunction, ...)
+
+Check if minimal one element of the SET_UNIT is in the Zone.
@@ -1105,6 +1299,18 @@ mission designer to add a dedicated method
SET_UNIT:IsIncludeObject(MUnit)
+
+
+
+ SET_UNIT:IsNotInZone(ZoneObject, Zone)
+
+Check if no element of the SET_UNIT is in the Zone.
+
+
+
+ SET_UNIT:IsPartiallyInZone(ZoneTest)
+
+Check if minimal one element of the SET_UNIT is in the Zone.
@@ -1443,6 +1649,100 @@ The following iterator methods are currently available within the SETGROUP:
+
+
+
+
+
+ #SET_STATIC
+
+SET_STATIC
+
+
+
+
+3) SET_STATIC class, extends Set#SET_BASE
+
+Mission designers can use the SET_STATIC class to build sets of Statics belonging to certain:
+
+
+ Coalitions
+ Categories
+ Countries
+ Static types
+ Starting with certain prefix strings.
+
+
+
+
+3.1) SET_STATIC constructor
+
+Create a new SET_STATIC object with the SET_STATIC.New method:
+
+
+
+3.2) Add or Remove STATIC(s) from SET_STATIC
+
+STATICs can be added and removed using the Set#SET_STATIC.AddStaticsByName and Set#SET_STATIC.RemoveStaticsByName respectively.
+These methods take a single STATIC name or an array of STATIC names to be added or removed from SET_STATIC.
+
+3.3) SET_STATIC filter criteria
+
+You can set filter criteria to define the set of units within the SET_STATIC.
+Filter criteria are defined by:
+
+
+
+Once the filter criteria have been set for the SET_STATIC, you can start filtering using:
+
+
+
+Planned filter criteria within development are (so these are not yet available):
+
+
+
+3.4) SET_STATIC iterators
+
+Once the filters have been defined and the SETSTATIC has been built, you can iterate the SET STATIC with the available iterator methods.
+The iterator methods will walk the SETSTATIC set, and call for each element within the set a function that you provide.
+The following iterator methods are currently available within the SET STATIC:
+
+
+ SET_STATIC.ForEachStatic : Calls a function for each alive unit it finds within the SET_STATIC.
+ SET_GROUP.ForEachGroupCompletelyInZone : Iterate the SET_GROUP and call an iterator function for each alive GROUP presence completely in a Zone , providing the GROUP and optional parameters to the called function.
+ SET_GROUP.ForEachGroupNotInZone : Iterate the SET_GROUP and call an iterator function for each alive GROUP presence not in a Zone , providing the GROUP and optional parameters to the called function.
+
+
+Planned iterators methods in development are (so these are not yet available):
+
+
+
+3.5 ) SET_STATIC atomic methods
+
+Various methods exist for a SETSTATIC to perform actions or calculations and retrieve results from the SET STATIC:
+
+
+
+
+
@@ -4477,6 +4777,744 @@ A single name or an array of GROUP names.
+
+
+
+
+ Field(s)
+
+
+
+
+SET_STATIC:AddInDatabase(Event)
+
+
+
+
+Handles the Database to check on an event (birth) that the Object was added in the Database.
+
+
+This is required, because sometimes the DATABASE birth event gets called later than the SET BASE birth event!
+
+ Parameter
+
+ Return values
+
+
+
+#string:
+The name of the STATIC
+
+
+
+
+#table:
+The STATIC
+
+
+
+
+
+
+
+
+
+SET_STATIC:AddStatic(AddStatic)
+
+
+
+
+Add STATIC(s) to SET_STATIC.
+
+ Parameter
+
+ Return value
+
+#SET_STATIC :
+self
+
+
+
+
+
+
+
+SET_STATIC:AddStaticsByName(AddStaticNames)
+
+
+
+
+Add STATIC(s) to SET_STATIC.
+
+ Parameter
+
+ Return value
+
+#SET_STATIC :
+self
+
+
+
+
+
+
+
+SET_STATIC:FilterCategories(Categories)
+
+
+
+
+Builds a set of units out of categories.
+
+
+Possible current categories are plane, helicopter, ground, ship.
+
+ Parameter
+
+ Return value
+
+#SET_STATIC :
+self
+
+
+
+
+
+
+
+SET_STATIC:FilterCoalitions(Coalitions)
+
+
+
+
+Builds a set of units of coalitions.
+
+
+Possible current coalitions are red, blue and neutral.
+
+ Parameter
+
+ Return value
+
+#SET_STATIC :
+self
+
+
+
+
+
+
+
+SET_STATIC:FilterCountries(Countries)
+
+
+
+
+Builds a set of units of defined countries.
+
+
+Possible current countries are those known within DCS world.
+
+ Parameter
+
+ Return value
+
+#SET_STATIC :
+self
+
+
+
+
+
+
+
+SET_STATIC:FilterPrefixes(Prefixes)
+
+
+
+
+Builds a set of units of defined unit prefixes.
+
+
+All the units starting with the given prefixes will be included within the set.
+
+ Parameter
+
+ Return value
+
+#SET_STATIC :
+self
+
+
+
+
+
+
+
+SET_STATIC:FilterStart()
+
+
+
+
+Starts the filtering.
+
+ Return value
+
+#SET_STATIC :
+self
+
+
+
+
+
+
+
+SET_STATIC:FilterTypes(Types)
+
+
+
+
+Builds a set of units of defined unit types.
+
+
+Possible current types are those types known within DCS world.
+
+ Parameter
+
+ Return value
+
+#SET_STATIC :
+self
+
+
+
+
+
+
+
+SET_STATIC:FindInDatabase(Event)
+
+
+
+
+Handles the Database to check on any event that Object exists in the Database.
+
+
+This is required, because sometimes the DATABASE event gets called later than the SET BASE event or vise versa!
+
+ Parameter
+
+ Return values
+
+
+
+#string:
+The name of the STATIC
+
+
+
+
+#table:
+The STATIC
+
+
+
+
+
+
+
+
+
+SET_STATIC:FindStatic(StaticName)
+
+
+
+
+Finds a Static based on the Static Name.
+
+ Parameter
+
+
+
+#string StaticName :
+
+
+
+ Return value
+
+Wrapper.Static#STATIC :
+The found Static.
+
+
+
+
+
+
+
+SET_STATIC:ForEachStatic(IteratorFunction, ...)
+
+
+
+
+Iterate the SET_STATIC and call an interator function for each alive STATIC, providing the STATIC and optional parameters.
+
+ Parameters
+
+ Return value
+
+#SET_STATIC :
+self
+
+
+
+
+
+
+
+SET_STATIC:ForEachStaticCompletelyInZone(ZoneObject, IteratorFunction, ...)
+
+
+
+
+Iterate the SET_STATIC and call an iterator function for each alive STATIC presence completely in a Zone , providing the STATIC and optional parameters to the called function.
+
+ Parameters
+
+
+
+Core.Zone#ZONE ZoneObject :
+The Zone to be tested for.
+
+
+
+
+#function IteratorFunction :
+The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter.
+
+
+
+
+ ... :
+
+
+
+ Return value
+
+#SET_STATIC :
+self
+
+
+
+
+
+
+
+SET_STATIC:ForEachStaticInZone(IteratorFunction, ...)
+
+
+
+
+Check if minimal one element of the SET_STATIC is in the Zone.
+
+ Parameters
+
+ Return value
+
+#SET_STATIC :
+self
+
+
+
+
+
+
+
+SET_STATIC:ForEachStaticNotInZone(ZoneObject, IteratorFunction, ...)
+
+
+
+
+Iterate the SET_STATIC and call an iterator function for each alive STATIC presence not in a Zone , providing the STATIC and optional parameters to the called function.
+
+ Parameters
+
+
+
+Core.Zone#ZONE ZoneObject :
+The Zone to be tested for.
+
+
+
+
+#function IteratorFunction :
+The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter.
+
+
+
+
+ ... :
+
+
+
+ Return value
+
+#SET_STATIC :
+self
+
+
+
+
+
+
+
+SET_STATIC:GetCoordinate()
+
+
+
+
+Get the center coordinate of the SET_STATIC.
+
+ Return value
+
+Core.Point#COORDINATE :
+The center coordinate of all the units in the set, including heading in degrees and speed in mps in case of moving units.
+
+
+
+
+
+
+
+SET_STATIC:GetFirst()
+
+
+
+
+Get the first unit from the set.
+
+ Return value
+
+Wrapper.Static#STATIC :
+The STATIC object.
+
+
+
+
+
+
+
+SET_STATIC:GetHeading()
+
+
+
+
+Get the average heading of the SET_STATIC.
+
+ Return value
+
+#number:
+Heading Heading in degrees and speed in mps in case of moving units.
+
+
+
+
+
+
+
+SET_STATIC:GetStaticTypes()
+
+
+
+
+Returns map of unit types.
+
+ Return value
+
+#map :
+string,#number> A map of the unit types found. The key is the StaticTypeName and the value is the amount of unit types found.
+
+
+
+
+
+
+
+SET_STATIC:GetStaticTypesText()
+
+
+
+
+Returns a comma separated string of the unit types with a count in the Set .
+
+ Return value
+
+#string:
+The unit types string
+
+
+
+
+
+
+
+SET_STATIC:GetTypeNames(Delimiter)
+
+
+
+
+Retrieve the type names of the Static s in the SET, delimited by an optional delimiter.
+
+ Parameter
+
+ Return value
+
+#string:
+The types of the Static s delimited.
+
+
+
+
+
+
+
+SET_STATIC:GetVelocity()
+
+
+
+
+Get the maximum velocity of the SET_STATIC.
+
+ Return value
+
+#number:
+The speed in mps in case of moving units.
+
+
+
+
+
+
+
+SET_STATIC:IsIncludeObject(MStatic)
+
+
+
+
+
+
+ Parameter
+
+ Return value
+
+#SET_STATIC :
+self
+
+
+
+
+
+
+
+SET_STATIC:IsNotInZone(ZoneObject, Zone)
+
+
+
+
+Check if no element of the SET_STATIC is in the Zone.
+
+ Parameters
+
+ Return value
+
+#boolean:
+
+
+
+
+
+
+
+
+SET_STATIC:IsPatriallyInZone(Zone)
+
+
+
+
+Check if minimal one element of the SET_STATIC is in the Zone.
+
+ Parameter
+
+ Return value
+
+#boolean:
+
+
+
+
+
+
+
+
+SET_STATIC:New()
+
+
+
+
+Creates a new SET_STATIC object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names.
+
+ Return value
+
+#SET_STATIC :
+
+
+ Usage:
+ -- Define a new SET_STATIC Object. This DBObject will contain a reference to all alive Statics.
+DBObject = SET_STATIC:New()
+
+
+
+
+
+
+
+SET_STATIC:RemoveStaticsByName(RemoveStaticNames)
+
+
+
+
+Remove STATIC(s) from SET_STATIC.
+
+ Parameter
+
+ Return value
+
+
+self
+
@@ -4947,6 +5985,38 @@ self
+
+SET_UNIT:ForEachUnitInZone(IteratorFunction, ...)
+
+
+
+
+Check if minimal one element of the SET_UNIT is in the Zone.
+
+ Parameters
+
+ Return value
+
+#SET_UNIT :
+self
+
+
+
+
+
+
SET_UNIT:ForEachUnitNotInZone(ZoneObject, IteratorFunction, ...)
@@ -5302,6 +6372,65 @@ The amount of SEADable units in the Set
#SET_UNIT :
self
+
+
+
+
+
+
+SET_UNIT:IsNotInZone(ZoneObject, Zone)
+
+
+
+
+Check if no element of the SET_UNIT is in the Zone.
+
+ Parameters
+
+ Return value
+
+#boolean:
+
+
+
+
+
+
+
+
+SET_UNIT:IsPartiallyInZone(ZoneTest)
+
+
+
+
+Check if minimal one element of the SET_UNIT is in the Zone.
+
+ Parameter
+
+ Return value
+
+#boolean:
+
+
diff --git a/docs/Documentation/Settings.html b/docs/Documentation/Settings.html
index 274a10019..9de5667f7 100644
--- a/docs/Documentation/Settings.html
+++ b/docs/Documentation/Settings.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Smoke.html b/docs/Documentation/Smoke.html
index 14e4cd382..ddc5f021c 100644
--- a/docs/Documentation/Smoke.html
+++ b/docs/Documentation/Smoke.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Spawn.html b/docs/Documentation/Spawn.html
index e59e84497..e0457d619 100644
--- a/docs/Documentation/Spawn.html
+++ b/docs/Documentation/Spawn.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
@@ -3163,7 +3169,7 @@ Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Schedule( 600, 0.5 )
-
+ #boolean
SPAWN.SpawnUnControlled
@@ -3187,7 +3193,7 @@ Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Schedule( 600, 0.5 )
- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
+ When the first Spawn executes, all the Groups need to be made visible before start.
diff --git a/docs/Documentation/SpawnStatic.html b/docs/Documentation/SpawnStatic.html
index 487563b09..3304a02fb 100644
--- a/docs/Documentation/SpawnStatic.html
+++ b/docs/Documentation/SpawnStatic.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Spot.html b/docs/Documentation/Spot.html
index 47f777855..e67e29ef7 100644
--- a/docs/Documentation/Spot.html
+++ b/docs/Documentation/Spot.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
@@ -766,7 +772,6 @@ true if it is lasing
-
SPOT.ScheduleID
@@ -780,7 +785,6 @@ true if it is lasing
-
SPOT.SpotIR
@@ -794,7 +798,6 @@ true if it is lasing
-
SPOT.SpotLaser
@@ -808,7 +811,6 @@ true if it is lasing
-
SPOT.Target
diff --git a/docs/Documentation/Static.html b/docs/Documentation/Static.html
index 04a72d991..2d8f1ccfb 100644
--- a/docs/Documentation/Static.html
+++ b/docs/Documentation/Static.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/StaticObject.html b/docs/Documentation/StaticObject.html
index 963e6ded9..5c2e035a1 100644
--- a/docs/Documentation/StaticObject.html
+++ b/docs/Documentation/StaticObject.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Task.html b/docs/Documentation/Task.html
index c33573603..ea4232815 100644
--- a/docs/Documentation/Task.html
+++ b/docs/Documentation/Task.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
@@ -331,7 +337,7 @@
- TASK:Goal()
+ TASK:Goal(PlayerUnit, PlayerName)
Goal Trigger for TASK
@@ -469,7 +475,7 @@
- TASK:OnAfterGoal(Controllable, From, Event, To)
+ TASK:OnAfterGoal(From, Event, To, PlayerUnit, PlayerName)
Goal Handler OnAfter for TASK
@@ -493,7 +499,7 @@
- TASK:OnBeforeGoal(Controllable, From, Event, To)
+ TASK:OnBeforeGoal(From, Event, To, PlayerUnit, PlayerName)
Goal Handler OnBefore for TASK
@@ -805,7 +811,7 @@
- TASK:__Goal(Delay)
+ TASK:__Goal(Delay, PlayerUnit, PlayerName)
Goal Asynchronous Trigger for TASK
@@ -1648,13 +1654,28 @@ TaskType
-TASK:Goal()
+TASK:Goal(PlayerUnit, PlayerName)
Goal Trigger for TASK
+ Parameters
+
@@ -2097,7 +2118,7 @@ self
-TASK:OnAfterGoal(Controllable, From, Event, To)
+TASK:OnAfterGoal(From, Event, To, PlayerUnit, PlayerName)
@@ -2108,11 +2129,6 @@ self
@@ -2217,7 +2245,7 @@ The name of the Player.
-TASK:OnBeforeGoal(Controllable, From, Event, To)
+TASK:OnBeforeGoal(From, Event, To, PlayerUnit, PlayerName)
@@ -2228,11 +2256,6 @@ The name of the Player.
Return value
@@ -3378,19 +3413,31 @@ self
-TASK:__Goal(Delay)
+TASK:__Goal(Delay, PlayerUnit, PlayerName)
Goal Asynchronous Trigger for TASK
- Parameter
+ Parameters
diff --git a/docs/Documentation/TaskZoneCapture.html b/docs/Documentation/TaskZoneCapture.html
new file mode 100644
index 000000000..897ea4c63
--- /dev/null
+++ b/docs/Documentation/TaskZoneCapture.html
@@ -0,0 +1,796 @@
+
+
+
+
+
+
+
+
+
+
+
+
Module TaskZoneCapture
+
+
Tasking - The TASK_Protect models tasks for players to protect or capture specific zones.
+
+
+
+
+
+
Author: Sven Van de Velde (FlightControl)
+
+
Contributions: MillerTime
+
+
+
+
+
Global(s)
+
+
+ TASK_ZONE_CAPTURE
+
+
+
+The TASKZONE CAPTURE class defines an Suppression or Extermination of Air Defenses task for a human player to be executed.
+
+
+
+ TASK_ZONE_GOAL
+
+TASKZONE GOAL class, extends Task#TASK
+
+The TASKZONE GOAL class defines the task to protect or capture a protection zone.
+
+
+
+
+
+
+
+
+
+
Global(s)
+
+
+
+ #TASK_ZONE_CAPTURE
+
+TASK_ZONE_CAPTURE
+
+
+
+
+
+
+The TASKZONE CAPTURE class defines an Suppression or Extermination of Air Defenses task for a human player to be executed.
+
+
+These tasks are important to be executed as they will help to achieve air superiority at the vicinity.
+
+The TASKZONE CAPTURE is used by the TaskA2G Dispatcher#TASKA2G DISPATCHER to automatically create SEAD tasks
+based on detected enemy ground targets.
+
+
+
+
+
+
+
+ #TASK_ZONE_GOAL
+
+TASK_ZONE_GOAL
+
+
+
+
+TASKZONE GOAL class, extends Task#TASK
+
+The TASKZONE GOAL class defines the task to protect or capture a protection zone.
+
+
+The TASKZONE GOAL is implemented using a Fsm#FSM_TASK , and has the following statuses:
+
+
+ None : Start of the process
+ Planned : The A2G task is planned.
+ Assigned : The A2G task is assigned to a Group#GROUP .
+ Success : The A2G task is successfully completed.
+ Failed : The A2G task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ.
+
+
+Set the scoring of achievements in an A2G attack.
+
+Scoring or penalties can be given in the following circumstances:
+
+
+
+
+
+
+
+
+
+
+
+
+
The TASKZONE CAPTURE class
+
+
Field(s)
+
+
+
+
+TASK_ZONE_CAPTURE:New(Mission, SetGroup, TaskName, ZoneGoalCoalition, TaskBriefing)
+
+
+
+
+Instantiates a new TASKZONE CAPTURE.
+
+ Parameters
+
+ Return value
+
+#TASKZONE CAPTURE :
+self
+
+
+
+
+
+
+
+TASK_ZONE_CAPTURE:OnAfterGoal(TaskUnit, From, Event, To, PlayerUnit, PlayerName)
+
+
+
+
+
+
+ Parameters
+
+
+
+
+
+
+
+TASK_ZONE_CAPTURE:ReportOrder(ReportGroup)
+
+
+
+
+
+
+ Parameter
+
+
+
+ ReportGroup :
+
+
+
+
+
+
+
+
+
+
+TASK_ZONE_CAPTURE.TaskCoalition
+
+
+
+
+
+
+
+
+
+
+
+
+
+TASK_ZONE_CAPTURE.TaskCoalitionName
+
+
+
+
+
+
+
+
+
+
+
+
+
+TASK_ZONE_CAPTURE.TaskZoneName
+
+
+
+
+
+
+
+
+
+
+
+
+TASK_ZONE_CAPTURE:UpdateTaskInfo()
+
+
+
+
+Instantiates a new TASKZONE CAPTURE.
+
+
+
+
+
+
+ Core.ZoneGoalCoalition#ZONE_GOAL_COALITION
+
+TASK_ZONE_CAPTURE.ZoneGoal
+
+
+
+
+
+
+
+
+
+
+
+
The TASKZONE GOAL class
+
+
Field(s)
+
+
+
+
+TASK_ZONE_GOAL:GetGoalTotal()
+
+
+
+
+
+
+
+
+
+
+
+
+TASK_ZONE_GOAL:GetMarkInfo(TaskInfoID, TaskInfo)
+
+
+
+
+
+
+ Parameters
+
+
+
+ TaskInfoID :
+
+
+
+
+ TaskInfo :
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+TASK_ZONE_GOAL:GetReportDetail(ReportGroup, TaskInfoID, TaskInfo)
+
+
+
+
+
+
+ Parameters
+
+
+
+ ReportGroup :
+
+
+
+
+ TaskInfoID :
+
+
+
+
+ TaskInfo :
+
+
+
+
+
+
+
+
+
+TASK_ZONE_GOAL:GetTargetZone(TaskUnit)
+
+
+
+
+
+
+ Parameter
+
+ Return value
+
+Core.Zone#ZONE_BASE :
+The Zone object where the Target is located on the map.
+
+
+
+
+
+
+
+TASK_ZONE_GOAL:New(Mission, SetGroup, TaskName, ZoneGoal, TaskType, TaskBriefing)
+
+
+
+
+Instantiates a new TASKZONE GOAL.
+
+ Parameters
+
+ Return value
+
+#TASKZONE GOAL :
+self
+
+
+
+
+
+
+
+TASK_ZONE_GOAL:SetGoalTotal(GoalTotal)
+
+
+
+
+
+
+ Parameter
+
+
+
+
+
+
+
+TASK_ZONE_GOAL:SetProtect(ZoneGoal)
+
+
+
+
+
+
+ Parameter
+
+
+
+
+
+
+
+TASK_ZONE_GOAL:SetTargetZone(TargetZone, TaskUnit)
+
+
+
+
+
+
+ Parameters
+
+
+
+
+
+
+
+
+TASK_ZONE_GOAL.TaskType
+
+
+
+
+
+
+
+
+
+
+
+ Core.ZoneGoal#ZONE_GOAL
+
+TASK_ZONE_GOAL.ZoneGoal
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/Documentation/Task_A2A.html b/docs/Documentation/Task_A2A.html
index da59f25d5..c9afabb62 100644
--- a/docs/Documentation/Task_A2A.html
+++ b/docs/Documentation/Task_A2A.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+
Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+
Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+
TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+
ZoneCaptureCoalition
+
ZoneGoal
+
ZoneGoalCoalition
env
land
routines
@@ -163,6 +169,12 @@ based on the tasking capabilities defined in
Task#TA
TASK_A2A:GetGoalTotal()
+
+
+
+ TASK_A2A:GetMarkInfo(TaskInfoID, TaskInfo)
+
+
@@ -181,6 +193,12 @@ based on the tasking capabilities defined in Task#TA
TASK_A2A:GetRendezVousZone(TaskUnit)
+
+
+
+ TASK_A2A:GetReportDetail(ReportGroup, TaskInfoID, TaskInfo)
+
+
@@ -535,6 +553,32 @@ The task is given a name and a briefing, that is used in the menu structure and
+
+
+
+
+
+
+TASK_A2A:GetMarkInfo(TaskInfoID, TaskInfo)
+
+
+
+
+
+
+ Parameters
+
+
+
+ TaskInfoID :
+
+
+
+
+ TaskInfo :
+
+
+
@@ -615,6 +659,37 @@ The Zone object where the RendezVous is located on the map.
+
+TASK_A2A:GetReportDetail(ReportGroup, TaskInfoID, TaskInfo)
+
+
+
+
+
+
+ Parameters
+
+
+
+ ReportGroup :
+
+
+
+
+ TaskInfoID :
+
+
+
+
+ TaskInfo :
+
+
+
+
+
+
+
+
TASK_A2A:GetTargetCoordinate(TaskUnit)
diff --git a/docs/Documentation/Task_A2A_Dispatcher.html b/docs/Documentation/Task_A2A_Dispatcher.html
index 10cbbda64..127db2f84 100644
--- a/docs/Documentation/Task_A2A_Dispatcher.html
+++ b/docs/Documentation/Task_A2A_Dispatcher.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Task_A2G.html b/docs/Documentation/Task_A2G.html
index 014905fb4..d055e51b6 100644
--- a/docs/Documentation/Task_A2G.html
+++ b/docs/Documentation/Task_A2G.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
@@ -163,6 +169,12 @@ based on the tasking capabilities defined in Task#TA
TASK_A2G:GetGoalTotal()
+
+
+
+ TASK_A2G:GetMarkInfo(TaskInfoID, TaskInfo)
+
+
@@ -181,6 +193,12 @@ based on the tasking capabilities defined in Task#TA
TASK_A2G:GetRendezVousZone(TaskUnit)
+
+
+
+ TASK_A2G:GetReportDetail(ReportGroup, TaskInfoID, TaskInfo)
+
+
@@ -531,6 +549,32 @@ based on detected enemy ground targets.
+
+
+
+
+
+
+TASK_A2G:GetMarkInfo(TaskInfoID, TaskInfo)
+
+
+
+
+
+
+ Parameters
+
+
+
+ TaskInfoID :
+
+
+
+
+ TaskInfo :
+
+
+
@@ -611,6 +655,37 @@ The Zone object where the RendezVous is located on the map.
+
+TASK_A2G:GetReportDetail(ReportGroup, TaskInfoID, TaskInfo)
+
+
+
+
+
+
+ Parameters
+
+
+
+ ReportGroup :
+
+
+
+
+ TaskInfoID :
+
+
+
+
+ TaskInfo :
+
+
+
+
+
+
+
+
TASK_A2G:GetTargetCoordinate(TaskUnit)
diff --git a/docs/Documentation/Task_A2G_Dispatcher.html b/docs/Documentation/Task_A2G_Dispatcher.html
index 4c9ff113b..fcc1f967d 100644
--- a/docs/Documentation/Task_A2G_Dispatcher.html
+++ b/docs/Documentation/Task_A2G_Dispatcher.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Task_Cargo.html b/docs/Documentation/Task_Cargo.html
index c7e689347..dc67be90b 100644
--- a/docs/Documentation/Task_Cargo.html
+++ b/docs/Documentation/Task_Cargo.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
@@ -553,7 +559,7 @@ based on the tasking capabilities defined in Task#TA
- Core.Cargo#CARGO
+ Core.Cargo#CARGO_GROUP
FSM_PROCESS.Cargo
@@ -567,6 +573,7 @@ based on the tasking capabilities defined in Task#TA
+
FSM_PROCESS.DeployZone
diff --git a/docs/Documentation/Task_PICKUP.html b/docs/Documentation/Task_PICKUP.html
index 01f437f36..4ef412939 100644
--- a/docs/Documentation/Task_PICKUP.html
+++ b/docs/Documentation/Task_PICKUP.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Unit.html b/docs/Documentation/Unit.html
index 4dc9e6940..488725a32 100644
--- a/docs/Documentation/Unit.html
+++ b/docs/Documentation/Unit.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Utils.html b/docs/Documentation/Utils.html
index 321003dae..2733c9330 100644
--- a/docs/Documentation/Utils.html
+++ b/docs/Documentation/Utils.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/Zone.html b/docs/Documentation/Zone.html
index 21e03ca21..672e10322 100644
--- a/docs/Documentation/Zone.html
+++ b/docs/Documentation/Zone.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
@@ -328,6 +334,12 @@
ZONE_BASE:New(ZoneName)
ZONE_BASE constructor
+
+
+
+ ZONE_BASE:SetName(ZoneName)
+
+Sets the name of the zone.
@@ -498,12 +510,30 @@
ZONE_RADIUS:BoundZone(Points, UnBound, CountryID)
Bounds the zone with tires.
+
+
+
+ ZONE_RADIUS.Coalitions
+
+
+
+
+
+ ZONE_RADIUS:CountCoalitions()
+
+
ZONE_RADIUS:FlareZone(FlareColor, Points, Azimuth)
Flares the zone boundaries in a color.
+
+
+
+ ZONE_RADIUS:GetCoalition()
+
+Get the Zone Coalitions.
@@ -546,6 +576,36 @@
ZONE_RADIUS:GetVec3(Height)
Returns the DCSTypes#Vec3 of the ZONE_RADIUS.
+
+
+
+ ZONE_RADIUS:IsAllInZoneOfCoalition(Coalition)
+
+Is All in Zone of Coalition?
+
+
+
+ ZONE_RADIUS:IsAllInZoneOfOtherCoalition(Coalition)
+
+Is All in Zone of Other Coalition?
+
+
+
+ ZONE_RADIUS:IsNoneInZone()
+
+Is None in Zone?
+
+
+
+ ZONE_RADIUS:IsNoneInZoneOfCoalition(Coalition)
+
+Is None in Zone of Coalition?
+
+
+
+ ZONE_RADIUS:IsSomeInZoneOfCoalition(Coalition)
+
+Is Some in Zone of Coalition?
@@ -570,6 +630,18 @@
ZONE_RADIUS.Radius
The radius of the zone.
+
+
+
+ ZONE_RADIUS:Scan(Coalition)
+
+Scan the zone
+
+
+
+ ZONE_RADIUS:SearchZone(EvaluateFunction)
+
+Searches the zone
@@ -679,8 +751,10 @@
+
Each zone implements two polymorphic functions defined in Zone#ZONE_BASE :
@@ -1302,6 +1376,33 @@ Name of the zone.
#ZONE_BASE :
self
+
+
+
+
+
+
+ZONE_BASE:SetName(ZoneName)
+
+
+
+
+Sets the name of the zone.
+
+ Parameter
+
+ Return value
+
+#ZONE_BASE :
+
+
@@ -1886,6 +1987,33 @@ If true the tyres will be destroyed.
#ZONE_RADIUS :
self
+
+
+
+
+
+
+
+ZONE_RADIUS.Coalitions
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_RADIUS:CountCoalitions()
+
+
+
+
+
+
@@ -1930,6 +2058,27 @@ self
+
+ZONE_RADIUS:GetCoalition()
+
+
+
+
+Get the Zone Coalitions.
+
+
+Returns nil if there are none ot two coalitions in the zone!
+
+ Return value
+
+
+Coalitions
+
+
+
+
+
+
ZONE_RADIUS:GetRadius()
@@ -2120,6 +2269,128 @@ The height to add to the land height where the center of the zone is located.Dcs.DCSTypes#Vec3 :
The point of the zone.
+
+
+
+
+
+
+ZONE_RADIUS:IsAllInZoneOfCoalition(Coalition)
+
+
+
+
+Is All in Zone of Coalition?
+
+ Parameter
+
+ Return value
+
+#boolean:
+
+
+
+
+
+
+
+
+ZONE_RADIUS:IsAllInZoneOfOtherCoalition(Coalition)
+
+
+
+
+Is All in Zone of Other Coalition?
+
+ Parameter
+
+ Return value
+
+#boolean:
+
+
+
+
+
+
+
+
+ZONE_RADIUS:IsNoneInZone()
+
+
+
+
+Is None in Zone?
+
+ Return value
+
+#boolean:
+
+
+
+
+
+
+
+
+ZONE_RADIUS:IsNoneInZoneOfCoalition(Coalition)
+
+
+
+
+Is None in Zone of Coalition?
+
+ Parameter
+
+ Return value
+
+#boolean:
+
+
+
+
+
+
+
+
+ZONE_RADIUS:IsSomeInZoneOfCoalition(Coalition)
+
+
+
+
+Is Some in Zone of Coalition?
+
+ Parameter
+
+ Return value
+
+#boolean:
+
+
@@ -2232,6 +2503,48 @@ self
+
+ZONE_RADIUS:Scan(Coalition)
+
+
+
+
+Scan the zone
+
+ Parameter
+
+
+
+
+
+
+
+ZONE_RADIUS:SearchZone(EvaluateFunction)
+
+
+
+
+Searches the zone
+
+ Parameter
+
+
+
+ EvaluateFunction :
+
+
+
+
+
+
+
+
ZONE_RADIUS:SetRadius(Radius)
diff --git a/docs/Documentation/ZoneCaptureCoalition.html b/docs/Documentation/ZoneCaptureCoalition.html
new file mode 100644
index 000000000..69edb77de
--- /dev/null
+++ b/docs/Documentation/ZoneCaptureCoalition.html
@@ -0,0 +1,265 @@
+
+
+
+
+
+
+
+
+
+
+
+
Module ZoneCaptureCoalition
+
+
Functional (wIP) -- Base class that models processes to capture a Zone for a Coalition, guarded by another Coalition.
+
+
+
+
+
+
ZONECAPTURE COALITION models processes that have an objective with a defined achievement involving a Zone. Derived classes implement the ways how the achievements can be realized.
+
+
+
+
Author: Sven Van de Velde (FlightControl)
+
+
+
+
+
Global(s)
+
+
+ ZONE_CAPTURE_COALITION
+
+
+
+ZONECAPTURE COALITION models processes that have an objective with a defined achievement involving a Zone.
+
+
+
+
+
+
+
Global(s)
+
+
+
+ #ZONE_CAPTURE_COALITION
+
+ZONE_CAPTURE_COALITION
+
+
+
+
+
+
+ZONECAPTURE COALITION models processes that have an objective with a defined achievement involving a Zone.
+
+
+Derived classes implement the ways how the achievements can be realized.
+
+1. ZONECAPTURE COALITION constructor
+
+
+
+2. ZONECAPTURE COALITION is a finite state machine (FSM).
+
+2.1 ZONECAPTURE COALITION States
+
+2.2 ZONECAPTURE COALITION Events
+
+
+
+
+
+
+
+
Field(s)
+
+
+
+
+ZONE_CAPTURE_COALITION:New(Zone, Coalition)
+
+
+
+
+ZONECAPTURE COALITION Constructor.
+
+ Parameters
+
+ Return value
+
+#ZONECAPTURE COALITION :
+
+
+
+
+
+
+
+
+
+ZONE_CAPTURE_COALITION.States
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_CAPTURE_COALITION:onenterCaptured()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/Documentation/ZoneGoal.html b/docs/Documentation/ZoneGoal.html
new file mode 100644
index 000000000..43f76e34c
--- /dev/null
+++ b/docs/Documentation/ZoneGoal.html
@@ -0,0 +1,565 @@
+
+
+
+
+
+
+
+
+
+
+
+
Module ZoneGoal
+
+
Functional (WIP) -- Base class that models processes to achieve goals involving a Zone.
+
+
+
+
+
+
ZONE_GOAL models processes that have a Goal with a defined achievement involving a Zone.
+Derived classes implement the ways how the achievements can be realized.
+
+
+
+
Author: Sven Van de Velde (FlightControl)
+
+
+
+
+
Global(s)
+
+
+ ZONE_GOAL
+
+ZONE_GOAL class, extends Fsm#FSM
+
+ZONE_GOAL models processes that have a Goal with a defined achievement involving a Zone.
+
+
+
+
+
+
+
Global(s)
+
+
+
+ #ZONE_GOAL
+
+ZONE_GOAL
+
+
+
+
+ZONE_GOAL class, extends Fsm#FSM
+
+ZONE_GOAL models processes that have a Goal with a defined achievement involving a Zone.
+
+
+Derived classes implement the ways how the achievements can be realized.
+
+1. ZONE_GOAL constructor
+
+
+
+2. ZONE_GOAL is a finite state machine (FSM).
+
+2.1 ZONE_GOAL States
+
+
+
+2.2 ZONE_GOAL Events
+
+
+
+
+
+
+
+
+
+
+
+
Field(s)
+
+
+
+
+ZONE_GOAL:Flare(FlareColor)
+
+
+
+
+Flare the center of the zone.
+
+ Parameter
+
+
+
+
+
+
+
+ZONE_GOAL:GetZone()
+
+
+
+
+Get the Zone
+
+ Return value
+
+Core.Zone#ZONE_BASE :
+
+
+
+
+
+
+
+
+ZONE_GOAL:GetZoneName()
+
+
+
+
+Get the name of the ProtectZone
+
+ Return value
+
+#string:
+
+
+
+
+
+
+
+
+
+ZONE_GOAL.Goal
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL:MonitorDestroyedUnits()
+
+
+
+
+Activate the event UnitDestroyed to be fired when a unit is destroyed in the zone.
+
+
+
+
+
+
+
+ZONE_GOAL:New(Zone)
+
+
+
+
+ZONE_GOAL Constructor.
+
+ Parameter
+
+ Return value
+
+#ZONE_GOAL :
+
+
+
+
+
+
+
+
+ZONE_GOAL:OnAfterDestroyedUnit(From, Event, To, DestroyedUnit, PlayerName)
+
+
+
+
+DestroyedUnit Handler OnAfter for ZONE_GOAL
+
+ Parameters
+
+
+
+
+
+
+
+ZONE_GOAL:Smoke(SmokeColor)
+
+
+
+
+Smoke the center of theh zone.
+
+ Parameter
+
+
+
+
+
+
+
+
+ZONE_GOAL.SmokeColor
+
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL.SmokeScheduler
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL.SmokeTime
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL:StatusSmoke()
+
+
+
+
+Check status Smoke.
+
+
+
+
+
+
+ Core.Zone#ZONE_BASE
+
+ZONE_GOAL.Zone
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL:__Destroyed(EventData)
+
+
+
+
+
+
+ Parameter
+
+
+
+
+
+
+
+ZONE_GOAL:onafterGuard()
+
+
+
+
+When started, check the Smoke and the Zone status.
+
+
+
+
+
+
+
+
+
diff --git a/docs/Documentation/ZoneGoalCoalition.html b/docs/Documentation/ZoneGoalCoalition.html
new file mode 100644
index 000000000..43149f1be
--- /dev/null
+++ b/docs/Documentation/ZoneGoalCoalition.html
@@ -0,0 +1,1147 @@
+
+
+
+
+
+
+
+
+
+
+
+
Module ZoneGoalCoalition
+
+
Functional (WIP) -- Base class that models processes to achieve goals involving a Zone for a Coalition.
+
+
+
+
+
+
ZONEGOAL COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
+Derived classes implement the ways how the achievements can be realized.
+
+
+
+
Author: Sven Van de Velde (FlightControl)
+
+
+
+
+
Global(s)
+
+
+ ZONE_GOAL_COALITION
+
+
+
+ZONEGOAL COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
+
+
+
+
+
+
+ ZONE_GOAL_COALITION:Attack()
+
+Attack Trigger for ZONEGOAL COALITION
+
+
+
+ ZONE_GOAL_COALITION:Capture()
+
+Capture Trigger for ZONEGOAL COALITION
+
+
+
+ ZONE_GOAL_COALITION.Coalition
+
+
+
+
+
+ ZONE_GOAL_COALITION:Empty()
+
+Empty Trigger for ZONEGOAL COALITION
+
+
+
+ ZONE_GOAL_COALITION:GetCoalition()
+
+Get the owning coalition of the zone.
+
+
+
+ ZONE_GOAL_COALITION:GetCoalitionName()
+
+Get the owning coalition name of the zone.
+
+
+
+ ZONE_GOAL_COALITION:Guard()
+
+Guard Trigger for ZONEGOAL COALITION
+
+
+
+ ZONE_GOAL_COALITION:IsAttacked()
+
+
+
+
+
+ ZONE_GOAL_COALITION:IsCaptured()
+
+
+
+
+
+ ZONE_GOAL_COALITION:IsEmpty()
+
+
+
+
+
+ ZONE_GOAL_COALITION:IsGuarded()
+
+
+
+
+
+ ZONE_GOAL_COALITION:Mark()
+
+Mark.
+
+
+
+ ZONE_GOAL_COALITION.MarkBlue
+
+
+
+
+
+ ZONE_GOAL_COALITION.MarkRed
+
+
+
+
+
+ ZONE_GOAL_COALITION:New(Zone, Coalition)
+
+ZONEGOAL COALITION Constructor.
+
+
+
+ ZONE_GOAL_COALITION:OnAfterAttack(From, Event, To)
+
+Attack Handler OnAfter for ZONEGOAL COALITION
+
+
+
+ ZONE_GOAL_COALITION:OnAfterCapture(From, Event, To)
+
+Capture Handler OnAfter for ZONEGOAL COALITION
+
+
+
+ ZONE_GOAL_COALITION:OnAfterEmpty(From, Event, To)
+
+Empty Handler OnAfter for ZONEGOAL COALITION
+
+
+
+ ZONE_GOAL_COALITION:OnAfterGuard(From, Event, To)
+
+Guard Handler OnAfter for ZONEGOAL COALITION
+
+
+
+ ZONE_GOAL_COALITION:OnBeforeAttack(From, Event, To)
+
+Attack Handler OnBefore for ZONEGOAL COALITION
+
+
+
+ ZONE_GOAL_COALITION:OnBeforeCapture(From, Event, To)
+
+Capture Handler OnBefore for ZONEGOAL COALITION
+
+
+
+ ZONE_GOAL_COALITION:OnBeforeEmpty(From, Event, To)
+
+Empty Handler OnBefore for ZONEGOAL COALITION
+
+
+
+ ZONE_GOAL_COALITION:OnBeforeGuard(From, Event, To)
+
+Guard Handler OnBefore for ZONEGOAL COALITION
+
+
+
+ ZONE_GOAL_COALITION.ScheduleStatusZone
+
+
+
+
+
+ ZONE_GOAL_COALITION:SetCoalition(Coalition)
+
+Set the owning coalition of the zone.
+
+
+
+ ZONE_GOAL_COALITION.SmokeScheduler
+
+
+
+
+
+ ZONE_GOAL_COALITION.States
+
+
+
+
+
+ ZONE_GOAL_COALITION:StatusZone()
+
+Check status Coalition ownership.
+
+
+
+ ZONE_GOAL_COALITION:__Attack(Delay)
+
+Attack Asynchronous Trigger for ZONEGOAL COALITION
+
+
+
+ ZONE_GOAL_COALITION:__Capture(Delay)
+
+Capture Asynchronous Trigger for ZONEGOAL COALITION
+
+
+
+ ZONE_GOAL_COALITION:__Empty(Delay)
+
+Empty Asynchronous Trigger for ZONEGOAL COALITION
+
+
+
+ ZONE_GOAL_COALITION:__Guard(Delay)
+
+Guard Asynchronous Trigger for ZONEGOAL COALITION
+
+
+
+ ZONE_GOAL_COALITION:onafterGuard()
+
+When started, check the Coalition status.
+
+
+
+ ZONE_GOAL_COALITION:onenterAttacked()
+
+
+
+
+
+ ZONE_GOAL_COALITION:onenterCaptured()
+
+
+
+
+
+ ZONE_GOAL_COALITION:onenterEmpty()
+
+
+
+
+
+ ZONE_GOAL_COALITION:onenterGuarded()
+
+Bound.
+
+
+
+
+
Global(s)
+
+
+
+ #ZONE_GOAL_COALITION
+
+ZONE_GOAL_COALITION
+
+
+
+
+
+
+ZONEGOAL COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
+
+
+Derived classes implement the ways how the achievements can be realized.
+
+1. ZONEGOAL COALITION constructor
+
+
+
+2. ZONEGOAL COALITION is a finite state machine (FSM).
+
+2.1 ZONEGOAL COALITION States
+
+
+ Captured : The Zone has been captured by an other coalition.
+ Attacked : The Zone is currently intruded by an other coalition. There are units of the owning coalition and an other coalition in the Zone.
+ Guarded : The Zone is guarded by the owning coalition. There is no other unit of an other coalition in the Zone.
+ Empty : The Zone is empty. There is not valid unit in the Zone.
+
+
+2.2 ZONEGOAL COALITION Events
+
+
+ Capture : The Zone has been captured by an other coalition.
+ Attack : The Zone is currently intruded by an other coalition. There are units of the owning coalition and an other coalition in the Zone.
+ Guard : The Zone is guarded by the owning coalition. There is no other unit of an other coalition in the Zone.
+ Empty : The Zone is empty. There is not valid unit in the Zone.
+
+
+2.3 ZONEGOAL COALITION State Machine
+
+
+
+
+
+
+
+
Field(s)
+
+
+
+
+ZONE_GOAL_COALITION:Attack()
+
+
+
+
+Attack Trigger for ZONEGOAL COALITION
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:Capture()
+
+
+
+
+Capture Trigger for ZONEGOAL COALITION
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION.Coalition
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:Empty()
+
+
+
+
+Empty Trigger for ZONEGOAL COALITION
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:GetCoalition()
+
+
+
+
+Get the owning coalition of the zone.
+
+ Return value
+
+DCSCoalition.DCSCoalition#coalition :
+Coalition.
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:GetCoalitionName()
+
+
+
+
+Get the owning coalition name of the zone.
+
+ Return value
+
+#string:
+Coalition name.
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:Guard()
+
+
+
+
+Guard Trigger for ZONEGOAL COALITION
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:IsAttacked()
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:IsCaptured()
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:IsEmpty()
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:IsGuarded()
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:Mark()
+
+
+
+
+Mark.
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION.MarkBlue
+
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION.MarkRed
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:New(Zone, Coalition)
+
+
+
+
+ZONEGOAL COALITION Constructor.
+
+ Parameters
+
+ Return value
+
+#ZONEGOAL COALITION :
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:OnAfterAttack(From, Event, To)
+
+
+
+
+Attack Handler OnAfter for ZONEGOAL COALITION
+
+ Parameters
+
+
+
+#string From :
+
+
+
+
+#string Event :
+
+
+
+
+#string To :
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:OnAfterCapture(From, Event, To)
+
+
+
+
+Capture Handler OnAfter for ZONEGOAL COALITION
+
+ Parameters
+
+
+
+#string From :
+
+
+
+
+#string Event :
+
+
+
+
+#string To :
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:OnAfterEmpty(From, Event, To)
+
+
+
+
+Empty Handler OnAfter for ZONEGOAL COALITION
+
+ Parameters
+
+
+
+#string From :
+
+
+
+
+#string Event :
+
+
+
+
+#string To :
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:OnAfterGuard(From, Event, To)
+
+
+
+
+Guard Handler OnAfter for ZONEGOAL COALITION
+
+ Parameters
+
+
+
+#string From :
+
+
+
+
+#string Event :
+
+
+
+
+#string To :
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:OnBeforeAttack(From, Event, To)
+
+
+
+
+Attack Handler OnBefore for ZONEGOAL COALITION
+
+ Parameters
+
+
+
+#string From :
+
+
+
+
+#string Event :
+
+
+
+
+#string To :
+
+
+
+ Return value
+
+#boolean:
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:OnBeforeCapture(From, Event, To)
+
+
+
+
+Capture Handler OnBefore for ZONEGOAL COALITION
+
+ Parameters
+
+
+
+#string From :
+
+
+
+
+#string Event :
+
+
+
+
+#string To :
+
+
+
+ Return value
+
+#boolean:
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:OnBeforeEmpty(From, Event, To)
+
+
+
+
+Empty Handler OnBefore for ZONEGOAL COALITION
+
+ Parameters
+
+
+
+#string From :
+
+
+
+
+#string Event :
+
+
+
+
+#string To :
+
+
+
+ Return value
+
+#boolean:
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:OnBeforeGuard(From, Event, To)
+
+
+
+
+Guard Handler OnBefore for ZONEGOAL COALITION
+
+ Parameters
+
+
+
+#string From :
+
+
+
+
+#string Event :
+
+
+
+
+#string To :
+
+
+
+ Return value
+
+#boolean:
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION.ScheduleStatusZone
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:SetCoalition(Coalition)
+
+
+
+
+Set the owning coalition of the zone.
+
+ Parameter
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION.SmokeScheduler
+
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION.States
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:StatusZone()
+
+
+
+
+Check status Coalition ownership.
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:__Attack(Delay)
+
+
+
+
+Attack Asynchronous Trigger for ZONEGOAL COALITION
+
+ Parameter
+
+
+
+#number Delay :
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:__Capture(Delay)
+
+
+
+
+Capture Asynchronous Trigger for ZONEGOAL COALITION
+
+ Parameter
+
+
+
+#number Delay :
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:__Empty(Delay)
+
+
+
+
+Empty Asynchronous Trigger for ZONEGOAL COALITION
+
+ Parameter
+
+
+
+#number Delay :
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:__Guard(Delay)
+
+
+
+
+Guard Asynchronous Trigger for ZONEGOAL COALITION
+
+ Parameter
+
+
+
+#number Delay :
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:onafterGuard()
+
+
+
+
+When started, check the Coalition status.
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:onenterAttacked()
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:onenterCaptured()
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:onenterEmpty()
+
+
+
+
+
+
+
+
+
+
+
+
+ZONE_GOAL_COALITION:onenterGuarded()
+
+
+
+
+Bound.
+
+
+
+
+
+
+
+
+
diff --git a/docs/Documentation/env.html b/docs/Documentation/env.html
index a88348417..21f208d56 100644
--- a/docs/Documentation/env.html
+++ b/docs/Documentation/env.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+
Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+
Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+
TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+
ZoneCaptureCoalition
+
ZoneGoal
+
ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/index.html b/docs/Documentation/index.html
index f84a58bb2..e11c3bd84 100644
--- a/docs/Documentation/index.html
+++ b/docs/Documentation/index.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+
Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+
Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+
TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+
ZoneCaptureCoalition
+
ZoneGoal
+
ZoneGoalCoalition
env
land
routines
@@ -130,7 +136,7 @@
-
AI CAP classes makes AI Controllables execute a Combat Air Patrol.
+
AI CAP classes makes AI Groups execute a Combat Air Patrol.
@@ -160,7 +166,7 @@
-AI PATROL classes makes AI Controllables execute an Patrol.
+AI PATROL classes makes AI Groups execute an Patrol.
@@ -443,6 +449,12 @@ even when there are hardly any players in the mission.
Core -- The FSM (F inite S tate M achine) class and derived FSM_ classes
are design patterns allowing efficient (long-lasting) processes and workflows.
+
+
+
+ Goal
+
+Core (WIP) -- Base class to allow the modeling of processes to achieve Goals.
@@ -515,6 +527,12 @@ are design patterns allowing efficient (long-lasting) processes and workflows.
Process_Pickup
+
+
+
+ Protect
+
+Functional -- The PROTECT class handles the protection of objects, which can be zones, units, scenery.
@@ -627,6 +645,12 @@ and creates a CSV file logging the scoring events and results for use at team or
Task
Tasking -- This module contains the TASK class, the main engine to run human taskings.
+
+
+
+ TaskZoneCapture
+
+Tasking - The TASK_Protect models tasks for players to protect or capture specific zones.
@@ -682,6 +706,24 @@ which are excellent tools to be reused in an OO environment!.
Zone
Core -- ZONE classes define zones within your mission of various forms , with various capabilities .
+
+
+
+ ZoneCaptureCoalition
+
+Functional (wIP) -- Base class that models processes to capture a Zone for a Coalition, guarded by another Coalition.
+
+
+
+ ZoneGoal
+
+Functional (WIP) -- Base class that models processes to achieve goals involving a Zone.
+
+
+
+ ZoneGoalCoalition
+
+Functional (WIP) -- Base class that models processes to achieve goals involving a Zone for a Coalition.
diff --git a/docs/Documentation/land.html b/docs/Documentation/land.html
index faf8ff947..9b94e02ea 100644
--- a/docs/Documentation/land.html
+++ b/docs/Documentation/land.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines
diff --git a/docs/Documentation/routines.html b/docs/Documentation/routines.html
index d055a38df..934850b4e 100644
--- a/docs/Documentation/routines.html
+++ b/docs/Documentation/routines.html
@@ -60,6 +60,7 @@
Escort
Event
Fsm
+ Goal
Group
Identifiable
Menu
@@ -72,6 +73,7 @@
Positionable
Process_JTAC
Process_Pickup
+ Protect
Radio
Rat
Route
@@ -89,6 +91,7 @@
Static
StaticObject
Task
+ TaskZoneCapture
Task_A2A
Task_A2A_Dispatcher
Task_A2G
@@ -98,6 +101,9 @@
Unit
Utils
Zone
+ ZoneCaptureCoalition
+ ZoneGoal
+ ZoneGoalCoalition
env
land
routines