From 8cceee49eace9a1efc64c00f17b09404a9b2b5b4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 6 Sep 2022 09:53:22 +0200 Subject: [PATCH] Merge remote-tracking branch 'origin/master' into develop # Conflicts: # Moose Development/Moose/AI/AI_A2A_Dispatcher.lua # Moose Development/Moose/AI/AI_A2G_Dispatcher.lua # Moose Development/Moose/AI/AI_CAP.lua # Moose Development/Moose/AI/AI_CAS.lua # Moose Development/Moose/AI/AI_Patrol.lua # Moose Development/Moose/Core/Base.lua # Moose Development/Moose/Core/Beacon.lua # Moose Development/Moose/Core/Database.lua # Moose Development/Moose/Core/Fsm.lua # Moose Development/Moose/Core/MarkerOps_Base.lua # Moose Development/Moose/Core/Menu.lua # Moose Development/Moose/Core/Message.lua # Moose Development/Moose/Core/Point.lua # Moose Development/Moose/Core/ScheduleDispatcher.lua # Moose Development/Moose/Core/Scheduler.lua # Moose Development/Moose/Core/Set.lua # Moose Development/Moose/Core/Spawn.lua # Moose Development/Moose/Core/Zone.lua # Moose Development/Moose/DCS.lua # Moose Development/Moose/Functional/Detection.lua # Moose Development/Moose/Functional/Mantis.lua # Moose Development/Moose/Functional/Range.lua # Moose Development/Moose/Functional/Scoring.lua # Moose Development/Moose/Functional/Sead.lua # Moose Development/Moose/Modules.lua # Moose Development/Moose/Ops/ATIS.lua # Moose Development/Moose/Ops/Airboss.lua # Moose Development/Moose/Sound/UserSound.lua # Moose Development/Moose/Utilities/Enums.lua # Moose Development/Moose/Utilities/FiFo.lua # Moose Development/Moose/Utilities/Profiler.lua # Moose Development/Moose/Utilities/Routines.lua # Moose Development/Moose/Utilities/STTS.lua # Moose Development/Moose/Utilities/Utils.lua # Moose Development/Moose/Wrapper/Airbase.lua # Moose Development/Moose/Wrapper/Controllable.lua # Moose Development/Moose/Wrapper/Group.lua # Moose Development/Moose/Wrapper/Marker.lua # Moose Development/Moose/Wrapper/Positionable.lua # Moose Development/Moose/Wrapper/Unit.lua # Moose Setup/Moose.files --- .vs/VSWorkspaceState.json | 10 + .vs/slnx.sqlite | Bin 0 -> 98304 bytes Moose Development/Moose/.lua-format | 33 + Moose Development/Moose/AI/AI_A2A_Cap.lua | 6 +- .../Moose/AI/AI_A2A_Dispatcher.lua | 668 ++-- Moose Development/Moose/AI/AI_A2A_Gci.lua | 6 +- Moose Development/Moose/AI/AI_A2A_Patrol.lua | 12 +- .../Moose/AI/AI_A2G_Dispatcher.lua | 514 +-- Moose Development/Moose/AI/AI_A2G_SEAD.lua | 4 +- .../Moose/AI/AI_Air_Dispatcher.lua | 122 +- Moose Development/Moose/AI/AI_Air_Engage.lua | 4 +- Moose Development/Moose/AI/AI_Air_Patrol.lua | 6 +- Moose Development/Moose/AI/AI_BAI.lua | 8 +- Moose Development/Moose/AI/AI_CAP.lua | 8 +- Moose Development/Moose/AI/AI_CAS.lua | 8 +- Moose Development/Moose/AI/AI_Patrol.lua | 28 +- Moose Development/Moose/Cargo/Cargo.lua | 12 +- Moose Development/Moose/Core/Base.lua | 546 ++- Moose Development/Moose/Core/Beacon.lua | 14 +- Moose Development/Moose/Core/Database.lua | 354 +- Moose Development/Moose/Core/Event.lua | 8 +- Moose Development/Moose/Core/Fsm.lua | 875 +++-- Moose Development/Moose/Core/Goal.lua | 89 +- Moose Development/Moose/Core/Menu.lua | 1 - Moose Development/Moose/Core/Message.lua | 318 +- Moose Development/Moose/Core/Report.lua | 27 +- .../Moose/Core/ScheduleDispatcher.lua | 195 +- Moose Development/Moose/Core/Scheduler.lua | 212 +- Moose Development/Moose/Core/Set.lua | 721 ++-- Moose Development/Moose/Core/Settings.lua | 235 +- Moose Development/Moose/Core/Spawn.lua | 2947 ++++++++--------- .../Moose/Functional/Artillery.lua | 2 +- .../Moose/Functional/Detection.lua | 1550 +++++---- Moose Development/Moose/Functional/Mantis.lua | 2 +- Moose Development/Moose/Functional/RAT.lua | 10 +- Moose Development/Moose/Functional/Range.lua | 7 + .../Moose/Functional/Scoring.lua | 766 ++--- .../Moose/Functional/Warehouse.lua | 14 +- .../Moose/Functional/ZoneCaptureCoalition.lua | 27 +- .../Moose/Functional/ZoneGoalCoalition.lua | 110 +- Moose Development/Moose/Ops/ATIS.lua | 1687 +++++----- Moose Development/Moose/Ops/Airboss.lua | 2 +- Moose Development/Moose/Tasking/Mission.lua | 2 +- Moose Development/Moose/Tasking/Task.lua | 2 +- Moose Development/Moose/Tasking/Task_A2A.lua | 244 +- .../Moose/Tasking/Task_A2A_Dispatcher.lua | 297 +- Moose Development/Moose/Tasking/Task_A2G.lua | 244 +- .../Moose/Tasking/Task_A2G_Dispatcher.lua | 399 ++- .../Moose/Utilities/Profiler.lua | 90 +- .../Moose/Utilities/Routines.lua | 2852 ++++++++-------- Moose Development/Moose/Utilities/STTS.lua | 236 +- Moose Development/Moose/Wrapper/Airbase.lua | 43 +- .../Moose/Wrapper/Controllable.lua | 885 +++-- Moose Development/Moose/Wrapper/Group.lua | 50 +- .../Moose/Wrapper/Identifiable.lua | 2 +- Moose Development/Moose/Wrapper/Marker.lua | 345 +- .../Moose/Wrapper/Positionable.lua | 513 +-- Moose Development/Moose/Wrapper/Static.lua | 5 +- Moose Development/Moose/Wrapper/Unit.lua | 34 +- 59 files changed, 8939 insertions(+), 9472 deletions(-) create mode 100644 .vs/VSWorkspaceState.json create mode 100644 .vs/slnx.sqlite create mode 100644 Moose Development/Moose/.lua-format diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 000000000..4e376c288 --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,10 @@ +{ + "ExpandedNodes": [ + "", + "\\Moose Development", + "\\Moose Development\\Moose", + "\\Moose Development\\Moose\\Ops" + ], + "SelectedNode": "\\Moose Development\\Moose\\Ops\\Airboss.lua", + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..cf066726d3907bcc3ac74baa21f0e3e08e266d0c GIT binary patch literal 98304 zcmeHw3w&codGC>qbo4rp-SyhCE!md$?cJ5vmS5{^Hi=f&?keloTJ~->izAMuW3OUa z((18~JW-Y;Bq4zi0;C}%5V8pY!Ug)GDW#?KL7^>_wm_imuW0Yu_?enq%|9R7anjoCd{3=eaQS0k=Ap)BPRc7v zW8=h27-vXc!X(5vABs(lkE6a$PQ{|hc+Z{td6z#Nw%r@9$O}28zU%d$1=X0@=z;Ng zgPsfC9R!zEASN>NfbVSlP<$pdJ(HM-&YTDxiJu5X=Vqr8lc07YJ~<0k!SCGo_-<9p zbg6JAn^8(;X^n@lph1|4AB@k$Cu8ws=%gkQ&sVaQ^Yc6O3Q*Bwh`_kbO^+cU0ESp} zEN&r<#mD0YNi&83IC(5O6FVH8*%28U>d_6Z!&MVGSk2|mAFax{>|$2QG*Vz44^8d} zc!`0R)?`;;{AxbI+f&=)bNN#+uTpqkVN26;KChHwa{82#T5UR+W%Z1_ zb(YnW*VJR0bM-Rdd#hWY@3}DN#WU`%c|7ClrRqQXtTV2j4DD+(uAXF`Gp}OCsRP=` z=2f0!nQ^$?OFPWv`HMEK&7IQx-C~W#s5`G#v1lO0Eu*c=!(__0s!k(x#i}a;Xp;ZvWyy~#dO(u@c>31HDM;9`= zmqt@N2KN}PhL~Kpkm%H7N~sJJZPm(u2jh16dwOga_F+HNlo9pTPadleT8SnP)ClH5 zdKI2WJVQ5_bjMg``!2c4YVGP>eKo>p$gXg?{PD0hWc7}mnwCpSzLG-Ac0JAAaQy1I z%oB@8wsFJW9G;Cn6{c92CnW-QVswCe42S|><7*)&D! zNw00H5StAze-%5CHOIhM>+m{;_EYHRHEankZ-q~k5jOLo1Bun!z;U@;nOZ0-r85d_ zi;R_5-7SX3;s>L1V4}R4SEhehJTMb-zt&99Dl? z&4=9xZ}*A7T9dYC+RpiR?XsP~o>M*zdo(3gR!&!yJnY)6A6bjv)Dd^&!TEQCWKZpo z&E*dSY^N_L- zytj5c9ZYQfn6gk^nw3jSN~Jsp=Wq2aSw5niKRJKlxWmcpyPHL)^MsLCOOp6|xljEG zqibEE(;^FlgQJ$-Bol@v2E!wRk;RA-i3|;=BjLS$Ljxm2U_@rPe=t1Qzc>U; zqXXfgy@Lab%J6_9?~R1h!;!tiBk6Q_ctqYC9vn>%hvm^sUpTU$EDj8hX7(;Dj)XI# zk^V?}C=(tX8IplX0j80mfpBDTXz%dC;=*Fz2rwNd&zDOk0KXgT8;EKM! z7Mp|F?oUDnW<^$`vi-dSy+i2yZy6;)53j1K{zSIg|N`X|D698|GWHS{O$ak`78KV+{m}JQ*QZ<$ zxr(kC*Ql%0W#eAt{*wDm?lG>$Epg-AR?g*o$@y2#-*&#wdAn0_9(L|>Zg9Nh_^RVm zj`ujuIj(n%ID!rb`vdlm*^jXIv8UM+?0$9&>#_g9{w4c|?04GJ_Wky5yW94X?QdVzttntI7fQ3$D+|>8a~GZzJ>>Ig>3U zi}010Qx@|b?Ks#FyVx%C!~>C(s+O2^Hrpi)j(mBUJ|fXMAyyWKZ&Og|D- z(uFfh>HMsmUsg&M7XNvU=>V44?D&|Ka84y8qcc{*Swe`;CXyCH`x%be4635p(n6tJ zw$QdzIc5ZC*BA22p@N)?!7qNuA949LdqWkD*<uq!v)zPdLizW21b|G(Rrmes+-5{MjRVc|B z3(21+*d&!YLi-BACYdgj;29>1P)m+u0-$duI%}zKUDk#@zht#Acq6fYx~x<)h3IU| zA{WRKj3!{rSKuK?3$gnYafyj6bS$ULmgFovaBX2}TO$3Nkl{IeJd8RtEsMm|gN1We z5rv4v%h12N1~a6=F{J`uivui?3s|)V)1*5(i$!;EL8F$lOL_Rxl7-qX6JTRGMb6I^ zEwqj|li2|Pr4E(Q+_aE`DWW)9s$_FHm>w2t_YK5uWG=jkLNy+-G;8kapC_Y@SS*CV zo5%*|#DX)@FkFeha)BXpX7il8&0@V0#%vyT`}IN+X}O&5w4;@~>pt}8euZss#gU9Gf# zlw*#r!Muk4oqIVZwuVS_8s86eTlS3TKogryw!po*n3Ku!r3-8uuIr>BV1cV^NOuIH zxW45rgSsQA42_x#43K%Hp>AoTEn*t2X$z^dpDcVJcR+?tr0d3@txp#{5KUM_H|^n= z0RkT&kV>Ae%9#?GwmJ%-UfmfGPlJgN3R^hh`6YE((sgXvtpk8`tbt?OF5OVzvKZRA zQ}1ImawwIINAWJrXrRYv0O)Hp(6K|k5hhYdOTF87Y`2i>!3u3Nx*3T!0-|H9NkKGq zD4n%*V#^kjk-8H09ODNGd{Q8_a6Z`3&M{3;J5^LQKwW#AdbcZ}yDZ)?>HO`jgmx%b zSdep8TALp^lbbtO&ExHsoYTcxTF5PLEQXgoy}qfLW12xERwyZY<;IP~Qao|>+VxEw zvmRKZ3B9Ui1LK5ZB`OB^!<^Ahh)3;ATa zlr5rX{*exyDIk*dB7+`qSR#{NCk6%NCT=+Lz=p7_geD6WrOasqwvi{DnkW>?3aJIC z{XTSqc60bt4GMKarpfG}s@hoq300NJBOPZ8rDcuQW=9ryID{%}&C<)Iv`(_Aq@`?Sg?Jv2 z;Lz*8-Rt@k^Bn82xtLx zxIq{bdWBYj;eW#a9sd{nXZa`j2lx^{!H0Ry_dVYieV_Jy*!M0t{ZIRbece9E@o%id z6Zb@14||{TzK8u^;y?Io-hcLf%lifIr#zb+-{-#S_$%>mU2k*U?7dU`GwBAY-?PsB zAKY)bUu3`UoOgf2d)EDB@3Q#3_)+f-jz4sKT>6sxvyR8SN4-(^Med^1Dn2UBO1}Z8 z{blJX_G|3t*^jXkY@hpK$Nl0%-T}v1=Mnb>SIpJt3c9QAti((2b!DUjj-3ucDtouM zjygvji(ZfCW%mj9xaWtS7u@^Bdt7eMmp#Aj`6%~u$1&$FcMtbIuEHJX;@l31K>W!0 z4d>^aPdM*z=D54WTb-?*#~g<}Z|A-*7Mvc>&1|#VFQz?t&vDnkyMEyMn(H$FG5vXi zBH;QE)4Yz|Y=>L47T#b!(aw(8k0p=8^?i5<0*0|OO3O$2-{lDsk*QyelM|Hd8;=#YPD=L}WtRu}+3X{S@=j5F4}7RVBSS!tiV}e4U51 zP2zfJrZXQw*5lgVMf5-!^UOwem#Sx0S%G~ZES1dDJR7jb)46Q1tS}e-Y`=X0J*k#j zK>>q8zTCSYSD2?z=PXNXDAF70?dvUO7MTyD&NU)t3h8sqlLFge*A6?(ha|S!uJsOV z{2;P1nN}bsfltlM6R0cGg)$Bnq1PPZt~`#!rWVkHcKGuL(B}!cq(--x$BUzsPpk^5X|G(t_^z_`b`$cLTfK zuEU3|c@OGha&{KqWtoT9vs>+G#{z*CBJyrj10h~C3V6H6JcM+{^VJo?@GfK-+B&2l z1_OIa!t@}*71xr@G{kQ5W$paTyt9+N+Dx@>1(|oWvIAyvObzQX4>YkoDj_;^XbN}W ze&p6_OE`>{(n1`}+mY{CNOcX}hg<_Qpsi*d@m|z(jfk7N$HZR0HDumqVvl0>yS17` zei26onY$3!NF}D~8pB*fBqK}!&rkM4dZj*AnL$nK z)d-a|sWHVQ7f?q`aYz{rur>DhW+Y^ZH>OZ~Xbh||#v2eqs2Vjjk9uj2CB6w&nB#{h z5l(u{@Olid8Yet~hr=2hybiJJ@xbFqn>hw}48fzu{pOIxS~L%@L$mGds9s~8-${g} z5!0JN&FFEwqX-^T>~0!asm1H2&<8C>H;IRb#N{TC4m}n(j!ZSj-;SUbjhNfDNR1X} zOCZznQmNf;4kNH}jU}FT2n`gj)^836^(sAX7Dtv~E;J@%cqGm7u^39DK`g=&6FYz= zA5^LlE>j#Vx`mxEQ4Ao>v9D{;)HB2lCS=0ZstJbkUWL#AcJ+Y2AI)#{(GttL5+w-r zT5Ei1UxGsKJE#KCFUG z6sTHKtbm1QtdIve0FNWt8S}3c%iYxc6OMm)DCRdOtQpqw&{#| z+-9rZQ2h;gi-}o{&uqp{XpYHj!c3MpOt-m;#9l(Uht_yY7n=8KjHMIJHa)J=fhg!E z53zuthxL;iaKObj*&t@pj#m~VPSS=xn_?ra2wWo`;z!EV7)XnoZMLDfM>Eoj9cLrQ zu7`ncLR@N0V*^r%#zl=|tVc98cCoIN9U^y}x_E_ze9j!B5D`?CeX)QV#XE8yEnHgs z!H4<bp@}$Xau- zfYth}MhNU);Ry4b?|Hj$2(thGD*Z(Ik@TYU9qI3+uS;L`eO~&K^heU?c&G0_d@uWc z?E8WA8R^r~$EByG55hME9+uuA-6LI)Zk5hTr=?{{ky6qLzKQ3hq%gpUYM3Xciz6&{3dE!-*GE}R$25XV>&WcUWdoG>L^ zEsP2S@J)v8zUPH*As{phlHe9>{J--*<$uI~kN-<;vFY=$~pXYy_|2ThzvnT=- z0g3=cfFeKm9O>0~}cE*~V9C&=Y-a`^zcJVq|>CznUb<=4pN5psDSx^yt_ zC71V*%fsaIZgP2uT;7E)LFPenc_+ENgIpdUm;1@(?c{PFx!g-G_mIom$mMQwxrTxM~33YSZ`T*Re<%M32lxLm-cjLSFU zGKI?K8*n*~%QxZjBrdPVwJ}E~jugiOUIGj^pwO zF0aL90+kyNkD--*i(Tn2F&z-2ow+i=;6OFu4KaM_H@jks*W8*jQB9z1&xr=WNf~x!)JRBfSLY{@bLK*alhu zmxOPLe=UAV_+$8w01ped3a5mmFaqb}b^O2Z-{!x-Kg&M?=id^a;$!@7Ui7`_`y=1` zedm2AeKB9e*XjLF?>D@^3mN|h*{|51b-%-X6ZZ*k!0WYtTKt^1=FNGJd$02Dbf02> z&-pj(Me*0UC%6YaFN+tYX{p0K>%PYM8TWSkhaFLOi|0kR+w)EFVex|J&)A3ATctm7 zz059qp7(sj_4m$;l3hG4jYev^H{_V2bATwh{Oh)bSF*r+4y`YkTW zUB&HiJ?(l#{1@q++&QVw{v-RJOQ$?<4o&PNEXTR(I zlI^pu{o)??@4BCaCm4L(Pr0uH#FzQQ{T}1r;Mi<4r>SA-4?0F{jT6;yL+Ensv#aUi z)v0Q@QG^`(HF|?=_0!D9HaVgyL*s-s+*~$0Vs$KaIqQqevwla|wkmB6cOu*$Q{vji zJk#RXrLu=UCdLRj(xUs#;Wwwcn{oy-TZ&89?lC-VgEUVSRNlX)Bq8_DcW<^#CKNN0C4k73#R zgmx$M{&kKnn~~D)WFEzg^-1kc=GU-FBdy)ZJfeGvn%M4S-iNj6scpCmZ**+8S(Dox z%zJR>we)re^Kg@6D}ZH6aCb28#&zZtcL(zj)~+YHJD7K2TX8nnl$V7&Iz}JYlSsZL z(H&&o8FE|=R-yDbp<6@gLFOIpjsXjUmh28P4>UV^G?JF?4l?)S>7i=0CcJ~p+c!C4 znh1HQq%L+JWbVW6gymN!y@SlX7!8e#;>bbf9i#3;OV6%!UN1(nqd;w zr@{lb=`=2n=*4U*#RH;FI_^|W~VdGb+v zTIn4AsHMi+&%!s>9Pphtb9Vf%85g8j1F$mUOzW!jxWDQLbPSr7Aoo``fXD0g2zYzl5999spXzWFrIVSFj>IQ;t&D z_&Y8B3^qh3VARrTohga#U%(wSrP2K|b_;7F-T!7RYDuO0Q@HKb$@JzMFos$>y?Gw@ z-<(iyeiN=Tr_`HIVkGsXdh_*UAhgGCPLScVCf1v;!$Nv$z4GkG0 z+z(5Fy?Hk17)4`D9lLwjyqsZxi=rd78ptQ=4-J+E$!Z%z+J*ivzB<@co+{2uCt`xHy*-+ ziwS!2ed9r+Qcu5cjAP3%Cvu*RW5ff@Dfp&X3v3}!me!JlZ#sY{IaIGs!#72@IVP}( zN;BX#C*qr~!4uq2VXz@%ny%K2F{VP(RTw7_iK&m7?8oZ~s<0&Fo38AD-3YRp)LT>O zP5U-E;wGxmu%47}x?+dpXk(T-U8CJ_FEd&)>CztE*sH-oY%z0~^65<@R+9}jjK;7w z3`B{hdq^7wwc4z35c`>i0oF2L;71<(h~Oy)JmxfgQ@_ThJ-5);2Dg9I$@)}%(;jUA ztjYSOUUR*Xu5Su!2Cq)oH|^G%Ra5p&yEN;}N&BXqx{6h4`=%bUuvrrKO*_n$T9>x# z23nH$P1_7+J$>J_)o8MQW6`w5ETBDNv6(opIf=hv6JfNZ@i%l^D%FQ5LZsi;RQ`r8 zysBsqOmyP8tEcn%4opSc7W8mL(C6^OdWXJm#|J!)CL2oX^X+6EGt&Be8?G@W_W4#k z2u5n3_hZG{0}d@-M>A{@&|?kFxDD)h{6@E9J#rAf33F=?E^NT+k?U!XE3C(K?IDGA z?XZz*nCkaQ*n7>%exHazwWRww0ULo&G8~T=O)cfm`P_~s1}FV7oOM}$)=O+N^8Tzx zbaXMM%>O#r-l(a5xVKb`t`^50t-_r9=N$pYoc#CTM%O#GLhGhy1z4;PfuZI9(fj|I zb(eWOrQ#F;iU37`B0v$K2v7tl0u%v?07ZZzKoOt_yrB_5@BgRg|2OmyQR65A6ak6= zMSvne5ugZA1SkR&0g3=cfFf|&2+;ihWkX06Py{Ff6ak6=MSvne5ugZA1SkR&0g3=c z;0=!e&HumQhlmOu5ugZA z1SkR&0g3=cfFeK5K;vc0g3=cfFeK2f zIPnt38IqSU2{F!xVpHSesIQY#v1l^hbLW2E8r zTS#N^@wh?Kj3EF{9*fSz4o7EpM23cXbc5?~)kF?fbGh?Jt8y;8m{l^36j;YYlRE-l zV&J9qSffVMX{B7wE@X44i#n|JY;``kiqGh>89+2qlIP16WkrXuz6wDZGNfM%mg9xA zjOOP|;q2NS(3oO{Ty-VCnoscd)b{vX{uIos6rNYu(zKkdy$tx?>elCbF3frHjJsfHtyu zmFHMy9B%j04s&__qD^aar!;@JSferO&Z||dn*WU_`Rw^3tdiPFr8S|=yxHuEQm)7= z#rY6gJZ2LUu#&=hg*-9raryW3*zS<9Cu+?ieJNCE6~r*FI;?Y(iKBD+ok!!*g-q_H z(bSH?Jw~e`Cf6+_I`x=RD#JuuwesJ=xLy999@~X|*bg;jM7{Ns$LfPtqR9g_f_ac$ zh365^&IIN5*A8$l|1zw6=Z%%mGz-;i?j)?dOMxg2~tlsO_6%i zYnv*>X2Z*0#ZF|+F)-FTypEy$6#97$TY}45;S*(q&3x!UV)Zs~TrO9p7RpNLi~`#t zW93zMi=naj!RXxhY$#nV;Tc=emT8#e1=i*757=s5c$y@YN+p|L!gE^PZ<88_)t^@L zVK>6teIl^dr0toubN*esY$veiluyGRO-YrN(^Vx8yEf}b*5WsH#2tBX{@oziQ#)jH z`2zvl9SYW|+NNIY79A(mT9eDEHLshe9g}la7{@yABsWQX{%0Owq)Gk(-@p2%-2JXw zxIb}>vmb=Y*Zt?NbsJsH;jsI@<7cwUS+rN5mMf?59Wbku>p!bFA4|lKhw5n|xh!~( zCuR@Byot@tBooKtt9hbHb*qNvR}zjGvDwh-^&k{Yh7QPO%N{(D*&SMI0wewy@q5BT z?%+rSEfW@U3%{?rmV6mE~Zv_rGrLDizrLTW$$$ zf7A95T&8>VZbCoIy{?=xWJ&+(h7xg48$5~0NjQaG3+K*NNFdIGNjTOU+|Zc@qqN^_ z4_b6I*kQ>LyxQm{dw1@xv%9Q2hTwfAV_%JtVCq`7tsWzA(8F*{ZtN_fL=tagYde}; z{*yhn+KIYd#h6@?*E|Io_#n0x>r!eZuBoHpZ5pXV3$39;gL%H^t>-r24eUL;>h94< z$PhrEnyqW3m1tTjt%TRMVj4PhQ;l1!dc!iD>oweFFZ1NBZI;cw(~dcoyS~Oqy75Yp zlvg@Q8MlE;@8e&?wnn|#%*L;?299Rs(vnhXbQfA9QWGoWE6TZ7BY_SMk@XOD7_dYg z@b+*mbVF5_G`db|zUQuhfI8f&gBrH||2>FU>_QvD`H;T6tg(3f zKU{6}$dffLrN*4Lh%TD^NT8gKX=$lXTC90eqsb4N{CG`~j?3JFX?cxbz2pad{{M9k za?l1T0u%v?07ZZzKoOt_Py{Ff6ak6=MSvpkx+6f(|F1jbv_XmhMSvne5ugZA1SkR& z0g3=cfFeKE* zP${RY%3&o}K;(9<-R_$>rXL9^>B1SMbbeOOFDoSri~l^wbO1|ic6`iAIHwYl(HSe@ zEFnZ^6G;m}YO`_7X2!$yE@dk#g-kW4lr>AF-(=h)(9D)9$Dr%=g}id8Am?K6i(mGS zH2F2|P=#amn7QDMV7O;GN1)6xTTL7m@$Ds!*{>2~a zEo0dAHad&ys8)-`5_)NSA#Z7>t-vweAe}r_D9ITM$)6_}C6zit`wBpWWYUEayvx)g z)RN{4E?SfOZ_39zx8BIoCd7Rin`lUV`)r4E(Q9JP>xDWW)9 zs$_FHm?jo#_YK5uWG?(i3e|YT(yY0wf1ZpsVzCecZz3ab3?^n8-uG`Mo+QLMcsK6B zoQ!=zSJ`?!8FTc~ay%+J#eYI|tzrQKtP?t~Q{A;FpUo#L@QVJlrM=eUL}e1uEtJ49 zVu|{;Sv)AZ+Lk#o)IhWd1!oCrNqAL1n(9_7wB^(XkL(CRA50uUP6^ z$4UF~GwRgQ+Xx&XaG|$TF2LJfEn;ogk{%sUefTjK5Rl51cf~2?Gwx5g?sDW2$Sp-8@lKT%Lp)pA% zPh~Nry70Dr9J7xYO*r7*n@tyr72@DJ->xe-CT`|4OkJ(Cf0Sd6uED&9{+)X{Cbou1 zbQ<3ebX)d}=s**jO}0d{rD9Gd%a<;&ZMd$JhJXdGt|8qKh~oN|w+!lzpfWUSE-*mm zm4>>djkbtsw5Bbj&VI7+f!qNZI+3m$gSI|h^guLW5#6+hV+II(fIupFx+-T%WZLQ| zgnD&nKs*g5LMUwEi07BoWl7huWw#Ch(y<1PZM$?sfy-iO=T5zk(a51xG9JadHKTzZ zqXD3=(Ll!z^+uRTAuaW8-?807t_Lf$&FE$%+6ah_ttJK0)S-0N(uplwOh!i6G;*^= z32fjjN`jlzNu92wW(rlZ4cMTqNgds)Fi@kgvmtAZK@M5TOZY~plRLXql#z5g4Lb$A zRO$4VPIRwubH__^v8a@It<}~Jw1T?1IoLqJZdNJll$Icon3c=RuyX758v`8U2MK&) z&}%oeb4(M|P8AIe?QQDaj%=tA7CL`>E1@0A6&B1q@=XX?OuVkELW zyohfUhRix*3H%SQvEGTePXlzb#4$rKFY)fQkWZ#d*&=%PKhmKy1w^u5WR!*K(h`~U zIx#39H*v$!2R4LdB{W&6C}mF5zmX@MnkW>?ieBsYp&PWD8&ehPg!K*>7sk!?o+)b@ zI=#eIN_gi2|ADK#+*>(UVKsWYN9}dFke5p-*#9l-G`}0%1Gw5+kW*^vcsgae>qx0YTmr8N@z{(qn6w;Ab0=})Dnq}!zh>9Dj@;>GWZeX$Gd4A#f8)$(3Py{Ff6ak6=MSvne5ugZM4g%fnH8|v=m@}Ly7Yb#3 zO3*U|TWWB+MFII^$>XpMgdkm7L9s)n_eR*bI<&pt_8J^<_t{}XjyB9G5?xGh9M$$Iiol!pf!_vhJ+Gc{i%DhKRP17P*w++;WP!sOjBRgA;J9o>^7j zo2%pQs=;{}#ioe8v67+&y2X5;o69EDLLqY-1KkUpn#`fG41Mrj-^df2JyXsf}A7-uQ;h_Fs;ufcJc zWFr&^{^RhpPVB0|VHiO;T`1!W4f-F$=vbh0wAJ7^j8so8+z284IvN~_5p6;)sVOI2 zuB!%zVx&2df%6OecimN*TWfGUMj0U8j8Uh9GOw3NOuL)Tb=B$!O0oHpPZe=M{1oC zsKN0WC8|)U0x~RCwSgKOqY)MUjR^8U9P`m-TWfHlM)t(>)fKB?R}Bu;2n7^ZNI`-I z0!~;ss9C(d21je;JD7t+-5|-6FKdBzopVPG4%=5-Ij!*{opnOkc^Mg85 zM-7hND4R)`qBDo45Lh~WOAQX+XvtBP;QU-l%bn1. - DefenderSquadron.Grouping=1 + DefenderSquadron.Grouping = 1 -- Get free parking for fighter aircraft. - local nfreeparking=DefenderSquadron.Airbase:GetFreeParkingSpotsNumber(AIRBASE.TerminalType.FighterAircraft, true) + local nfreeparking = DefenderSquadron.Airbase:GetFreeParkingSpotsNumber( AIRBASE.TerminalType.FighterAircraft, true ) -- Take number of free parking spots if no resource count was specifed. - DefenderSquadron.ResourceCount=DefenderSquadron.ResourceCount or nfreeparking + DefenderSquadron.ResourceCount = DefenderSquadron.ResourceCount or nfreeparking -- Check that resource count is not larger than free parking spots. - DefenderSquadron.ResourceCount=math.min(DefenderSquadron.ResourceCount, nfreeparking) + DefenderSquadron.ResourceCount = math.min( DefenderSquadron.ResourceCount, nfreeparking ) -- Set uncontrolled spawning option. - for SpawnTemplate,_DefenderSpawn in pairs( self.DefenderSpawns ) do - local DefenderSpawn=_DefenderSpawn --Core.Spawn#SPAWN - DefenderSpawn:InitUnControlled(true) + for SpawnTemplate, _DefenderSpawn in pairs( self.DefenderSpawns ) do + local DefenderSpawn = _DefenderSpawn -- Core.Spawn#SPAWN + DefenderSpawn:InitUnControlled( true ) end end @@ -1756,14 +1733,14 @@ do -- AI_A2A_DISPATCHER -- @return #boolean true if visible. -- @usage -- - -- -- Set the Squadron visible before startup of dispatcher. - -- local IsVisible = A2ADispatcher:IsSquadronVisible( "Mineralnye" ) + -- -- Set the Squadron visible before startup of dispatcher. + -- local IsVisible = A2ADispatcher:IsSquadronVisible( "Mineralnye" ) -- function AI_A2A_DISPATCHER:IsSquadronVisible( SquadronName ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - local DefenderSquadron = self:GetSquadron( SquadronName ) --#AI_A2A_DISPATCHER.Squadron + local DefenderSquadron = self:GetSquadron( SquadronName ) -- #AI_A2A_DISPATCHER.Squadron if DefenderSquadron then return DefenderSquadron.Uncontrolled == true @@ -1790,24 +1767,24 @@ do -- AI_A2A_DISPATCHER -- @return #AI_A2A_DISPATCHER -- @usage -- - -- -- CAP Squadron execution. - -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) - -- -- Setup a CAP, engaging between 800 and 900 km/h, altitude 30 (above the sea), radio altitude measurement, - -- -- patrolling speed between 500 and 600 km/h, altitude between 4000 and 10000 meters, barometric altitude measurement. - -- A2ADispatcher:SetSquadronCapV2( "Mineralnye", 800, 900, 30, 30, "RADIO", CAPZoneEast, 500, 600, 4000, 10000, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) + -- -- CAP Squadron execution. + -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) + -- -- Setup a CAP, engaging between 800 and 900 km/h, altitude 30 (above the sea), radio altitude measurement, + -- -- patrolling speed between 500 and 600 km/h, altitude between 4000 and 10000 meters, barometric altitude measurement. + -- A2ADispatcher:SetSquadronCapV2( "Mineralnye", 800, 900, 30, 30, "RADIO", CAPZoneEast, 500, 600, 4000, 10000, "BARO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) -- - -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) - -- -- Setup a CAP, engaging between 800 and 1200 km/h, altitude between 4000 and 10000 meters, radio altitude measurement, - -- -- patrolling speed between 600 and 800 km/h, altitude between 4000 and 8000, barometric altitude measurement. - -- A2ADispatcher:SetSquadronCapV2( "Sochi", 800, 1200, 2000, 3000, "RADIO", CAPZoneWest, 600, 800, 4000, 8000, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) + -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) + -- -- Setup a CAP, engaging between 800 and 1200 km/h, altitude between 4000 and 10000 meters, radio altitude measurement, + -- -- patrolling speed between 600 and 800 km/h, altitude between 4000 and 8000, barometric altitude measurement. + -- A2ADispatcher:SetSquadronCapV2( "Sochi", 800, 1200, 2000, 3000, "RADIO", CAPZoneWest, 600, 800, 4000, 8000, "BARO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) -- - -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") - -- -- Setup a CAP, engaging between 800 and 1200 km/h, altitude between 5000 and 8000 meters, barometric altitude measurement, - -- -- patrolling speed between 600 and 800 km/h, altitude between 4000 and 8000, radio altitude. - -- A2ADispatcher:SetSquadronCapV2( "Maykop", 800, 1200, 5000, 8000, "BARO", CAPZoneMiddle, 600, 800, 4000, 8000, "RADIO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Maykop", 2, 30, 120, 1 ) + -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") + -- -- Setup a CAP, engaging between 800 and 1200 km/h, altitude between 5000 and 8000 meters, barometric altitude measurement, + -- -- patrolling speed between 600 and 800 km/h, altitude between 4000 and 8000, radio altitude. + -- A2ADispatcher:SetSquadronCapV2( "Maykop", 800, 1200, 5000, 8000, "BARO", CAPZoneMiddle, 600, 800, 4000, 8000, "RADIO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Maykop", 2, 30, 120, 1 ) -- function AI_A2A_DISPATCHER:SetSquadronCap2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType ) @@ -1859,18 +1836,18 @@ do -- AI_A2A_DISPATCHER -- @return #AI_A2A_DISPATCHER -- @usage -- - -- -- CAP Squadron execution. - -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) - -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) + -- -- CAP Squadron execution. + -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) + -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) + -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) -- - -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) - -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) + -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) + -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) -- - -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") - -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) + -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") + -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) -- function AI_A2A_DISPATCHER:SetSquadronCap( SquadronName, Zone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) @@ -1887,18 +1864,18 @@ do -- AI_A2A_DISPATCHER -- @return #AI_A2A_DISPATCHER -- @usage -- - -- -- CAP Squadron execution. - -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) - -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) + -- -- CAP Squadron execution. + -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) + -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) + -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) -- - -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) - -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) + -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) + -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) -- - -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") - -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) + -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") + -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) -- function AI_A2A_DISPATCHER:SetSquadronCapInterval( SquadronName, CapLimit, LowInterval, HighInterval, Probability ) @@ -1916,7 +1893,7 @@ do -- AI_A2A_DISPATCHER Cap.Scheduler = Cap.Scheduler or SCHEDULER:New( self ) local Scheduler = Cap.Scheduler -- Core.Scheduler#SCHEDULER local ScheduleID = Cap.ScheduleID - local Variance = ( Cap.HighInterval - Cap.LowInterval ) / 2 + local Variance = (Cap.HighInterval - Cap.LowInterval) / 2 local Repeat = Cap.LowInterval + Variance local Randomization = Variance / Repeat local Start = math.random( 1, Cap.HighInterval ) @@ -1956,7 +1933,7 @@ do -- AI_A2A_DISPATCHER -- @param #string SquadronName The squadron name. -- @return #AI_A2A_DISPATCHER.Squadron DefenderSquadron function AI_A2A_DISPATCHER:CanCAP( SquadronName ) - self:F({SquadronName = SquadronName}) + self:F( { SquadronName = SquadronName } ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} @@ -1965,7 +1942,7 @@ do -- AI_A2A_DISPATCHER if DefenderSquadron.Captured == false then -- We can only spawn new CAP if the base has not been captured. - if ( not DefenderSquadron.ResourceCount ) or ( DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0 ) then -- And, if there are sufficient resources. + if (not DefenderSquadron.ResourceCount) or (DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0) then -- And, if there are sufficient resources. local Cap = DefenderSquadron.Cap if Cap then @@ -1983,7 +1960,6 @@ do -- AI_A2A_DISPATCHER return nil end - --- Set race track pattern as default when any squadron is performing CAP. -- @param #AI_A2A_DISPATCHER self -- @param #number LeglengthMin Min length of the race track leg in meters. Default 10,000 m. @@ -1994,16 +1970,16 @@ do -- AI_A2A_DISPATCHER -- @param #number DurationMax (Optional) Max duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. -- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading. -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetDefaultCapRacetrack(LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates) + function AI_A2A_DISPATCHER:SetDefaultCapRacetrack( LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates ) - self.DefenderDefault.Racetrack=true - self.DefenderDefault.RacetrackLengthMin=LeglengthMin - self.DefenderDefault.RacetrackLengthMax=LeglengthMax - self.DefenderDefault.RacetrackHeadingMin=HeadingMin - self.DefenderDefault.RacetrackHeadingMax=HeadingMax - self.DefenderDefault.RacetrackDurationMin=DurationMin - self.DefenderDefault.RacetrackDurationMax=DurationMax - self.DefenderDefault.RacetrackCoordinates=CapCoordinates + self.DefenderDefault.Racetrack = true + self.DefenderDefault.RacetrackLengthMin = LeglengthMin + self.DefenderDefault.RacetrackLengthMax = LeglengthMax + self.DefenderDefault.RacetrackHeadingMin = HeadingMin + self.DefenderDefault.RacetrackHeadingMax = HeadingMax + self.DefenderDefault.RacetrackDurationMin = DurationMin + self.DefenderDefault.RacetrackDurationMax = DurationMax + self.DefenderDefault.RacetrackCoordinates = CapCoordinates return self end @@ -2019,31 +1995,30 @@ do -- AI_A2A_DISPATCHER -- @param #number DurationMax (Optional) Max duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. -- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading. -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetSquadronCapRacetrack(SquadronName, LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates) + function AI_A2A_DISPATCHER:SetSquadronCapRacetrack( SquadronName, LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates ) local DefenderSquadron = self:GetSquadron( SquadronName ) if DefenderSquadron then - DefenderSquadron.Racetrack=true - DefenderSquadron.RacetrackLengthMin=LeglengthMin - DefenderSquadron.RacetrackLengthMax=LeglengthMax - DefenderSquadron.RacetrackHeadingMin=HeadingMin - DefenderSquadron.RacetrackHeadingMax=HeadingMax - DefenderSquadron.RacetrackDurationMin=DurationMin - DefenderSquadron.RacetrackDurationMax=DurationMax - DefenderSquadron.RacetrackCoordinates=CapCoordinates + DefenderSquadron.Racetrack = true + DefenderSquadron.RacetrackLengthMin = LeglengthMin + DefenderSquadron.RacetrackLengthMax = LeglengthMax + DefenderSquadron.RacetrackHeadingMin = HeadingMin + DefenderSquadron.RacetrackHeadingMax = HeadingMax + DefenderSquadron.RacetrackDurationMin = DurationMin + DefenderSquadron.RacetrackDurationMax = DurationMax + DefenderSquadron.RacetrackCoordinates = CapCoordinates end return self end - --- Check if squadron can do GCI. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The squadron name. -- @return #table DefenderSquadron function AI_A2A_DISPATCHER:CanGCI( SquadronName ) - self:F({SquadronName = SquadronName}) + self:F( { SquadronName = SquadronName } ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} self.DefenderSquadrons[SquadronName].Gci = self.DefenderSquadrons[SquadronName].Gci or {} @@ -2052,7 +2027,7 @@ do -- AI_A2A_DISPATCHER if DefenderSquadron.Captured == false then -- We can only spawn new CAP if the base has not been captured. - if ( not DefenderSquadron.ResourceCount ) or ( DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0 ) then -- And, if there are sufficient resources. + if (not DefenderSquadron.ResourceCount) or (DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0) then -- And, if there are sufficient resources. local Gci = DefenderSquadron.Gci if Gci then return DefenderSquadron @@ -2070,14 +2045,14 @@ do -- AI_A2A_DISPATCHER -- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. -- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. -- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". + -- @return #AI_A2A_DISPATCHER -- @usage -- - -- -- GCI Squadron execution. - -- A2ADispatcher:SetSquadronGci2( "Mozdok", 900, 1200, 5000, 5000, "BARO" ) - -- A2ADispatcher:SetSquadronGci2( "Novo", 900, 2100, 30, 30, "RADIO" ) - -- A2ADispatcher:SetSquadronGci2( "Maykop", 900, 1200, 100, 300, "RADIO" ) + -- -- GCI Squadron execution. + -- A2ADispatcher:SetSquadronGci2( "Mozdok", 900, 1200, 5000, 5000, "BARO" ) + -- A2ADispatcher:SetSquadronGci2( "Novo", 900, 2100, 30, 30, "RADIO" ) + -- A2ADispatcher:SetSquadronGci2( "Maykop", 900, 1200, 100, 300, "RADIO" ) -- - -- @return #AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:SetSquadronGci2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} @@ -2099,14 +2074,14 @@ do -- AI_A2A_DISPATCHER -- @param #string SquadronName The squadron name. -- @param #number EngageMinSpeed The minimum speed [km/h] at which the GCI can be executed. -- @param #number EngageMaxSpeed The maximum speed [km/h] at which the GCI can be executed. + -- @return #AI_A2A_DISPATCHER -- @usage -- - -- -- GCI Squadron execution. - -- A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 ) - -- A2ADispatcher:SetSquadronGci( "Novo", 900, 2100 ) - -- A2ADispatcher:SetSquadronGci( "Maykop", 900, 1200 ) + -- -- GCI Squadron execution. + -- A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 ) + -- A2ADispatcher:SetSquadronGci( "Novo", 900, 2100 ) + -- A2ADispatcher:SetSquadronGci( "Maykop", 900, 1200 ) -- - -- @return #AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:SetSquadronGci( SquadronName, EngageMinSpeed, EngageMaxSpeed ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} @@ -2122,7 +2097,8 @@ do -- AI_A2A_DISPATCHER --- Defines the default amount of extra planes that will take-off as part of the defense system. -- @param #AI_A2A_DISPATCHER self - -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @return #AI_A2A_DISPATCHER -- The default overhead is 1, so equal balance. The @{#AI_A2A_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2A missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. @@ -2149,7 +2125,6 @@ do -- AI_A2A_DISPATCHER -- -- A2ADispatcher:SetDefaultOverhead( 1.5 ) -- - -- @return #AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:SetDefaultOverhead( Overhead ) self.DefenderDefault.Overhead = Overhead @@ -2157,11 +2132,11 @@ do -- AI_A2A_DISPATCHER return self end - --- Defines the amount of extra planes that will take-off as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @return #AI_A2A_DISPATCHER self -- The default overhead is 1, so equal balance. The @{#AI_A2A_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2A missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. @@ -2188,7 +2163,6 @@ do -- AI_A2A_DISPATCHER -- -- A2ADispatcher:SetSquadronOverhead( "SquadronName", 1.5 ) -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronOverhead( SquadronName, Overhead ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -2197,11 +2171,11 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets the default grouping of new airplanes spawned. -- Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense. -- @param #AI_A2A_DISPATCHER self -- @param #number Grouping The level of grouping that will be applied of the CAP or GCI defenders. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2209,8 +2183,6 @@ do -- AI_A2A_DISPATCHER -- -- Set a grouping by default per 2 airplanes. -- A2ADispatcher:SetDefaultGrouping( 2 ) -- - -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefaultGrouping( Grouping ) self.DefenderDefault.Grouping = Grouping @@ -2218,12 +2190,12 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets the grouping of new airplanes spawned. -- Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number Grouping The level of grouping that will be applied of the CAP or GCI defenders. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2231,8 +2203,6 @@ do -- AI_A2A_DISPATCHER -- -- Set a grouping per 2 airplanes. -- A2ADispatcher:SetSquadronGrouping( "SquadronName", 2 ) -- - -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronGrouping( SquadronName, Grouping ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -2241,10 +2211,10 @@ do -- AI_A2A_DISPATCHER return self end - --- Defines the default method at which new flights will spawn and take-off as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2261,9 +2231,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights by default take-off from the airbase cold. -- A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Cold ) -- - -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetDefaultTakeoff( Takeoff ) self.DefenderDefault.Takeoff = Takeoff @@ -2275,6 +2242,7 @@ do -- AI_A2A_DISPATCHER -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2291,9 +2259,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off from the airbase cold. -- A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Cold ) -- - -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetSquadronTakeoff( SquadronName, Takeoff ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -2302,7 +2267,6 @@ do -- AI_A2A_DISPATCHER return self end - --- Gets the default method at which new flights will spawn and take-off as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. @@ -2316,7 +2280,7 @@ do -- AI_A2A_DISPATCHER -- ... -- end -- - function AI_A2A_DISPATCHER:GetDefaultTakeoff( ) + function AI_A2A_DISPATCHER:GetDefaultTakeoff() return self.DefenderDefault.Takeoff end @@ -2341,9 +2305,9 @@ do -- AI_A2A_DISPATCHER return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff end - --- Sets flights to default take-off in the air, as part of the defense system. -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2351,8 +2315,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights by default take-off in the air. -- A2ADispatcher:SetDefaultTakeoffInAir() -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetDefaultTakeoffInAir() self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Air ) @@ -2371,6 +2333,7 @@ do -- AI_A2A_DISPATCHER -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number TakeoffAltitude (optional) The altitude in meters above the ground. If not given, the default takeoff altitude will be used. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2378,8 +2341,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off in the air. -- A2ADispatcher:SetSquadronTakeoffInAir( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetSquadronTakeoffInAir( SquadronName, TakeoffAltitude ) self:SetSquadronTakeoff( SquadronName, AI_A2A_DISPATCHER.Takeoff.Air ) @@ -2391,9 +2352,9 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights by default to take-off from the runway, as part of the defense system. -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2401,8 +2362,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights by default take-off from the runway. -- A2ADispatcher:SetDefaultTakeoffFromRunway() -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetDefaultTakeoffFromRunway() self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Runway ) @@ -2410,10 +2369,10 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights to take-off from the runway, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2421,8 +2380,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off from the runway. -- A2ADispatcher:SetSquadronTakeoffFromRunway( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetSquadronTakeoffFromRunway( SquadronName ) self:SetSquadronTakeoff( SquadronName, AI_A2A_DISPATCHER.Takeoff.Runway ) @@ -2430,9 +2387,9 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights by default to take-off from the airbase at a hot location, as part of the defense system. -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2440,8 +2397,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights by default take-off at a hot parking spot. -- A2ADispatcher:SetDefaultTakeoffFromParkingHot() -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingHot() self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Hot ) @@ -2452,6 +2407,7 @@ do -- AI_A2A_DISPATCHER --- Sets flights to take-off from the airbase at a hot location, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2459,8 +2415,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off in the air. -- A2ADispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingHot( SquadronName ) self:SetSquadronTakeoff( SquadronName, AI_A2A_DISPATCHER.Takeoff.Hot ) @@ -2468,9 +2422,9 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights to by default take-off from the airbase at a cold location, as part of the defense system. -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2478,8 +2432,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off from a cold parking spot. -- A2ADispatcher:SetDefaultTakeoffFromParkingCold() -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingCold() self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Cold ) @@ -2487,10 +2439,10 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights to take-off from the airbase at a cold location, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2498,8 +2450,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off from a cold parking spot. -- A2ADispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingCold( SquadronName ) self:SetSquadronTakeoff( SquadronName, AI_A2A_DISPATCHER.Takeoff.Cold ) @@ -2507,10 +2457,10 @@ do -- AI_A2A_DISPATCHER return self end - --- Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected. -- @param #AI_A2A_DISPATCHER self -- @param #number TakeoffAltitude The altitude in meters above the ground. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2518,8 +2468,6 @@ do -- AI_A2A_DISPATCHER -- -- Set the default takeoff altitude when taking off in the air. -- A2ADispatcher:SetDefaultTakeoffInAirAltitude( 2000 ) -- This makes planes start at 2000 meters above the ground. -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetDefaultTakeoffInAirAltitude( TakeoffAltitude ) self.DefenderDefault.TakeoffAltitude = TakeoffAltitude @@ -2531,6 +2479,7 @@ do -- AI_A2A_DISPATCHER -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number TakeoffAltitude The altitude in meters above the ground. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2538,8 +2487,6 @@ do -- AI_A2A_DISPATCHER -- -- Set the default takeoff altitude when taking off in the air. -- A2ADispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes planes start at 2000 meters above the ground. -- - -- @return #AI_A2A_DISPATCHER self - -- function AI_A2A_DISPATCHER:SetSquadronTakeoffInAirAltitude( SquadronName, TakeoffAltitude ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -2548,10 +2495,10 @@ do -- AI_A2A_DISPATCHER return self end - --- Defines the default method at which flights will land and despawn as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2565,7 +2512,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights by default despawn after landing and parking, and after engine shutdown. -- A2ADispatcher:SetDefaultLanding( AI_A2A_Dispatcher.Landing.AtEngineShutdown ) -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefaultLanding( Landing ) self.DefenderDefault.Landing = Landing @@ -2573,11 +2519,11 @@ do -- AI_A2A_DISPATCHER return self end - --- Defines the method at which flights will land and despawn as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2591,7 +2537,6 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights despawn after landing and parking, and after engine shutdown. -- A2ADispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.AtEngineShutdown ) -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronLanding( SquadronName, Landing ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -2600,7 +2545,6 @@ do -- AI_A2A_DISPATCHER return self end - --- Gets the default method at which flights will land and despawn as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown @@ -2619,7 +2563,6 @@ do -- AI_A2A_DISPATCHER return self.DefenderDefault.Landing end - --- Gets the method at which flights will land and despawn as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. @@ -2640,9 +2583,9 @@ do -- AI_A2A_DISPATCHER return DefenderSquadron.Landing or self.DefenderDefault.Landing end - --- Sets flights by default to land and despawn near the airbase in the air, as part of the defense system. -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2650,7 +2593,6 @@ do -- AI_A2A_DISPATCHER -- -- Let flights by default to land near the airbase and despawn. -- A2ADispatcher:SetDefaultLandingNearAirbase() -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefaultLandingNearAirbase() self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.NearAirbase ) @@ -2658,10 +2600,10 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights to land and despawn near the airbase in the air, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2669,7 +2611,6 @@ do -- AI_A2A_DISPATCHER -- -- Let flights to land near the airbase and despawn. -- A2ADispatcher:SetSquadronLandingNearAirbase( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronLandingNearAirbase( SquadronName ) self:SetSquadronLanding( SquadronName, AI_A2A_DISPATCHER.Landing.NearAirbase ) @@ -2677,9 +2618,9 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights by default to land and despawn at the runway, as part of the defense system. -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2687,7 +2628,6 @@ do -- AI_A2A_DISPATCHER -- -- Let flights by default land at the runway and despawn. -- A2ADispatcher:SetDefaultLandingAtRunway() -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefaultLandingAtRunway() self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.AtRunway ) @@ -2695,10 +2635,10 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights to land and despawn at the runway, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2706,7 +2646,6 @@ do -- AI_A2A_DISPATCHER -- -- Let flights land at the runway and despawn. -- A2ADispatcher:SetSquadronLandingAtRunway( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronLandingAtRunway( SquadronName ) self:SetSquadronLanding( SquadronName, AI_A2A_DISPATCHER.Landing.AtRunway ) @@ -2714,9 +2653,9 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights by default to land and despawn at engine shutdown, as part of the defense system. -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2724,7 +2663,6 @@ do -- AI_A2A_DISPATCHER -- -- Let flights by default land and despawn at engine shutdown. -- A2ADispatcher:SetDefaultLandingAtEngineShutdown() -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefaultLandingAtEngineShutdown() self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.AtEngineShutdown ) @@ -2732,10 +2670,10 @@ do -- AI_A2A_DISPATCHER return self end - --- Sets flights to land and despawn at engine shutdown, as part of the defense system. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. + -- @return #AI_A2A_DISPATCHER self -- @usage: -- -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) @@ -2743,7 +2681,6 @@ do -- AI_A2A_DISPATCHER -- -- Let flights land and despawn at engine shutdown. -- A2ADispatcher:SetSquadronLandingAtEngineShutdown( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronLandingAtEngineShutdown( SquadronName ) self:SetSquadronLanding( SquadronName, AI_A2A_DISPATCHER.Landing.AtEngineShutdown ) @@ -2751,17 +2688,17 @@ do -- AI_A2A_DISPATCHER return self end - --- Set the default fuel treshold when defenders will RTB or Refuel in the air. - -- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. + --- Set the default fuel threshold when defenders will RTB or Refuel in the air. + -- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. -- @param #AI_A2A_DISPATCHER self - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. + -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. -- @return #AI_A2A_DISPATCHER self -- @usage -- -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- A2ADispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- function AI_A2A_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold ) @@ -2771,19 +2708,18 @@ do -- AI_A2A_DISPATCHER return self end - - --- Set the fuel treshold for the squadron when defenders will RTB or Refuel in the air. - -- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. + --- Set the fuel threshold for the squadron when defenders will RTB or Refuel in the air. + -- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. + -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. -- @return #AI_A2A_DISPATCHER self -- @usage -- -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- A2ADispatcher:SetSquadronFuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- function AI_A2A_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold ) @@ -2803,11 +2739,12 @@ do -- AI_A2A_DISPATCHER -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- A2ADispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- -- -- Now Setup the default tanker. -- A2ADispatcher:SetDefaultTanker( "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. + -- function AI_A2A_DISPATCHER:SetDefaultTanker( TankerName ) self.DefenderDefault.TankerName = TankerName @@ -2815,7 +2752,6 @@ do -- AI_A2A_DISPATCHER return self end - --- Set the squadron tanker where defenders will Refuel in the air. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. @@ -2826,11 +2762,12 @@ do -- AI_A2A_DISPATCHER -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) -- - -- -- Now Setup the squadron fuel treshold. + -- -- Now Setup the squadron fuel threshold. -- A2ADispatcher:SetSquadronFuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- -- -- Now Setup the squadron tanker. -- A2ADispatcher:SetSquadronTanker( "SquadronName", "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. + -- function AI_A2A_DISPATCHER:SetSquadronTanker( SquadronName, TankerName ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -2839,7 +2776,6 @@ do -- AI_A2A_DISPATCHER return self end - --- Set the squadron language. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. @@ -2867,7 +2803,6 @@ do -- AI_A2A_DISPATCHER return self end - --- Set the frequency of communication and the mode of communication for voice overs. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. @@ -2902,7 +2837,7 @@ do -- AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:AddDefenderToSquadron( Squadron, Defender, Size ) self.Defenders = self.Defenders or {} local DefenderName = Defender:GetName() - self.Defenders[ DefenderName ] = Squadron + self.Defenders[DefenderName] = Squadron if Squadron.ResourceCount then Squadron.ResourceCount = Squadron.ResourceCount - Size end @@ -2919,7 +2854,7 @@ do -- AI_A2A_DISPATCHER if Squadron.ResourceCount then Squadron.ResourceCount = Squadron.ResourceCount + Defender:GetSize() end - self.Defenders[ DefenderName ] = nil + self.Defenders[DefenderName] = nil self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } ) end @@ -2932,13 +2867,12 @@ do -- AI_A2A_DISPATCHER if Defender ~= nil then local DefenderName = Defender:GetName() self:F( { DefenderName = DefenderName } ) - return self.Defenders[ DefenderName ] + return self.Defenders[DefenderName] else - return nil + return nil end end - --- Creates an SWEEP task when there are targets for it. -- @param #AI_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem @@ -2949,7 +2883,6 @@ do -- AI_A2A_DISPATCHER local DetectedSet = DetectedItem.Set local DetectedZone = DetectedItem.Zone - if DetectedItem.IsDetected == false then -- Here we're doing something advanced... We're copying the DetectedSet. @@ -2979,9 +2912,9 @@ do -- AI_A2A_DISPATCHER if AIGroup and AIGroup:IsAlive() then -- Check if the CAP is patrolling or engaging. If not, this is not a valid CAP, even if it is alive! -- The CAP could be damaged, lost control, or out of fuel! - --env.info("FF fsm state "..tostring(DefenderTask.Fsm:GetState())) + -- env.info("FF fsm state "..tostring(DefenderTask.Fsm:GetState())) if DefenderTask.Fsm:Is( "Patrolling" ) or DefenderTask.Fsm:Is( "Engaging" ) or DefenderTask.Fsm:Is( "Refuelling" ) or DefenderTask.Fsm:Is( "Started" ) then - --env.info("FF capcount "..CapCount) + -- env.info("FF capcount "..CapCount) CapCount = CapCount + 1 end end @@ -2993,24 +2926,23 @@ do -- AI_A2A_DISPATCHER return CapCount end - --- Count number of engaging defender groups. -- @param #AI_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detection object. -- @return #number Number of defender groups engaging. function AI_A2A_DISPATCHER:CountDefendersEngaged( AttackerDetection ) - -- First, count the active AIGroups Units, targetting the DetectedSet + -- First, count the active AIGroups Units, targeting the DetectedSet local DefenderCount = 0 local DetectedSet = AttackerDetection.Set - --DetectedSet:Flush() + -- DetectedSet:Flush() local DefenderTasks = self:GetDefenderTasks() for DefenderGroup, DefenderTask in pairs( DefenderTasks ) do local Defender = DefenderGroup -- Wrapper.Group#GROUP - local DefenderTaskTarget = DefenderTask.Target --Functional.Detection#DETECTION_BASE.DetectedItem + local DefenderTaskTarget = DefenderTask.Target -- Functional.Detection#DETECTION_BASE.DetectedItem local DefenderSquadronName = DefenderTask.SquadronName if DefenderTaskTarget and DefenderTaskTarget.Index == AttackerDetection.Index then @@ -3090,7 +3022,6 @@ do -- AI_A2A_DISPATCHER return Friendlies end - --- Activate resource. -- @param #AI_A2A_DISPATCHER self -- @param #AI_A2A_DISPATCHER.Squadron DefenderSquadron The defender squadron. @@ -3105,36 +3036,36 @@ do -- AI_A2A_DISPATCHER local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping - DefenderGrouping = ( DefenderGrouping < DefendersNeeded ) and DefenderGrouping or DefendersNeeded + DefenderGrouping = (DefenderGrouping < DefendersNeeded) and DefenderGrouping or DefendersNeeded - --env.info(string.format("FF resource activate: Squadron=%s grouping=%d needed=%d visible=%s", SquadronName, DefenderGrouping, DefendersNeeded, tostring(self:IsSquadronVisible( SquadronName )))) + -- env.info(string.format("FF resource activate: Squadron=%s grouping=%d needed=%d visible=%s", SquadronName, DefenderGrouping, DefendersNeeded, tostring(self:IsSquadronVisible( SquadronName )))) if self:IsSquadronVisible( SquadronName ) then - local n=#self.uncontrolled[SquadronName] + local n = #self.uncontrolled[SquadronName] - if n>0 then + if n > 0 then -- Random number 1,...n - local id=math.random(n) + local id = math.random( n ) -- Pick a random defender group. - local Defender=self.uncontrolled[SquadronName][id].group --Wrapper.Group#GROUP + local Defender = self.uncontrolled[SquadronName][id].group -- Wrapper.Group#GROUP -- Start uncontrolled group. Defender:StartUncontrolled() -- Get grouping. - DefenderGrouping=self.uncontrolled[SquadronName][id].grouping + DefenderGrouping = self.uncontrolled[SquadronName][id].grouping -- Add defender to squadron. self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping ) -- Remove defender from uncontrolled table. - table.remove(self.uncontrolled[SquadronName], id) + table.remove( self.uncontrolled[SquadronName], id ) return Defender, DefenderGrouping else - return nil,0 + return nil, 0 end -- Here we CAP the new planes. @@ -3192,7 +3123,7 @@ do -- AI_A2A_DISPATCHER --- Squadron not visible --- ---------------------------- - local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] -- Core.Spawn#SPAWN + local Spawn = DefenderSquadron.Spawn[math.random( 1, #DefenderSquadron.Spawn )] -- Core.Spawn#SPAWN if DefenderGrouping then Spawn:InitGrouping( DefenderGrouping ) @@ -3220,7 +3151,7 @@ do -- AI_A2A_DISPATCHER -- @param #string SquadronName Name of the squadron. function AI_A2A_DISPATCHER:onafterCAP( From, Event, To, SquadronName ) - self:F({SquadronName = SquadronName}) + self:F( { SquadronName = SquadronName } ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} @@ -3244,13 +3175,13 @@ do -- AI_A2A_DISPATCHER AI_A2A_Fsm:SetDisengageRadius( self.DisengageRadius ) AI_A2A_Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) if DefenderSquadron.Racetrack or self.DefenderDefault.Racetrack then - AI_A2A_Fsm:SetRaceTrackPattern(DefenderSquadron.RacetrackLengthMin or self.DefenderDefault.RacetrackLengthMin, - DefenderSquadron.RacetrackLengthMax or self.DefenderDefault.RacetrackLengthMax, - DefenderSquadron.RacetrackHeadingMin or self.DefenderDefault.RacetrackHeadingMin, - DefenderSquadron.RacetrackHeadingMax or self.DefenderDefault.RacetrackHeadingMax, - DefenderSquadron.RacetrackDurationMin or self.DefenderDefault.RacetrackDurationMin, - DefenderSquadron.RacetrackDurationMax or self.DefenderDefault.RacetrackDurationMax, - DefenderSquadron.RacetrackCoordinates or self.DefenderDefault.RacetrackCoordinates) + AI_A2A_Fsm:SetRaceTrackPattern( DefenderSquadron.RacetrackLengthMin or self.DefenderDefault.RacetrackLengthMin, + DefenderSquadron.RacetrackLengthMax or self.DefenderDefault.RacetrackLengthMax, + DefenderSquadron.RacetrackHeadingMin or self.DefenderDefault.RacetrackHeadingMin, + DefenderSquadron.RacetrackHeadingMax or self.DefenderDefault.RacetrackHeadingMax, + DefenderSquadron.RacetrackDurationMin or self.DefenderDefault.RacetrackDurationMin, + DefenderSquadron.RacetrackDurationMax or self.DefenderDefault.RacetrackDurationMax, + DefenderSquadron.RacetrackCoordinates or self.DefenderDefault.RacetrackCoordinates ) end AI_A2A_Fsm:Start() @@ -3259,8 +3190,8 @@ do -- AI_A2A_DISPATCHER function AI_A2A_Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) -- Issue GetCallsign() returns nil, see https://github.com/FlightControl-Master/MOOSE/issues/1228 if DefenderGroup and DefenderGroup:IsAlive() then - self:F({"CAP Takeoff", DefenderGroup:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) + self:F( { "CAP Takeoff", DefenderGroup:GetName() } ) + -- self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = AI_A2A_Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER @@ -3277,8 +3208,8 @@ do -- AI_A2A_DISPATCHER function AI_A2A_Fsm:onafterPatrolRoute( DefenderGroup, From, Event, To ) if DefenderGroup and DefenderGroup:IsAlive() then - self:F({"CAP PatrolRoute", DefenderGroup:GetName()}) - self:GetParent(self).onafterPatrolRoute( self, DefenderGroup, From, Event, To ) + self:F( { "CAP PatrolRoute", DefenderGroup:GetName() } ) + self:GetParent( self ).onafterPatrolRoute( self, DefenderGroup, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER @@ -3293,9 +3224,9 @@ do -- AI_A2A_DISPATCHER function AI_A2A_Fsm:onafterRTB( DefenderGroup, From, Event, To ) if DefenderGroup and DefenderGroup:IsAlive() then - self:F({"CAP RTB", DefenderGroup:GetName()}) + self:F( { "CAP RTB", DefenderGroup:GetName() } ) - self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) + self:GetParent( self ).onafterRTB( self, DefenderGroup, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER @@ -3310,8 +3241,8 @@ do -- AI_A2A_DISPATCHER --- @param #AI_A2A_DISPATCHER self function AI_A2A_Fsm:onafterHome( Defender, From, Event, To, Action ) if Defender and Defender:IsAlive() then - self:F({"CAP Home", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + self:F( { "CAP Home", Defender:GetName() } ) + self:GetParent( self ).onafterHome( self, Defender, From, Event, To ) local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) @@ -3334,7 +3265,6 @@ do -- AI_A2A_DISPATCHER end - --- On after "ENGAGE" event. -- @param #AI_A2A_DISPATCHER self -- @param #string From From state. @@ -3343,7 +3273,7 @@ do -- AI_A2A_DISPATCHER -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. -- @param #table Defenders Defenders table. function AI_A2A_DISPATCHER:onafterENGAGE( From, Event, To, AttackerDetection, Defenders ) - self:F("ENGAGING Detection ID="..tostring(AttackerDetection.ID)) + self:F( "ENGAGING Detection ID=" .. tostring( AttackerDetection.ID ) ) if Defenders then @@ -3370,7 +3300,7 @@ do -- AI_A2A_DISPATCHER -- @param #table DefenderFriendlies Friendly defenders. function AI_A2A_DISPATCHER:onafterGCI( From, Event, To, AttackerDetection, DefendersMissing, DefenderFriendlies ) - self:F("GCI Detection ID="..tostring(AttackerDetection.ID)) + self:F( "GCI Detection ID=" .. tostring( AttackerDetection.ID ) ) self:F( { From, Event, To, AttackerDetection.Index, DefendersMissing, DefenderFriendlies } ) @@ -3399,7 +3329,7 @@ do -- AI_A2A_DISPATCHER local BreakLoop = false - while( DefenderCount > 0 and not BreakLoop ) do + while (DefenderCount > 0 and not BreakLoop) do self:F( { DefenderSquadrons = self.DefenderSquadrons } ) @@ -3445,7 +3375,7 @@ do -- AI_A2A_DISPATCHER local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping local DefendersNeeded = math.ceil( DefenderCount * DefenderOverhead ) - self:F( { Overhead = DefenderOverhead, SquadronOverhead = DefenderSquadron.Overhead , DefaultOverhead = self.DefenderDefault.Overhead } ) + self:F( { Overhead = DefenderOverhead, SquadronOverhead = DefenderSquadron.Overhead, DefaultOverhead = self.DefenderDefault.Overhead } ) self:F( { Grouping = DefenderGrouping, SquadronGrouping = DefenderSquadron.Grouping, DefaultGrouping = self.DefenderDefault.Grouping } ) self:F( { DefendersCount = DefenderCount, DefendersNeeded = DefendersNeeded } ) @@ -3456,7 +3386,7 @@ do -- AI_A2A_DISPATCHER BreakLoop = true end - while ( DefendersNeeded > 0 ) do + while (DefendersNeeded > 0) do local DefenderGCI, DefenderGrouping = self:ResourceActivate( DefenderSquadron, DefendersNeeded ) @@ -3474,13 +3404,11 @@ do -- AI_A2A_DISPATCHER Fsm:SetDisengageRadius( self.DisengageRadius ) Fsm:Start() - self:SetDefenderTask( ClosestDefenderSquadronName, DefenderGCI, "GCI", Fsm, AttackerDetection ) - function Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) - self:F({"GCI Birth", DefenderGroup:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) + self:F( { "GCI Birth", DefenderGroup:GetName() } ) + -- self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER @@ -3493,13 +3421,13 @@ do -- AI_A2A_DISPATCHER elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then Dispatcher:MessageToPlayers( Squadron, DefenderName .. " колёса вверх.", DefenderGroup ) end - --Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit + -- Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit end end function Fsm:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F({"GCI Route", DefenderGroup:GetName()}) + self:F( { "GCI Route", DefenderGroup:GetName() } ) local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER @@ -3521,7 +3449,7 @@ do -- AI_A2A_DISPATCHER end function Fsm:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F({"GCI Engage", DefenderGroup:GetName()}) + self:F( { "GCI Engage", DefenderGroup:GetName() } ) local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER @@ -3541,8 +3469,8 @@ do -- AI_A2A_DISPATCHER end function Fsm:onafterRTB( DefenderGroup, From, Event, To ) - self:F({"GCI RTB", DefenderGroup:GetName()}) - self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) + self:F( { "GCI RTB", DefenderGroup:GetName() } ) + self:GetParent( self ).onafterRTB( self, DefenderGroup, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER @@ -3560,8 +3488,8 @@ do -- AI_A2A_DISPATCHER --- @param #AI_A2A_DISPATCHER self function Fsm:onafterLostControl( Defender, From, Event, To ) - self:F({"GCI LostControl", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + self:F( { "GCI LostControl", Defender:GetName() } ) + self:GetParent( self ).onafterHome( self, Defender, From, Event, To ) local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) @@ -3573,8 +3501,8 @@ do -- AI_A2A_DISPATCHER --- @param #AI_A2A_DISPATCHER self function Fsm:onafterHome( DefenderGroup, From, Event, To, Action ) - self:F({"GCI Home", DefenderGroup:GetName()}) - self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) + self:F( { "GCI Home", DefenderGroup:GetName() } ) + self:GetParent( self ).onafterHome( self, DefenderGroup, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER @@ -3598,8 +3526,8 @@ do -- AI_A2A_DISPATCHER end end - end -- if DefenderGCI then - end -- while ( DefendersNeeded > 0 ) do + end -- if DefenderGCI then + end -- while ( DefendersNeeded > 0 ) do end else -- No more resources, try something else. @@ -3615,8 +3543,6 @@ do -- AI_A2A_DISPATCHER end -- if AttackerUnit end - - --- Creates an ENGAGE task when there are human friendlies airborne near the targets. -- @param #AI_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. @@ -3624,7 +3550,7 @@ do -- AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:EvaluateENGAGE( DetectedItem ) self:F( { DetectedItem.ItemID } ) - -- First, count the active AIGroups Units, targetting the DetectedSet + -- First, count the active AIGroups Units, targeting the DetectedSet local DefenderCount = self:CountDefendersEngaged( DetectedItem ) local DefenderGroups = self:CountDefendersToBeEngaged( DetectedItem, DefenderCount ) @@ -3653,7 +3579,7 @@ do -- AI_A2A_DISPATCHER local AttackerSet = DetectedItem.Set local AttackerCount = AttackerSet:Count() - -- First, count the active AIGroups Units, targetting the DetectedSet + -- First, count the active AIGroups Units, targeting the DetectedSet local DefenderCount = self:CountDefendersEngaged( DetectedItem ) local DefendersMissing = AttackerCount - DefenderCount self:F( { AttackerCount = AttackerCount, DefenderCount = DefenderCount, DefendersMissing = DefendersMissing } ) @@ -3668,12 +3594,11 @@ do -- AI_A2A_DISPATCHER return nil, nil end - --- Assigns A2G AI Tasks in relation to the detected items. - -- @param #AI_A2G_DISPATCHER self + -- @param #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:Order( DetectedItem ) - local detection=self.Detection -- Functional.Detection#DETECTION_AREAS + local detection = self.Detection -- Functional.Detection#DETECTION_AREAS local ShortestDistance = 999999999 @@ -3702,7 +3627,6 @@ do -- AI_A2A_DISPATCHER return ShortestDistance end - --- Shows the tactical display. -- @param #AI_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. @@ -3719,8 +3643,10 @@ do -- AI_A2A_DISPATCHER local DefenderGroupCount = 0 -- Now that all obsolete tasks are removed, loop through the detected targets. - --for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do + -- for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do + for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) + return self:Order( t[a] ) < self:Order( t[b] ) + end ) do local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT @@ -3735,30 +3661,30 @@ do -- AI_A2A_DISPATCHER local DetectedItemChanged = DetectedItem.Changed -- Show tactical situation - Report:Add( string.format( "\n- Target %s (%s): (#%d) %s" , DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Set:GetObjectNames() ) ) + Report:Add( string.format( "\n- Target %s (%s): (#%d) %s", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Set:GetObjectNames() ) ) for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do local Defender = Defender -- Wrapper.Group#GROUP - if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then - if Defender and Defender:IsAlive() then - DefenderGroupCount = DefenderGroupCount + 1 - local Fuel = Defender:GetFuelMin() * 100 - local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - Report:Add( string.format( " - %s*%d/%d (%s - %s): (#%d) F: %3d, D:%3d - %s", - Defender:GetName(), - Defender:GetSize(), - Defender:GetInitialSize(), - DefenderTask.Type, - DefenderTask.Fsm:GetState(), - Defender:GetSize(), - Fuel, - Damage, - Defender:HasTask() == true and "Executing" or "Idle" ) ) - end - end + if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then + if Defender and Defender:IsAlive() then + DefenderGroupCount = DefenderGroupCount + 1 + local Fuel = Defender:GetFuelMin() * 100 + local Damage = Defender:GetLife() / Defender:GetLife0() * 100 + Report:Add( string.format( " - %s*%d/%d (%s - %s): (#%d) F: %3d, D:%3d - %s", + Defender:GetName(), + Defender:GetSize(), + Defender:GetInitialSize(), + DefenderTask.Type, + DefenderTask.Fsm:GetState(), + Defender:GetSize(), + Fuel, + Damage, + Defender:HasTask() == true and "Executing" or "Idle" ) ) + end + end end end - Report:Add( "\n- No Targets:") + Report:Add( "\n- No Targets:" ) local TaskCount = 0 for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do TaskCount = TaskCount + 1 @@ -3803,7 +3729,6 @@ do -- AI_A2A_DISPATCHER local TaskReport = REPORT:New() - for AIGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do local AIGroup = AIGroup -- Wrapper.Group#GROUP if not AIGroup:IsAlive() then @@ -3837,8 +3762,10 @@ do -- AI_A2A_DISPATCHER -- Now that all obsolete tasks are removed, loop through the detected targets. -- Closest detected targets to be considered first! - --for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do + -- for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do + for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) + return self:Order( t[a] ) < self:Order( t[b] ) + end ) do local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT @@ -3897,7 +3824,7 @@ do for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT local PlayerName = PlayerUnit:GetPlayerName() - --self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } ) + -- self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } ) if PlayerUnit:IsAirPlane() and PlayerName ~= nil then local FriendlyUnitThreatLevel = PlayerUnit:GetThreatLevel() PlayersCount = PlayersCount + 1 @@ -3910,19 +3837,18 @@ do end - --self:F( { PlayersCount = PlayersCount } ) + -- self:F( { PlayersCount = PlayersCount } ) local PlayerTypesReport = REPORT:New() if PlayersCount > 0 then for PlayerName, PlayerType in pairs( PlayerTypes ) do - PlayerTypesReport:Add( string.format('"%s" in %s', PlayerName, PlayerType ) ) + PlayerTypesReport:Add( string.format( '"%s" in %s', PlayerName, PlayerType ) ) end else PlayerTypesReport:Add( "-" ) end - return PlayersCount, PlayerTypesReport end @@ -3946,7 +3872,7 @@ do local FriendlyUnitThreatLevel = FriendlyUnit:GetThreatLevel() FriendliesCount = FriendliesCount + 1 local FriendlyType = FriendlyUnit:GetTypeName() - FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and ( FriendlyTypes[FriendlyType] + 1 ) or 1 + FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and (FriendlyTypes[FriendlyType] + 1) or 1 if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then end end @@ -3954,19 +3880,18 @@ do end - --self:F( { FriendliesCount = FriendliesCount } ) + -- self:F( { FriendliesCount = FriendliesCount } ) local FriendlyTypesReport = REPORT:New() if FriendliesCount > 0 then for FriendlyType, FriendlyTypeCount in pairs( FriendlyTypes ) do - FriendlyTypesReport:Add( string.format("%d of %s", FriendlyTypeCount, FriendlyType ) ) + FriendlyTypesReport:Add( string.format( "%d of %s", FriendlyTypeCount, FriendlyType ) ) end else FriendlyTypesReport:Add( "-" ) end - return FriendliesCount, FriendlyTypesReport end @@ -4097,13 +4022,13 @@ do -- -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_4.JPG) -- - -- **All airplane or helicopter groups that are starting with any of the choosen Template Prefixes will result in a squadron created at the airbase.** + -- **All airplane or helicopter groups that are starting with any of the chosen Template Prefixes will result in a squadron created at the airbase.** -- -- ### 1.4) Place floating helicopters to create the CAP zones defined by its route points. -- -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_5.JPG) -- - -- **All airplane or helicopter groups that are starting with any of the choosen Template Prefixes will result in a squadron created at the airbase.** + -- **All airplane or helicopter groups that are starting with any of the chosen Template Prefixes will result in a squadron created at the airbase.** -- -- The helicopter indicates the start of the CAP zone. -- The route points define the form of the CAP zone polygon. @@ -4116,7 +4041,7 @@ do -- -- ### 2.1) Planes are taking off in the air from the airbases. -- - -- This prevents airbases to get cluttered with airplanes taking off, it also reduces the risk of human players colliding with taxiiing airplanes, + -- This prevents airbases to get cluttered with airplanes taking off, it also reduces the risk of human players colliding with taxiing airplanes, -- resulting in the airbase to halt operations. -- -- You can change the way how planes take off by using the inherited methods from AI\_A2A\_DISPATCHER: @@ -4140,7 +4065,7 @@ do -- -- ### 2.2) Planes return near the airbase or will land if damaged. -- - -- When damaged airplanes return to the airbase, they will be routed and will dissapear in the air when they are near the airbase. + -- When damaged airplanes return to the airbase, they will be routed and will disappear in the air when they are near the airbase. -- There are exceptions to this rule, airplanes that aren't "listening" anymore due to damage or out of fuel, will return to the airbase and land. -- -- You can change the way how planes land by using the inherited methods from AI\_A2A\_DISPATCHER: @@ -4150,7 +4075,7 @@ do -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. -- - -- You can use these methods to minimize the airbase coodination overhead and to increase the airbase efficiency. + -- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency. -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the -- A2A defense system, as no new CAP or GCI planes can takeoff. -- Note that the method @{#AI_A2A_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. @@ -4173,7 +4098,7 @@ do -- * The minimum and maximum engage speed -- * The type of altitude measurement -- - -- These define how the squadron will perform the CAP while partrolling. Different terrain types requires different types of CAP. + -- These define how the squadron will perform the CAP while patrolling. Different terrain types requires different types of CAP. -- -- The @{#AI_A2A_DISPATCHER.SetSquadronCapInterval}() method specifies **how much** and **when** CAP flights will takeoff. -- @@ -4199,7 +4124,7 @@ do -- Essentially this controls how many flights of GCI aircraft can be active at any time. -- Note allowing large numbers of active GCI flights can adversely impact mission performance on low or medium specification hosts/servers. -- GCI needs to be setup at strategic airbases. Too far will mean that the aircraft need to fly a long way to reach the intruders, - -- too short will mean that the intruders may have alraedy passed the ideal interception point! + -- too short will mean that the intruders may have already passed the ideal interception point! -- -- For example, the following setup will create a GCI for squadron "Sochi": -- @@ -4238,8 +4163,7 @@ do -- These late activated Groups start with the name `SQUADRON CCCP`. Each Group object contains only one Unit, and defines the weapon payload, skin and skill level. -- * `"CAP CCCP"`: CAP Zones are defined using floating, late activated Helicopter Group objects, where the route points define the route of the polygon of the CAP Zone. -- These Helicopter Group objects start with the name `CAP CCCP`, and will be the locations wherein CAP will be performed. - -- * `2` Defines how many CAP airplanes are patrolling in each CAP zone defined simulateneously. - -- + -- * `2` Defines how many CAP airplanes are patrolling in each CAP zone defined simultaneously. -- -- ### 4.2) A more advanced setup: -- @@ -4256,7 +4180,7 @@ do -- * `{ "104th CAP" }`: An array of the names of the CAP zones are defined using floating, late activated helicopter group objects, -- where the route points define the route of the polygon of the CAP Zone. -- These Helicopter Group objects start with the name `104th CAP`, and will be the locations wherein CAP will be performed. - -- * `4` Defines how many CAP airplanes are patrolling in each CAP zone defined simulateneously. + -- * `4` Defines how many CAP airplanes are patrolling in each CAP zone defined simultaneously. -- -- @field #AI_A2A_GCICAP AI_A2A_GCICAP = { @@ -4264,7 +4188,6 @@ do Detection = nil, } - --- AI_A2A_GCICAP constructor. -- @param #AI_A2A_GCICAP self -- @param #string EWRPrefixes A list of prefixes that of groups that setup the Early Warning Radar network. @@ -4344,7 +4267,7 @@ do -- -- The CAP Zone prefix is nil. No CAP is created. -- -- The CAP Limit is nil. -- -- The Grouping Radius is nil. The default range of 6km radius will be grouped as a group of targets. - -- -- The Engage Radius is set nil. The default Engage Radius will be used to consider a defenser being assigned to a task. + -- -- The Engage Radius is set nil. The default Engage Radius will be used to consider a defender being assigned to a task. -- -- The GCI Radius is nil. Any target detected within the default GCI Radius will be considered for GCI engagement. -- -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created. -- @@ -4356,7 +4279,7 @@ do EWRSetGroup:FilterPrefixes( EWRPrefixes ) EWRSetGroup:FilterStart() - local Detection = DETECTION_AREAS:New( EWRSetGroup, GroupingRadius or 30000 ) + local Detection = DETECTION_AREAS:New( EWRSetGroup, GroupingRadius or 30000 ) local self = BASE:Inherit( self, AI_A2A_DISPATCHER:New( Detection ) ) -- #AI_A2A_GCICAP @@ -4377,14 +4300,11 @@ do end end - self.Templates = SET_GROUP - :New() - :FilterPrefixes( TemplatePrefixes ) - :FilterOnce() + self.Templates = SET_GROUP:New():FilterPrefixes( TemplatePrefixes ):FilterOnce() -- Setup squadrons - self:I( { Airbases = AirbaseNames } ) + self:I( { Airbases = AirbaseNames } ) self:I( "Defining Templates for Airbases ..." ) for AirbaseID, AirbaseName in pairs( AirbaseNames ) do @@ -4462,7 +4382,7 @@ do self:HandleEvent( EVENTS.Crash, self.OnEventCrashOrDead ) self:HandleEvent( EVENTS.Dead, self.OnEventCrashOrDead ) - --self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCrashOrDead ) + -- self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCrashOrDead ) self:HandleEvent( EVENTS.Land ) self:HandleEvent( EVENTS.EngineShutdown ) @@ -4559,7 +4479,7 @@ do -- -- The CAP Zone prefix is nil. No CAP is created. -- -- The CAP Limit is nil. -- -- The Grouping Radius is nil. The default range of 6km radius will be grouped as a group of targets. - -- -- The Engage Radius is set nil. The default Engage Radius will be used to consider a defenser being assigned to a task. + -- -- The Engage Radius is set nil. The default Engage Radius will be used to consider a defender being assigned to a task. -- -- The GCI Radius is nil. Any target detected within the default GCI Radius will be considered for GCI engagement. -- -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created. -- diff --git a/Moose Development/Moose/AI/AI_A2A_Gci.lua b/Moose Development/Moose/AI/AI_A2A_Gci.lua index 4a3f4570b..15653d1e0 100644 --- a/Moose Development/Moose/AI/AI_A2A_Gci.lua +++ b/Moose Development/Moose/AI/AI_A2A_Gci.lua @@ -42,8 +42,8 @@ -- -- ![Process](..\Presentations\AI_GCI\Dia10.JPG) -- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Process](..\Presentations\AI_GCI\Dia13.JPG) -- @@ -73,7 +73,7 @@ -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. -- * **@{#AI_A2A_GCI.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. -- * **@{#AI_A2A_GCI.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. +-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. -- -- ## 3. Set the Range of Engagement -- diff --git a/Moose Development/Moose/AI/AI_A2A_Patrol.lua b/Moose Development/Moose/AI/AI_A2A_Patrol.lua index d0a01bdd6..f4252fac1 100644 --- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua +++ b/Moose Development/Moose/AI/AI_A2A_Patrol.lua @@ -39,8 +39,8 @@ -- -- ![Process](..\Presentations\AI_PATROL\Dia10.JPG) -- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Process](..\Presentations\AI_PATROL\Dia11.JPG) -- @@ -68,7 +68,7 @@ -- * **RTB** ( Group ): Route the AI to the home base. -- * **Detect** ( Group ): The AI is detecting targets. -- * **Detected** ( Group ): The AI has detected new targets. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. +-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. -- -- ## 3. Set or Get the AI controllable -- @@ -100,8 +100,8 @@ -- ## 6. Manage the "out of fuel" in the AI_A2A_PATROL -- -- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, +-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated. +-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, -- while a new AI is targetted to the AI_A2A_PATROL. -- Once the time is finished, the old AI will return to the base. -- Use the method @{#AI_A2A_PATROL.ManageFuel}() to have this proces in place. @@ -109,7 +109,7 @@ -- ## 7. Manage "damage" behaviour of the AI in the AI_A2A_PATROL -- -- 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). +-- Therefore, when the damage threshold 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. -- -- === diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index fdd9c6ccc..ac1a06b7d 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -1,4 +1,4 @@ ---- **AI** - Create an automated A2G defense system based on a detection network of reconnaissance vehicles and air units, coordinating SEAD, BAI and CAP operations. +--- **AI** - Create an automated A2G defense system based on a detection network of reconnaissance vehicles and air units, coordinating SEAD, BAI and CAS operations. -- -- === -- @@ -7,14 +7,14 @@ -- * Setup quickly an A2G defense system for a coalition. -- * Setup multiple defense zones to defend specific coordinates in your battlefield. -- * Setup (SEAD) Suppression of Air Defense squadrons, to gain control in the air of enemy grounds. --- * Setup (CAS) Controlled Air Support squadrons, to attack closeby enemy ground units near friendly installations. -- * Setup (BAI) Battleground Air Interdiction squadrons to attack remote enemy ground units and targets. +-- * Setup (CAS) Controlled Air Support squadrons, to attack close by enemy ground units near friendly installations. -- * Define and use a detection network controlled by recce. --- * Define A2G defense squadrons at airbases, farps and carriers. +-- * Define A2G defense squadrons at airbases, FARPs and carriers. -- * Enable airbases for A2G defenses. -- * Add different planes and helicopter templates to squadrons. -- * Assign squadrons to execute a specific engagement type depending on threat level of the detected ground enemy unit composition. --- * Add multiple squadrons to different airbases, farps or carriers. +-- * Add multiple squadrons to different airbases, FARPs or carriers. -- * Define different ranges to engage upon. -- * Establish an automatic in air refuel process for planes using refuel tankers. -- * Setup default settings for all squadrons and A2G defenses. @@ -30,7 +30,7 @@ -- -- ## YouTube Channel: -- --- [DCS WORLD - MOOSE - A2G GCICAP - Build an automatic A2G Defense System](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0S4KMNUUJpaUs6zZHjLKNx) +-- [DCS WORLD - MOOSE - A2G DISPATCHER - Build an automatic A2G Defense System - Introduction](https://www.youtube.com/watch?v=zwSxWRAGVH8) -- -- === -- @@ -40,10 +40,10 @@ -- -- AI_A2G_DISPATCHER is the main A2G defense class that models the A2G defense system. -- --- Before you start using the AI_A2G_DISPATCHER, ask youself the following questions. +-- Before you start using the AI_A2G_DISPATCHER, ask yourself the following questions: -- -- --- ## 1. Which coalition am I modeling an A2G defense system for? blue or red? +-- ## 1. Which coalition am I modeling an A2G defense system for? Blue or red? -- -- One AI_A2G_DISPATCHER object can create a defense system for **one coalition**, which is blue or red. -- If you want to create a **mutual defense system**, for both blue and red, then you need to create **two** AI_A2G_DISPATCHER **objects**, @@ -52,7 +52,7 @@ -- -- ## 2. Which type of detection will I setup? Grouping based per AREA, per TYPE or per UNIT? (Later others will follow). -- --- The MOOSE framework leverages the @{Functional.Detection} classes to perform the reconnaissance, detecting enemy units +-- The MOOSE framework leverages the @{Functional.Detection} classes to perform the reconnaissance, detecting enemy units -- and reporting them to the head quarters. -- Several types of @{Functional.Detection} classes exist, and the most common characteristics of these classes is that they: -- @@ -73,10 +73,10 @@ -- the recce will be very effective at detecting approaching enemy targets. Therefore, always use the terrain very carefully! -- -- Airborne recce (AFAC) are also very effective. The are capable of patrolling at a functional detection altitude, --- having an overview of the whole battlefield. However, airborne recce can be vulnerable to air to ground attacks, +-- having an overview of the whole battlefield. However, airborne recce can be vulnerable to air to ground attacks, -- so you need air superiority to make them effective. -- Airborne recce will also have varying ground detection technology, which plays a big role in the effectiveness of the reconnaissance. --- Certain helicopter or plane types have ground searching radars or advanced ground scanning technology, and are very effective +-- Certain helicopter or plane types have ground searching radars or advanced ground scanning technology, and are very effective -- compared to air units having only visual detection capabilities. -- For example, for the red coalition, the Mi-28N and the Su-34; and for the blue side, the reaper, are such effective airborne recce units. -- @@ -88,7 +88,7 @@ -- -- ## 4. How do the defenses decide **when and where to engage** on approaching enemy units? -- --- The A2G dispacher needs you to setup (various) defense coordinates, which are strategic positions in the battle field to be defended. +-- The A2G dispatcher needs you to setup (various) defense coordinates, which are strategic positions in the battle field to be defended. -- Any ground based enemy approaching within the proximity of such a defense point, may trigger for a defensive action by friendly air units. -- -- There are 2 important parameters that play a role in the defensive decision making: defensiveness and reactivity. @@ -108,7 +108,7 @@ -- ## 5. Are defense coordinates and defense reactivity the only parameters? -- -- No, depending on the target type, and the threat level of the target, the probability of defense will be higher. --- In other words, when a SAM-10 radar emitter is detected, its probabilty for defense will be much higher than when a BMP-1 vehicle is +-- In other words, when a SAM-10 radar emitter is detected, its probability for defense will be much higher than when a BMP-1 vehicle is -- detected, even when both enemies are at the same distance from a defense coordinate. -- This will ensure optimal defenses, SEAD tasks will be launched much more quicker against engaging radar emitters, to ensure air superiority. -- Approaching main battle tanks will be engaged much faster, than a group of approaching trucks. @@ -117,25 +117,26 @@ -- ## 6. Which Squadrons will I create and which name will I give each Squadron? -- -- The A2G defense system works with **Squadrons**. Each Squadron must be given a unique name, that forms the **key** to the squadron. --- Several options and activities can be set per Squadron. A free format name can be given, but always ensure that the name is meaningfull +-- Several options and activities can be set per Squadron. A free format name can be given, but always ensure that the name is meaningful -- for your mission, and remember that squadron names are used for communication to the players of your mission. -- --- There are mainly 3 types of defenses: **SEAD**, **CAS** and **BAI**. +-- There are mainly 3 types of defenses: **SEAD**, **BAI**, and **CAS**. -- --- Suppression of Air Defenses (SEAD) are effective agains radar emitters. Close Air Support (CAS) is launched when the enemy is close near friendly units. +-- Suppression of Air Defenses (SEAD) are effective against radar emitters. -- Battleground Air Interdiction (BAI) tasks are launched when there are no friendlies around. +-- Close Air Support (CAS) is launched when the enemy is close near friendly units. -- -- Depending on the defense type, different payloads will be needed. See further points on squadron definition. -- -- --- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On Farps? +-- ## 7. Where will the Squadrons be located? On Airbases? On Carriers? On FARPs? -- --- Squadrons are placed at the **home base** on an **airfield**, **carrier** or **farp**. +-- Squadrons are placed at the **home base** on an **airfield**, **carrier** or **FARP**. -- Carefully plan where each Squadron will be located as part of the defense system required for mission effective defenses. -- If the home base of the squadron is too far from assumed enemy positions, then the defenses will be too late. -- The home bases must be **behind** enemy lines, you want to prevent your home bases to be engaged by enemies! -- Depending on the units applied for defenses, the home base can be further or closer to the enemies. --- Any airbase, farp or carrier can act as the launching platform for A2G defenses. +-- Any airbase, FARP, or carrier can act as the launching platform for A2G defenses. -- Carefully plan which airbases will take part in the coalition. Color each airbase **in the color of the coalition**, using the mission editor, -- or your air units will not return for landing at the airbase! -- @@ -146,7 +147,7 @@ -- These are late activated groups with one airplane or helicopter that start with a specific name, called the **template prefix**. -- The A2G defense system will select from the given templates a random template to spawn a new plane (group). -- --- A squadron will perform specific task types (SEAD, CAS or BAI). So, squadrons will require specific templates for the +-- A squadron will perform specific task types (SEAD, BAI or CAS). So, squadrons will require specific templates for the -- task types it will perform. A squadron executing SEAD defenses, will require a payload with long range anti-radar seeking missiles. -- -- @@ -157,10 +158,10 @@ -- The A2G defense system will select from the given templates a random template to spawn a new plane (group). -- -- --- ## 10. How to squadrons engage in a defensive action? +-- ## 10. How do squadrons engage in a defensive action? -- -- There are two ways how squadrons engage and execute your A2G defenses. --- Squadrons can start the defense directly from the airbase, farp or carrier. When a squadron launches a defensive group, that group +-- Squadrons can start the defense directly from the airbase, FARP or carrier. When a squadron launches a defensive group, that group -- will start directly from the airbase. The other way is to launch early on in the mission a patrolling mechanism. -- Squadrons will launch air units to patrol in specific zone(s), so that when ground enemy targets are detected, that the airborne -- A2G defenses can come immediately into action. @@ -180,26 +181,26 @@ -- ## 12. Are moving defense coordinates possible? -- -- Yes, different COORDINATE types are possible to be used. --- The COORDINATE_UNIT will help you to specify a defense coodinate that is attached to a moving unit. +-- The COORDINATE_UNIT will help you to specify a defense coordinate that is attached to a moving unit. -- -- --- ## 13. How much defense coordinates do I need to create? +-- ## 13. How many defense coordinates do I need to create? -- -- It depends, but the idea is to define only the necessary defense points that drive your mission. --- If you define too much defense points, the performance of your mission may decrease. Per defense point defined, --- all the possible enemies are evaluated. Note that each defense coordinate has a reach depending on the size of the defense radius. --- The default defense radius is about 60km, and depending on the defense reactivity, defenses will be launched when the enemy is at --- close or greater distance from the defense coordinate. +-- If you define too many defense coordinates, the performance of your mission may decrease. For each defined defense coordinate, +-- all the possible enemies are evaluated. Note that each defense coordinate has a reach depending on the size of the associated defense radius. +-- The default defense radius is about 60km. Depending on the defense reactivity, defenses will be launched when the enemy is at a +-- closer distance from the defense coordinate than the defense radius. -- -- -- ## 14. For each Squadron doing patrols, what are the time intervals and patrol amounts to be performed? -- -- For each patrol: -- --- * **How many** patrol you want to have airborne at the same time? +-- * **How many** patrols you want to have airborne at the same time? -- * **How frequent** you want the defense mechanism to check whether to start a new patrol? -- --- other considerations: +-- Other considerations: -- -- * **How far** is the patrol area from the engagement "hot zone". You want to ensure that the enemy is reached on time! -- * **How safe** is the patrol area taking into account air superiority. Is it well defended, are there nearby A2A bases? @@ -214,9 +215,8 @@ -- * From a parking spot with running engines -- * From a parking spot with cold engines -- --- **The default takeoff method is staight in the air.** --- This takeoff method is the most useful if you want to avoid airplane clutter at airbases! --- But it is the least realistic one! +-- **The default takeoff method is straight in the air.** +-- This takeoff method is the most useful if you want to avoid airplane clutter at airbases, but it is the least realistic one. -- -- -- ## 16. For each Squadron, which landing method will I use? @@ -227,20 +227,19 @@ -- * Despawn after landing on the runway -- * Despawn after engine shutdown after landing -- --- **The default landing method is despawn when near the airbase when returning.** --- This landing method is the most useful if you want to avoid airplane clutter at airbases! --- But it is the least realistic one! +-- **The default landing method is to despawn when near the airbase when returning.** +-- This landing method is the most useful if you want to avoid aircraft clutter at airbases, but it is the least realistic one. -- -- -- ## 19. For each Squadron, which **defense overhead** will I use? -- -- For each Squadron, depending on the helicopter or airplane type (modern, old) and payload, which overhead is required to provide any defense? -- --- In other words, if **X** enemy ground units are detected, how many **Y** defense helicpters or airplanes need to engage (per squadron)? --- The **Y** is dependent on the type of airplane (era), payload, fuel levels, skills etc. +-- In other words, if **X** enemy ground units are detected, how many **Y** defense helicopters or airplanes need to engage (per squadron)? +-- The **Y** is dependent on the type of aircraft (era), payload, fuel levels, skills etc. -- But the most important factor is the payload, which is the amount of A2G weapons the defense can carry to attack the enemy ground units. --- For example, a Ka-50 can carry 16 vikrs, that means, that it potentially can destroy at least 8 ground units without a reload of ammunication. --- That means, that one defender can destroy more enemy ground units. +-- For example, a Ka-50 can carry 16 Vikhrs, this means that it potentially can destroy at least 8 ground units without a reload of ammunition. +-- That means, that one defender can destroy more enemy ground units. -- Thus, the overhead is a **factor** that will calculate dynamically how many **Y** defenses will be required based on **X** attackers detected. -- -- **The default overhead is 1. A smaller value than 1, like 0.25 will decrease the overhead to a 1 / 4 ratio, meaning, @@ -263,14 +262,13 @@ -- @image AI_Air_To_Ground_Dispatching.JPG - do -- AI_A2G_DISPATCHER --- AI_A2G_DISPATCHER class. -- @type AI_A2G_DISPATCHER -- @extends Tasking.DetectionManager#DETECTION_MANAGER - --- Create an automated A2G defense system based on a detection network of reconnaissance vehicles and air units, coordinating SEAD, BAI and CAP operations. + --- Create an automated A2G defense system based on a detection network of reconnaissance vehicles and air units, coordinating SEAD, BAI and CAS operations. -- -- === -- @@ -279,7 +277,7 @@ do -- AI_A2G_DISPATCHER -- Multiple defense coordinates can be setup. Defense coordinates can be strategic or tactical positions or references to strategic units or scenery. -- The A2G dispatcher will evaluate every x seconds the tactical situation around each defense coordinate. When a defense coordinate -- is under threat, it will communicate through the command center that defensive actions need to be taken and will launch groups of air units for defense. - -- The level of threat to the defense coordinate varyies upon the strength and types of the enemy units, the distance to the defense point, and the defensiveness parameters. + -- The level of threat to the defense coordinate varies upon the strength and types of the enemy units, the distance to the defense point, and the defensiveness parameters. -- Defensive actions are taken through probability, but the closer and the more threat the enemy poses to the defense coordinate, the faster it will be attacked by friendly A2G units. -- -- Please study carefully the underlying explanations how to setup and use this module, as it has many features. @@ -290,6 +288,7 @@ do -- AI_A2G_DISPATCHER -- -- # USAGE GUIDE -- + -- -- ## 1. AI\_A2G\_DISPATCHER constructor: -- -- ![Banner Image](..\Presentations\AI_A2G_DISPATCHER\AI_A2G_DISPATCHER-ME_1.JPG) @@ -297,6 +296,7 @@ do -- AI_A2G_DISPATCHER -- -- The @{#AI_A2G_DISPATCHER.New}() method creates a new AI_A2G_DISPATCHER instance. -- + -- -- ### 1.1. Define the **reconnaissance network**: -- -- As part of the AI_A2G_DISPATCHER :New() constructor, a reconnaissance network must be given as the first parameter. @@ -328,37 +328,38 @@ do -- AI_A2G_DISPATCHER -- By spawning in dynamically additional recce, you can ensure that there is sufficient reconnaissance coverage so the defense mechanism is continuously -- alerted of new enemy ground targets. -- - -- The following example defens a new reconnaissance network using a @{Functional.Detection#DETECTION_AREAS} object. + -- The following is an example defense of a new reconnaissance network using a @{Functional.Detection#DETECTION_AREAS} object. -- -- -- Define a SET_GROUP object that builds a collection of groups that define the recce network. -- -- Here we build the network with all the groups that have a name starting with CCCP Recce. - -- DetectionSetGroup = SET_GROUP:New() -- Defene a set of group objects, caled DetectionSetGroup. - -- + -- DetectionSetGroup = SET_GROUP:New() -- Define a set of group objects, called DetectionSetGroup. + -- -- DetectionSetGroup:FilterPrefixes( { "CCCP Recce" } ) -- The DetectionSetGroup will search for groups that start with the name "CCCP Recce". - -- - -- -- This command will start the dynamic filtering, so when groups spawn in or are destroyed, + -- + -- -- This command will start the dynamic filtering, so when groups spawn in or are destroyed, -- -- which have a group name starting with "CCCP Recce", then these will be automatically added or removed from the set. - -- DetectionSetGroup:FilterStart() - -- + -- DetectionSetGroup:FilterStart() + -- -- -- This command defines the reconnaissance network. -- -- It will group any detected ground enemy targets within a radius of 1km. -- -- It uses the DetectionSetGroup, which defines the set of reconnaissance groups to detect for enemy ground targets. -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 1000 ) -- - -- -- Setup the A2A dispatcher, and initialize it. + -- -- Setup the A2G dispatcher, and initialize it. -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- + -- + -- -- The above example creates a SET_GROUP instance, and stores this in the variable (object) **DetectionSetGroup**. -- **DetectionSetGroup** is then being configured to filter all active groups with a group name starting with `"CCCP Recce"` to be included in the set. - -- **DetectionSetGroup** is then calling `FilterStart()`, which is starting the dynamic filtering or inclusion of these groups. + -- **DetectionSetGroup** is then calling `FilterStart()`, which is starting the dynamic filtering or inclusion of these groups. -- Note that any destroy or new spawn of a group having a name, starting with the above prefix, will be removed or added to the set. -- - -- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is choosen. + -- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is chosen. -- - -- The `Detection` object is then passed to the @{#AI_A2G_DISPATCHER.New}() method to indicate the reconnaissance network + -- The `Detection` object is then passed to the @{#AI_A2G_DISPATCHER.New}() method to indicate the reconnaissance network -- configuration and setup the A2G defense detection mechanism. -- + -- -- ### 1.2. Setup the A2G dispatcher for both a red and blue coalition. -- -- Following the above described procedure, you'll need to create for each coalition an separate detection network, and a separate A2G dispatcher. @@ -374,19 +375,20 @@ do -- AI_A2G_DISPATCHER -- DetectionBlue = DETECTION_AREAS:New( DetectionSetGroupBlue, 1000 ) -- A2GDispatcherBlue = AI_A2G_DISPATCHER:New( DetectionBlue ) -- - -- -- Note: Also the SET_GROUP objects should be created for each coalition separately, containing each red and blue recce respectively! -- + -- -- ### 1.3. Define the enemy ground target **grouping radius**, in case you use DETECTION_AREAS: -- - -- The target grouping radius is a property of the DETECTION_AREAS class, that was passed to the AI_A2G_DISPATCHER:New() method, + -- The target grouping radius is a property of the DETECTION_AREAS class, that was passed to the AI_A2G_DISPATCHER:New() method -- but can be changed. The grouping radius should not be too small, but also depends on the types of ground forces and the way you want your mission to evolve. -- A large radius will mean large groups of enemy ground targets, while making smaller groups will result in a more fragmented defense system. -- Typically I suggest a grouping radius of 1km. This is the right balance to create efficient defenses. -- - -- Note that detected targets are constantly re-grouped, that is, when certain detected enemy ground units are moving further than the group radius, - -- then these units will become a separate area being detected. This may result in additional defenses being started by the dispatcher! - -- So don't make this value too small! Again, I advise about 1km or 1000 meters. + -- Note that detected targets are constantly re-grouped, that is, when certain detected enemy ground units are moving further than the group radius + -- then these units will become a separate area being detected. This may result in additional defenses being started by the dispatcher, + -- so don't make this value too small! Again, about 1km, or 1000 meters, is recommended. + -- -- -- ## 2. Setup (a) **Defense Coordinate(s)**. -- @@ -406,7 +408,7 @@ do -- AI_A2G_DISPATCHER -- -- Later, a COORDINATE_UNIT will be added to the framework, which can be used to assign "moving" coordinates to an A2G dispatcher. -- - -- **REMEMBER!** + -- **REMEMBER!** -- -- - **Defense coordinates are the center of the A2G dispatcher defense system!** -- - **You can define more defense coordinates to defend a larger area.** @@ -429,15 +431,16 @@ do -- AI_A2G_DISPATCHER -- This defines an A2G dispatcher which will engage on enemy ground targets within 30km radius around the defense coordinate. -- Note that the defense radius **applies to all defense coordinates** defined within the A2G dispatcher. -- + -- -- ### 2.2. The **Defense Reactivity**. -- - -- There are three levels that can be configured to tweak the defense reactivity. As explained above, the threat to a defense coordinate is + -- There are three levels that can be configured to tweak the defense reactivity. As explained above, the threat to a defense coordinate is -- also determined by the distance of the enemy ground target to the defense coordinate. -- If you want to have a **low** defense reactivity, that is, the probability that an A2G defense will engage to the enemy ground target, then - -- use the @{#AI_A2G_DISPATCHER.SetDefenseReactivityLow}() method. For medium and high reactivity, use the methods + -- use the @{#AI_A2G_DISPATCHER.SetDefenseReactivityLow}() method. For medium and high reactivity, use the methods -- @{#AI_A2G_DISPATCHER.SetDefenseReactivityMedium}() and @{#AI_A2G_DISPATCHER.SetDefenseReactivityHigh}() respectively. -- - -- Note that the reactivity of defenses is always in relation to the Defense Radius! the shorter the distance, + -- Note that the reactivity of defenses is always in relation to the Defense Radius! the shorter the distance, -- the less reactive the defenses will be in terms of distance to enemy ground targets! -- -- For example: @@ -446,11 +449,12 @@ do -- AI_A2G_DISPATCHER -- -- This defines an A2G dispatcher with high defense reactivity. -- + -- -- ## 3. **Squadrons**. -- -- The A2G dispatcher works with **Squadrons**, that need to be defined using the different methods available. -- - -- Use the method @{#AI_A2G_DISPATCHER.SetSquadron}() to **setup a new squadron** active at an airfield, farp or carrier, + -- Use the method @{#AI_A2G_DISPATCHER.SetSquadron}() to **setup a new squadron** active at an airfield, FARP or carrier, -- while defining which helicopter or plane **templates** are being used by the squadron and how many **resources** are available. -- -- **Multiple squadrons** can be defined within one A2G dispatcher, each having specific defense tasks and defense parameter settings! @@ -469,11 +473,11 @@ do -- AI_A2G_DISPATCHER -- * Control how new helicopters or aircraft are taking off from the airfield, farp or carrier (in the air, cold, hot, at the runway). -- * Control how returning helicopters or aircraft are landing at the airfield, farp or carrier (in the air near the airbase, after landing, after engine shutdown). -- * Control the **grouping** of new helicopters or aircraft spawned at the airfield, farp or carrier. If there is more than one helicopter or aircraft to be spawned, these may be grouped. - -- * Control the **overhead** or defensive strength of the squadron. Depending on the types of helicopters, planes, amount of resources and payload (weapon configuration) chosen, + -- * Control the **overhead** or defensive strength of the squadron. Depending on the types of helicopters, planes, amount of resources and payload (weapon configuration) chosen, -- the mission designer can choose to increase or reduce the amount of planes spawned. -- -- The method @{#AI_A2G_DISPATCHER.SetSquadron}() defines for you a new squadron. - -- The provided parameters are the squadron name, airbase name and a list of template prefixe, and a number that indicates the amount of resources. + -- The provided parameters are the squadron name, airbase name and a list of template prefixes, and a number that indicates the amount of resources. -- -- For example, this defines 3 new squadrons: -- @@ -489,16 +493,17 @@ do -- AI_A2G_DISPATCHER -- Squadrons can be commanded to execute 3 types of tasks, as explained above: -- -- - SEAD: Suppression of Air Defenses, which are ground targets that have medium or long range radar emitters. + -- - BAI : Battlefield Air Interdiction, which are targets further away from the front-line. -- - CAS : Close Air Support, when there are enemy ground targets close to friendly units. - -- - BAI : Battlefield Air Interdiction, which are targets further away from the frond-line. -- -- You need to configure each squadron which task types you want it to perform. Read on ... -- + -- -- ### 3.2. Squadrons enemy ground target **engagement types**. -- - -- There are two ways how targets can be engaged: directly **on call** from the airfield, farp or carrier, or through a **patrol**. + -- There are two ways how targets can be engaged: directly **on call** from the airfield, FARP or carrier, or through a **patrol**. -- - -- Patrols are extremely handy, as these will airborne your helicopters or airplanes in advance. They will patrol in defined zones outlined, + -- Patrols are extremely handy, as these will get your helicopters or airplanes airborne in advance. They will patrol in defined zones outlined, -- and will engage with the targets once commanded. If the patrol zone is close enough to the enemy ground targets, then the time required -- to engage is heavily minimized! -- @@ -506,13 +511,14 @@ do -- AI_A2G_DISPATCHER -- -- The mission designer needs to carefully balance the need for patrols or the need for engagement on call from the airfields. -- + -- -- ### 3.3. Squadron **on call** engagement. -- -- So to make squadrons engage targets from the airfields, use the following methods: -- -- - For SEAD, use the @{#AI_A2G_DISPATCHER.SetSquadronSead}() method. - -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCas}() method. -- - For BAI, use the @{#AI_A2G_DISPATCHER.SetSquadronBai}() method. + -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCas}() method. -- -- Note that for the tasks, specific helicopter or airplane templates are required to be used, which you can configure using your mission editor. -- Especially the payload (weapons configuration) is important to get right. @@ -522,11 +528,12 @@ do -- AI_A2G_DISPATCHER -- A2GDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50 SEAD" }, 10 ) -- A2GDispatcher:SetSquadronSead( "Maykop SEAD", 120, 250 ) -- + -- A2GDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50 BAI" }, 10 ) + -- A2GDispatcher:SetSquadronBai( "Maykop BAI", 120, 250 ) + -- -- A2GDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50 CAS" }, 10 ) -- A2GDispatcher:SetSquadronCas( "Maykop CAS", 120, 250 ) -- - -- A2GDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50 BAI" }, 10 ) - -- A2GDispatcher:SetSquadronBai( "Maykop BAI", 120, 250 ) -- -- ### 3.4. Squadron **on patrol engagement**. -- @@ -536,14 +543,14 @@ do -- AI_A2G_DISPATCHER -- So to make squadrons engage targets from a patrol zone, use the following methods: -- -- - For SEAD, use the @{#AI_A2G_DISPATCHER.SetSquadronSeadPatrol}() method. - -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCasPatrol}() method. -- - For BAI, use the @{#AI_A2G_DISPATCHER.SetSquadronBaiPatrol}() method. + -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCasPatrol}() method. -- -- Because a patrol requires more parameters, the following methods must be used to fine-tune the patrols for each squadron. -- -- - For SEAD, use the @{#AI_A2G_DISPATCHER.SetSquadronSeadPatrolInterval}() method. - -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCasPatrolInterval}() method. -- - For BAI, use the @{#AI_A2G_DISPATCHER.SetSquadronBaiPatrolInterval}() method. + -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCasPatrolInterval}() method. -- -- Here an example to setup patrols of various task types: -- @@ -551,16 +558,16 @@ do -- AI_A2G_DISPATCHER -- A2GDispatcher:SetSquadronSeadPatrol( "Maykop SEAD", PatrolZone, 300, 500, 50, 80, 250, 300 ) -- A2GDispatcher:SetSquadronPatrolInterval( "Maykop SEAD", 2, 30, 60, 1, "SEAD" ) -- - -- A2GDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50 CAS" }, 10 ) - -- A2GDispatcher:SetSquadronCasPatrol( "Maykop CAS", PatrolZone, 600, 700, 50, 80, 250, 300 ) - -- A2GDispatcher:SetSquadronPatrolInterval( "Maykop CAS", 2, 30, 60, 1, "CAS" ) - -- -- A2GDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50 BAI" }, 10 ) -- A2GDispatcher:SetSquadronBaiPatrol( "Maykop BAI", PatrolZone, 800, 900, 50, 80, 250, 300 ) -- A2GDispatcher:SetSquadronPatrolInterval( "Maykop BAI", 2, 30, 60, 1, "BAI" ) -- + -- A2GDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50 CAS" }, 10 ) + -- A2GDispatcher:SetSquadronCasPatrol( "Maykop CAS", PatrolZone, 600, 700, 50, 80, 250, 300 ) + -- A2GDispatcher:SetSquadronPatrolInterval( "Maykop CAS", 2, 30, 60, 1, "CAS" ) + -- -- - -- ### 3.5. Set squadron take-off methods + -- ### 3.5. Set squadron takeoff methods -- -- Use the various SetSquadronTakeoff... methods to control how squadrons are taking-off from the home airfield, FARP or ship. -- @@ -579,24 +586,24 @@ do -- AI_A2G_DISPATCHER -- * aircraft may get into a "dead-lock" situation, where two aircraft are blocking each other. -- * aircraft may collide at the airbase. -- * aircraft may be awaiting the landing of a plane currently in the air, but never lands ... - -- + -- -- Currently within the DCS engine, the airfield traffic coordination is erroneous and contains a lot of bugs. - -- If you experience while testing problems with aircraft take-off or landing, please use one of the above methods as a solution to workaround these issues! + -- If you experience while testing problems with aircraft takeoff or landing, please use one of the above methods as a solution to workaround these issues! -- -- This example sets the default takeoff method to be from the runway. -- And for a couple of squadrons overrides this default method. -- - -- -- Setup the Takeoff methods - -- - -- -- The default takeoff - -- A2ADispatcher:SetDefaultTakeOffFromRunway() - -- - -- -- The individual takeoff per squadron - -- A2ADispatcher:SetSquadronTakeoff( "Mineralnye", AI_A2G_DISPATCHER.Takeoff.Air ) - -- A2ADispatcher:SetSquadronTakeoffInAir( "Sochi" ) - -- A2ADispatcher:SetSquadronTakeoffFromRunway( "Mozdok" ) - -- A2ADispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" ) - -- A2ADispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) + -- -- Setup the takeoff methods + -- + -- -- Set the default takeoff method + -- A2GDispatcher:SetDefaultTakeoffFromRunway() + -- + -- -- Set the individual squadrons takeoff method + -- A2GDispatcher:SetSquadronTakeoff( "Mineralnye", AI_A2G_DISPATCHER.Takeoff.Air ) + -- A2GDispatcher:SetSquadronTakeoffInAir( "Sochi" ) + -- A2GDispatcher:SetSquadronTakeoffFromRunway( "Mozdok" ) + -- A2GDispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" ) + -- A2GDispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) -- -- -- ### 3.5.1. Set Squadron takeoff altitude when spawning new aircraft in the air. @@ -608,6 +615,7 @@ do -- AI_A2G_DISPATCHER -- As part of the method @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAir}() a parameter can be specified to set the takeoff altitude. -- If this parameter is not specified, then the default altitude will be used for the squadron. -- + -- -- ### 3.5.2. Set Squadron takeoff interval. -- -- The different types of available airfields have different amounts of available launching platforms: @@ -615,13 +623,13 @@ do -- AI_A2G_DISPATCHER -- - Airbases typically have a lot of platforms. -- - FARPs have 4 platforms. -- - Ships have 2 to 4 platforms. - -- + -- -- Depending on the demand of requested takeoffs by the A2G dispatcher, an airfield can become overloaded. Too many aircraft need to be taken -- off at the same time, which will result in clutter as described above. In order to better control this behaviour, a takeoff scheduler is implemented, -- which can be used to control how many aircraft are ordered for takeoff between specific time intervals. - -- The takeff intervals can be specified per squadron, which make sense, as each squadron have a "home" airfield. + -- The takeoff intervals can be specified per squadron, which make sense, as each squadron have a "home" airfield. -- - -- For this purpose, the method @{#AI_A2G_DISPATCHER.SetSquadronTakeOffInterval}() can be used to specify the takeoff intervals of + -- For this purpose, the method @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInterval}() can be used to specify the takeoff intervals of -- aircraft groups per squadron to avoid cluttering of aircraft at airbases. -- This is especially useful for FARPs and ships. Each takeoff dispatch is queued by the dispatcher and when the interval time -- has been reached, a new group will be spawned or activated for takeoff. @@ -634,8 +642,8 @@ do -- AI_A2G_DISPATCHER -- -- Imagine a squadron launched from a FARP, with a grouping of 4. -- -- Aircraft will cold start from the FARP, and thus, a maximum of 4 aircraft can be launched at the same time. -- -- Additionally, depending on the group composition of the aircraft, defending units will be ordered for takeoff together. - -- -- It takes about 3 to 4 minutes to takeoff helicopters from FARPs in cold start. - -- A2ADispatcher:SetSquadronTakeOffInterval( "Mineralnye", 60 * 4 ) + -- -- It takes about 3 to 4 minutes for helicopters to takeoff from FARPs in cold start. + -- A2GDispatcher:SetSquadronTakeoffInterval( "Mineralnye", 60 * 4 ) -- -- -- ### 3.6. Set squadron landing methods @@ -647,9 +655,9 @@ do -- AI_A2G_DISPATCHER -- * @{#AI_A2G_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. -- * @{#AI_A2G_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. -- - -- You can use these methods to minimize the airbase coodination overhead and to increase the airbase efficiency. + -- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency. -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the - -- A2A defense system, as no new CAP or GCI planes can takeoff. + -- A2G defense system, as no new SEAD, BAI or CAS planes can takeoff. -- Note that the method @{#AI_A2G_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. -- Damaged or out-of-fuel aircraft are returning to the nearest friendly airbase and will land, and are out of control from ground control. -- @@ -659,14 +667,14 @@ do -- AI_A2G_DISPATCHER -- -- Setup the Landing methods -- -- -- The default landing method - -- A2ADispatcher:SetDefaultLandingAtRunway() + -- A2GDispatcher:SetDefaultLandingAtRunway() -- -- -- The individual landing per squadron - -- A2ADispatcher:SetSquadronLandingAtRunway( "Mineralnye" ) - -- A2ADispatcher:SetSquadronLandingNearAirbase( "Sochi" ) - -- A2ADispatcher:SetSquadronLandingAtEngineShutdown( "Mozdok" ) - -- A2ADispatcher:SetSquadronLandingNearAirbase( "Maykop" ) - -- A2ADispatcher:SetSquadronLanding( "Novo", AI_A2G_DISPATCHER.Landing.AtRunway ) + -- A2GDispatcher:SetSquadronLandingAtRunway( "Mineralnye" ) + -- A2GDispatcher:SetSquadronLandingNearAirbase( "Sochi" ) + -- A2GDispatcher:SetSquadronLandingAtEngineShutdown( "Mozdok" ) + -- A2GDispatcher:SetSquadronLandingNearAirbase( "Maykop" ) + -- A2GDispatcher:SetSquadronLanding( "Novo", AI_A2G_DISPATCHER.Landing.AtRunway ) -- -- -- ### 3.7. Set squadron **grouping**. @@ -675,7 +683,7 @@ do -- AI_A2G_DISPATCHER -- -- ![Banner Image](..\Presentations\AI_A2G_DISPATCHER\Dia12.JPG) -- - -- In the case of **on call** engagement, the @{#AI_A2G_DISPATCHER.SetSquadronGrouping}() method has additional behaviour. + -- In the case of **on call** engagement, the @{#AI_A2G_DISPATCHER.SetSquadronGrouping}() method has additional behaviour. -- When there aren't enough patrol flights airborne, a on call will be initiated for the remaining -- targets to be engaged. Depending on the grouping parameter, the spawned flights for on call aircraft are grouped into this setting. -- For example with a group setting of 2, if 3 targets are detected and cannot be engaged by the available patrols or any airborne flight, @@ -697,13 +705,13 @@ do -- AI_A2G_DISPATCHER -- -- For example, a A-10C with full long-distance A2G missiles payload, may still be less effective than a Su-23 with short range A2G missiles... -- So in this case, one may want to use the @{#AI_A2G_DISPATCHER.SetOverhead}() method to allocate more defending planes as the amount of detected attacking ground units. - -- The overhead must be given as a decimal value with 1 as the neutral value, which means that overhead values: + -- The overhead must be given as a decimal value with 1 as the neutral value, which means that overhead values: -- -- * Higher than 1.0, for example 1.5, will increase the defense unit amounts. For 4 attacking ground units detected, 6 aircraft will be spawned. -- * Lower than 1, for example 0.75, will decrease the defense unit amounts. For 4 attacking ground units detected, only 3 aircraft will be spawned. -- - -- The amount of defending units is calculated by multiplying the amount of detected attacking ground units as part of the detected group - -- multiplied by the overhead parameter, and rounded up to the smallest integer. + -- The amount of defending units is calculated by multiplying the amount of detected attacking ground units as part of the detected group + -- multiplied by the overhead parameter, and rounded up to the smallest integer. -- -- Typically, for A2G defenses, values small than 1 will be used. Here are some good values for a couple of aircraft to support CAS operations: -- @@ -712,7 +720,7 @@ do -- AI_A2G_DISPATCHER -- - A-10A: 0.25 -- - SU-25T: 0.10 -- - -- So generically, the amount of missiles that an aircraft can take will determine its attacking effectiveness. The longer the range of the missiles, + -- So generically, the amount of missiles that an aircraft can take will determine its attacking effectiveness. The longer the range of the missiles, -- the less risk that the defender may be destroyed by the enemy, thus, the less aircraft needs to be activated in a defense. -- -- The **overhead value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense overhead when the tactical situation changes. @@ -724,13 +732,13 @@ do -- AI_A2G_DISPATCHER -- -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronEngageLimit}() to limit the amount of aircraft that will engage with the enemy, per squadron. -- - -- ## 4. Set the **fuel treshold**. + -- ## 4. Set the **fuel threshold**. -- - -- When aircraft get **out of fuel** to a certain %-tage, which is by default **15% (0.15)**, there are two possible actions that can be taken: + -- When an aircraft gets **out of fuel** with only a certain % of fuel left, which is **15% (0.15)** by default, there are two possible actions that can be taken: -- - The aircraft will go RTB, and will be replaced with a new aircraft if possible. -- - The aircraft will refuel at a tanker, if a tanker has been specified for the squadron. -- - -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel treshold** of the aircraft for all squadrons. + -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel threshold** of the aircraft for all squadrons. -- -- ## 6. Other configuration options -- @@ -742,7 +750,7 @@ do -- AI_A2G_DISPATCHER -- -- ## 10. Default settings. -- - -- Default settings configure the standard behaviour of the squadrons. + -- Default settings configure the standard behaviour of the squadrons. -- This section a good overview of the different parameters that setup the behaviour of **ALL** the squadrons by default. -- Note that default behaviour can be tweaked, and thus, this will change the behaviour of all the squadrons. -- Unless there is a specific behaviour set for a specific squadron, the default configured behaviour will be followed. @@ -761,7 +769,7 @@ do -- AI_A2G_DISPATCHER -- -- ## 10.2. Default landing behaviour. -- - -- The default landing behaviour is set to **near the airbase**, which means that returning airplanes will be despawned directly in the air by default. + -- The default landing behaviour is set to **near the airbase**, which means that returning aircraft will be despawned directly in the air by default. -- -- The default landing method can be set for ALL squadrons that don't have an individual landing method configured. -- @@ -780,23 +788,23 @@ do -- AI_A2G_DISPATCHER -- -- ## 10.4. Default **grouping**. -- - -- The default grouping is set to **one airplane**. That essentially means that there won't be any grouping applied by default. + -- The default grouping is set to **one aircraft**. That essentially means that there won't be any grouping applied by default. -- -- The default grouping value can be set for ALL squadrons that don't have an individual grouping value configured. -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons. + -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned aircraft for all squadrons. -- - -- ## 10.5. Default RTB fuel treshold. + -- ## 10.5. Default RTB fuel threshold. -- - -- When an airplane gets **out of fuel** to a certain %-tage, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable. + -- When an aircraft gets **out of fuel** with only a certain % of fuel left, which is **15% (0.15)** by default, it will go RTB, and will be replaced with a new aircraft when applicable. -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel treshold** of spawned airplanes for all squadrons. + -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel threshold** of spawned aircraft for all squadrons. -- - -- ## 10.6. Default RTB damage treshold. + -- ## 10.6. Default RTB damage threshold. -- - -- When an airplane is **damaged** to a certain %-tage, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable. + -- When an aircraft is **damaged** to a certain %, which is **40% (0.40)** by default, it will go RTB, and will be replaced with a new aircraft when applicable. -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage treshold** of spawned airplanes for all squadrons. + -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage threshold** of spawned aircraft for all squadrons. -- -- ## 10.7. Default settings for **patrol**. -- @@ -822,14 +830,14 @@ do -- AI_A2G_DISPATCHER -- Note that you can still change the patrol limit and patrol time intervals for each patrol individually using -- the @{#AI_A2G_DISPATCHER.SetSquadronPatrolTimeInterval}() method. -- - -- ## 10.7.3. Default tanker for refuelling when executing CAP. + -- ## 10.7.3. Default tanker for refuelling when executing SEAD, BAI and CAS operations. -- - -- Instead of sending CAP to RTB when out of fuel, you can let CAP refuel in mid air using a tanker. - -- This greatly increases the efficiency of your CAP operations. + -- Instead of sending SEAD, BAI and CAS aircraft to RTB when out of fuel, you can let SEAD, BAI and CAS aircraft refuel in mid air using a tanker. + -- This greatly increases the efficiency of your SEAD, BAI and CAS operations. -- -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. -- Then, use the method @{#AI_A2G_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher. - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultFuelThreshold}() to set the %-tage left in the defender airplane tanks when a refuel action is needed. + -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultFuelThreshold}() to set the % left in the defender aircraft tanks when a refuel action is needed. -- -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. -- @@ -837,15 +845,9 @@ do -- AI_A2G_DISPATCHER -- -- ![Banner Image](..\Presentations\AI_A2G_DISPATCHER\AI_A2G_DISPATCHER-ME_11.JPG) -- - -- -- Define the CAP - -- A2ADispatcher:SetSquadron( "Sochi", AIRBASE.Caucasus.Sochi_Adler, { "SQ CCCP SU-34" }, 20 ) - -- A2ADispatcher:SetSquadronCap( "Sochi", ZONE:New( "PatrolZone" ), 4000, 8000, 600, 800, 1000, 1300 ) - -- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 ) - -- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 ) - -- - -- -- Set the default tanker for refuelling to "Tanker", when the default fuel treshold has reached 90% fuel left. - -- A2ADispatcher:SetDefaultFuelThreshold( 0.9 ) - -- A2ADispatcher:SetDefaultTanker( "Tanker" ) + -- -- Set the default tanker for refuelling to "Tanker", when the default fuel threshold has reached 90% fuel left. + -- A2GDispatcher:SetDefaultFuelThreshold( 0.9 ) + -- A2GDispatcher:SetDefaultTanker( "Tanker" ) -- -- ## 10.8. Default settings for GCI. -- @@ -878,10 +880,10 @@ do -- AI_A2G_DISPATCHER -- ## 11. Airbase capture: -- -- Different squadrons can be located at one airbase. - -- If the airbase gets captured, that is, when there is an enemy unit near the airbase, and there aren't anymore friendlies at the airbase, the airbase will change coalition ownership. - -- As a result, the GCI and CAP will stop! - -- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes - -- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players. + -- If the airbase gets captured, that is when there is an enemy unit near the airbase and there are no friendlies at the airbase, the airbase will change coalition ownership. + -- As a result, further SEAD, BAI, and CAS operations from that airbase will stop. + -- However, the squadron will still stay alive. Any aircraft that is airborne will continue its operations until all airborne aircraft + -- of the squadron are destroyed. This is to keep consistency of air operations and avoid confusing players. -- -- -- @@ -900,9 +902,8 @@ do -- AI_A2G_DISPATCHER -- @field Core.Spawn#SPAWN Spawn The spawning object. -- @field #number ResourceCount The number of resources available. -- @field #list<#string> TemplatePrefixes The list of template prefixes. - -- @field #boolean Captured true if the squadron is captured. - -- @field #number Overhead The overhead for the squadron. - + -- @field #boolean Captured true if the squadron is captured. + -- @field #number Overhead The overhead for the squadron. --- List of defense coordinates. -- @type AI_A2G_DISPATCHER.DefenseCoordinates @@ -911,14 +912,14 @@ do -- AI_A2G_DISPATCHER --- @field #AI_A2G_DISPATCHER.DefenseCoordinates DefenseCoordinates AI_A2G_DISPATCHER.DefenseCoordinates = {} - --- Enumerator for spawns at airbases + --- Enumerator for spawns at airbases. -- @type AI_A2G_DISPATCHER.Takeoff -- @extends Wrapper.Group#GROUP.Takeoff --- @field #AI_A2G_DISPATCHER.Takeoff Takeoff AI_A2G_DISPATCHER.Takeoff = GROUP.Takeoff - --- Defnes Landing location. + --- Defines Landing location. -- @field #AI_A2G_DISPATCHER.Landing AI_A2G_DISPATCHER.Landing = { NearAirbase = 1, @@ -926,7 +927,7 @@ do -- AI_A2G_DISPATCHER AtEngineShutdown = 3, } - --- A defense queue item description + --- A defense queue item description. -- @type AI_A2G_DISPATCHER.DefenseQueueItem -- @field Squadron -- @field #AI_A2G_DISPATCHER.Squadron DefenderSquadron The squadron in the queue. @@ -938,7 +939,7 @@ do -- AI_A2G_DISPATCHER -- @field #string SquadronName The name of the squadron. --- Queue of planned defenses to be launched. - -- This queue exists because defenses must be launched on FARPS, or in the air, or on an airbase, or on carriers. + -- This queue exists because defenses must be launched from FARPs, in the air, from airbases, or from carriers. -- And some of these platforms have very limited amount of "launching" platforms. -- Therefore, this queue concept is introduced that queues each defender request. -- Depending on the location of the launching site, the queued defenders will be launched at varying time intervals. @@ -949,8 +950,7 @@ do -- AI_A2G_DISPATCHER --- @field #AI_A2G_DISPATCHER.DefenseQueue DefenseQueue AI_A2G_DISPATCHER.DefenseQueue = {} - - --- Defense approach types + --- Defense approach types. -- @type #AI_A2G_DISPATCHER.DefenseApproach AI_A2G_DISPATCHER.DefenseApproach = { Random = 1, @@ -958,9 +958,9 @@ do -- AI_A2G_DISPATCHER } --- AI_A2G_DISPATCHER constructor. - -- This is defining the A2G DISPATCHER for one coaliton. + -- This is defining the A2G DISPATCHER for one coalition. -- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units. - -- The Detection object is polymorphic, depending on the type of detection object choosen, the detection will work differently. + -- The Detection object is polymorphic, depending on the type of detection object chosen, the detection will work differently. -- @param #AI_A2G_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network. -- @return #AI_A2G_DISPATCHER self @@ -1009,11 +1009,11 @@ do -- AI_A2G_DISPATCHER self:SetDisengageRadius( 300000 ) -- The default Disengage Radius is 300 km. self:SetDefaultTakeoff( AI_A2G_DISPATCHER.Takeoff.Air ) - self:SetDefaultTakeoffInAirAltitude( 500 ) -- Default takeoff is 500 meters above the ground. + self:SetDefaultTakeoffInAirAltitude( 500 ) -- Default takeoff is 500 meters above ground level (AGL). self:SetDefaultLanding( AI_A2G_DISPATCHER.Landing.NearAirbase ) self:SetDefaultOverhead( 1 ) self:SetDefaultGrouping( 1 ) - self:SetDefaultFuelThreshold( 0.15, 0 ) -- 15% of fuel remaining in the tank will trigger the airplane to return to base or refuel. + self:SetDefaultFuelThreshold( 0.15, 0 ) -- 15% of fuel remaining in the tank will trigger the aircraft to return to base or refuel. self:SetDefaultDamageThreshold( 0.4 ) -- When 40% of damage, go RTB. self:SetDefaultPatrolTimeInterval( 180, 600 ) -- Between 180 and 600 seconds. self:SetDefaultPatrolLimit( 1 ) -- Maximum one Patrol per squadron. @@ -1157,6 +1157,7 @@ do -- AI_A2G_DISPATCHER end + --- Locks the DefenseItem from being defended. -- @param #AI_A2G_DISPATCHER self -- @param #string DetectedItemIndex The index of the detected item. @@ -1230,7 +1231,7 @@ do -- AI_A2G_DISPATCHER self:I( "Captured " .. AirbaseName ) - -- Now search for all squadrons located at the airbase, and sanatize them. + -- Now search for all squadrons located at the airbase, and sanitize them. for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do if Squadron.AirbaseName == AirbaseName then Squadron.ResourceCount = -999 -- The base has been captured, and the resources are eliminated. No more spawning. @@ -1240,12 +1241,14 @@ do -- AI_A2G_DISPATCHER end end + --- @param #AI_A2G_DISPATCHER self -- @param Core.Event#EVENTDATA EventData function AI_A2G_DISPATCHER:OnEventCrashOrDead( EventData ) self.Detection:ForgetDetectedUnit( EventData.IniUnitName ) end + --- @param #AI_A2G_DISPATCHER self -- @param Core.Event#EVENTDATA EventData function AI_A2G_DISPATCHER:OnEventLand( EventData ) @@ -1274,6 +1277,7 @@ do -- AI_A2G_DISPATCHER end end + --- @param #AI_A2G_DISPATCHER self -- @param Core.Event#EVENTDATA EventData function AI_A2G_DISPATCHER:OnEventEngineShutdown( EventData ) @@ -1295,6 +1299,7 @@ do -- AI_A2G_DISPATCHER end end + do -- Manage the defensive behaviour --- @param #AI_A2G_DISPATCHER self @@ -1304,16 +1309,19 @@ do -- AI_A2G_DISPATCHER self.DefenseCoordinates[DefenseCoordinateName] = DefenseCoordinate end + --- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:SetDefenseReactivityLow() self.DefenseReactivity = 0.05 end + --- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:SetDefenseReactivityMedium() self.DefenseReactivity = 0.15 end + --- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:SetDefenseReactivityHigh() self.DefenseReactivity = 0.5 @@ -1332,7 +1340,7 @@ do -- AI_A2G_DISPATCHER -- A2GDispatcher:SetDisengageRadius( 50000 ) -- -- -- Set 100km as the Disengage Radius. - -- A2GDispatcher:SetDisngageRadius() -- 300000 is the default value. + -- A2GDispatcher:SetDisengageRadius() -- 300000 is the default value. -- function AI_A2G_DISPATCHER:SetDisengageRadius( DisengageRadius ) @@ -1342,7 +1350,7 @@ do -- AI_A2G_DISPATCHER end - --- Define the defense radius to check if a target can be engaged by a squadron group for SEAD, CAS or BAI for defense. + --- Define the defense radius to check if a target can be engaged by a squadron group for SEAD, BAI, or CAS for defense. -- When targets are detected that are still really far off, you don't want the AI_A2G_DISPATCHER to launch defenders, as they might need to travel too far. -- You want it to wait until a certain defend radius is reached, which is calculated as: -- 1. the **distance of the closest airbase to target**, being smaller than the **Defend Radius**. @@ -1378,7 +1386,6 @@ do -- AI_A2G_DISPATCHER end - --- Define a border area to simulate a **cold war** scenario. -- A **cold war** is one where Patrol aircraft patrol their territory but will not attack enemy aircraft or launch GCI aircraft unless enemy aircraft enter their territory. In other words the EWR may detect an enemy aircraft but will only send aircraft to attack it if it crosses the border. -- A **hot war** is one where Patrol aircraft will intercept any detected enemy aircraft and GCI aircraft will launch against detected enemy aircraft without regard for territory. In other words if the ground radar can detect the enemy aircraft then it will send Patrol and GCI aircraft to attack it. @@ -1403,7 +1410,6 @@ do -- AI_A2G_DISPATCHER -- local BorderZone2 = ZONE_POLYGON( "CCCP Border2", GROUP:FindByName( "CCCP Border2" ) ) -- The GROUP object is a late activate helicopter unit. -- A2GDispatcher:SetBorderZone( { BorderZone1, BorderZone2 } ) -- - -- function AI_A2G_DISPATCHER:SetBorderZone( BorderZone ) self.Detection:SetAcceptZones( BorderZone ) @@ -1411,6 +1417,7 @@ do -- AI_A2G_DISPATCHER return self end + --- Display a tactical report every 30 seconds about which aircraft are: -- * Patrolling -- * Engaging @@ -1437,18 +1444,18 @@ do -- AI_A2G_DISPATCHER end - --- Set the default damage treshold when defenders will RTB. - -- The default damage treshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB. + --- Set the default damage threshold when defenders will RTB. + -- The default damage threshold is by default set to 40%, which means that when the aircraft is 40% damaged, it will go RTB. -- @param #AI_A2G_DISPATCHER self - -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the %-tage of the damage treshold before going RTB. + -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the % of damage when the aircraft will go RTB. -- @return #AI_A2G_DISPATCHER -- @usage -- -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default damage treshold. - -- A2GDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged. + -- -- Now Setup the default damage threshold. + -- A2GDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the aircraft is 90% damaged. -- function AI_A2G_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold ) @@ -1562,36 +1569,42 @@ do -- AI_A2G_DISPATCHER return DefenderFriendliesNearBy end + --- -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:GetDefenderTasks() return self.DefenderTasks or {} end + --- -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:GetDefenderTask( Defender ) return self.DefenderTasks[Defender] end + --- -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:GetDefenderTaskFsm( Defender ) return self:GetDefenderTask( Defender ).Fsm end + --- -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:GetDefenderTaskTarget( Defender ) return self:GetDefenderTask( Defender ).Target end + --- -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:GetDefenderTaskSquadronName( Defender ) return self:GetDefenderTask( Defender ).SquadronName end + --- -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:ClearDefenderTask( Defender ) @@ -1608,6 +1621,7 @@ do -- AI_A2G_DISPATCHER return self end + --- -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:ClearDefenderTaskTarget( Defender ) @@ -1777,6 +1791,7 @@ do -- AI_A2G_DISPATCHER return self end + --- Get an item from the Squadron table. -- @param #AI_A2G_DISPATCHER self -- @return #table @@ -1820,6 +1835,7 @@ do -- AI_A2G_DISPATCHER -- -- end + --- Check if the Squadron is visible before startup of the dispatcher. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -1843,6 +1859,7 @@ do -- AI_A2G_DISPATCHER end + --- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. -- @param #number TakeoffInterval Only Takeoff new units each specified interval in seconds in 10 seconds steps. @@ -1864,7 +1881,6 @@ do -- AI_A2G_DISPATCHER end - --- Set the squadron patrol parameters for a specific task type. -- Mission designers should not use this method, instead use the below methods. This method is used by the below methods. @@ -1875,7 +1891,7 @@ do -- AI_A2G_DISPATCHER -- -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that a Patrol is a group, so can consist out of 1 to 4 airplanes. The default is 1 Patrol group. + -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that each Patrol is a group, and can consist of 1 to 4 aircraft. The default is 1 Patrol group. -- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds. -- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds. -- @param #number Probability Is not in use, you can skip this parameter. @@ -1918,13 +1934,12 @@ do -- AI_A2G_DISPATCHER end - --- Set the squadron Patrol parameters for SEAD tasks. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that a Patrol is a group, so can consist out of 1 to 4 airplanes. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds. + -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Each Patrol group can consist of 1 to 4 aircraft. The default is 1 Patrol group. + -- @param #number LowInterval (optional) The minimum time in seconds between new Patrols being spawned. The default is 180 seconds. + -- @param #number HighInterval (optional) The maximum ttime in seconds between new Patrols being spawned. The default is 600 seconds. -- @param #number Probability Is not in use, you can skip this parameter. -- @return #AI_A2G_DISPATCHER -- @usage @@ -1944,9 +1959,9 @@ do -- AI_A2G_DISPATCHER --- Set the squadron Patrol parameters for CAS tasks. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that a Patrol is a group, so can consist out of 1 to 4 airplanes. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds. + -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Each Patrol group can consist of 1 to 4 aircraft. The default is 1 Patrol group. + -- @param #number LowInterval (optional) The minimum time in seconds between new Patrols being spawned. The default is 180 seconds. + -- @param #number HighInterval (optional) The maximum time in seconds between new Patrols being spawned. The default is 600 seconds. -- @param #number Probability Is not in use, you can skip this parameter. -- @return #AI_A2G_DISPATCHER -- @usage @@ -1966,9 +1981,9 @@ do -- AI_A2G_DISPATCHER --- Set the squadron Patrol parameters for BAI tasks. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that a Patrol is a group, so can consist out of 1 to 4 airplanes. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds. + -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Each Patrol group can consist of 1 to 4 aircraft. The default is 1 Patrol group. + -- @param #number LowInterval (optional) The minimum time in seconds between new Patrols being spawned. The default is 180 seconds. + -- @param #number HighInterval (optional) The maximum time in seconds between new Patrols being spawned. The default is 600 seconds. -- @param #number Probability Is not in use, you can skip this parameter. -- @return #AI_A2G_DISPATCHER -- @usage @@ -2004,6 +2019,7 @@ do -- AI_A2G_DISPATCHER end end + --- -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -2056,6 +2072,7 @@ do -- AI_A2G_DISPATCHER return nil end + --- Set the squadron engage limit for a specific task type. -- Mission designers should not use this method, instead use the below methods. This method is used by the below methods. -- @@ -2088,8 +2105,6 @@ do -- AI_A2G_DISPATCHER end - - --- Set a squadron to engage for suppression of air defenses, when a defense point is under attack. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -2126,6 +2141,7 @@ do -- AI_A2G_DISPATCHER return self end + --- Set a squadron to engage for suppression of air defenses, when a defense point is under attack. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -2146,6 +2162,7 @@ do -- AI_A2G_DISPATCHER return self:SetSquadronSead2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, "RADIO" ) end + --- Set the squadron SEAD engage limit. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -2162,8 +2179,6 @@ do -- AI_A2G_DISPATCHER self:SetSquadronEngageLimit( SquadronName, EngageLimit, "SEAD" ) end - - --- Set a Sead patrol for a Squadron. @@ -2277,6 +2292,7 @@ do -- AI_A2G_DISPATCHER return self end + --- Set a squadron to engage for close air support, when a defense point is under attack. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -2390,6 +2406,7 @@ do -- AI_A2G_DISPATCHER end + --- Set a squadron to engage for a battlefield area interdiction, when a defense point is under attack. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -2426,6 +2443,7 @@ do -- AI_A2G_DISPATCHER return self end + --- Set a squadron to engage for a battlefield area interdiction, when a defense point is under attack. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -2513,6 +2531,7 @@ do -- AI_A2G_DISPATCHER self:I( { BAI = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) end + --- Set a Bai patrol for a Squadron. -- The Bai patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded. -- @param #AI_A2G_DISPATCHER self @@ -2539,9 +2558,9 @@ do -- AI_A2G_DISPATCHER end - --- Defines the default amount of extra planes that will take-off as part of the defense system. + --- Defines the default amount of extra planes that will takeoff as part of the defense system. -- @param #AI_A2G_DISPATCHER self - -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- The default overhead is 1, so equal balance. The @{#AI_A2G_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2G missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. @@ -2577,10 +2596,10 @@ do -- AI_A2G_DISPATCHER end - --- Defines the amount of extra planes that will take-off as part of the defense system. + --- Defines the amount of extra planes that will takeoff as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- The default overhead is 1, so equal balance. The @{#AI_A2G_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2G missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. @@ -2620,7 +2639,7 @@ do -- AI_A2G_DISPATCHER --- Gets the overhead of planes as part of the defense system, in comparison with the attackers. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @return #number The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @return #number The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- The default overhead is 1, so equal balance. The @{#AI_A2G_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2G missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. @@ -2655,15 +2674,15 @@ do -- AI_A2G_DISPATCHER end - --- Sets the default grouping of new airplanes spawned. - -- Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense. + --- Sets the default grouping of new aircraft spawned. + -- Grouping will trigger how new aircraft will be grouped if more than one aircraft is spawned for defense. -- @param #AI_A2G_DISPATCHER self - -- @param #number Grouping The level of grouping that will be applied of the Patrol or GCI defenders. + -- @param #number Grouping The level of grouping that will be applied for the Patrol. -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Set a grouping by default per 2 airplanes. + -- -- Set a grouping by default per 2 aircraft. -- A2GDispatcher:SetDefaultGrouping( 2 ) -- -- @@ -2676,16 +2695,16 @@ do -- AI_A2G_DISPATCHER end - --- Sets the grouping of new airplanes spawned. - -- Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense. + --- Sets the Squadron grouping of new aircraft spawned. + -- Grouping will trigger how new aircraft will be grouped if more than one aircraft is spawned for defense. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number Grouping The level of grouping that will be applied of the Patrol or GCI defenders. + -- @param #number Grouping The level of grouping that will be applied for a Patrol from the Squadron. -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Set a grouping per 2 airplanes. + -- -- Set a Squadron specific grouping per 2 aircraft. -- A2GDispatcher:SetSquadronGrouping( "SquadronName", 2 ) -- -- @@ -2723,23 +2742,23 @@ do -- AI_A2G_DISPATCHER end - --- Defines the default method at which new flights will spawn and take-off as part of the defense system. + --- Defines the default method at which new flights will spawn and takeoff as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights by default take-off in the air. + -- -- Let new flights by default takeoff in the air. -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Air ) -- - -- -- Let new flights by default take-off from the runway. + -- -- Let new flights by default takeoff from the runway. -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Runway ) -- - -- -- Let new flights by default take-off from the airbase hot. + -- -- Let new flights by default takeoff from the airbase hot. -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Hot ) -- - -- -- Let new flights by default take-off from the airbase cold. + -- -- Let new flights by default takeoff from the airbase cold. -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Cold ) -- -- @@ -2752,7 +2771,7 @@ do -- AI_A2G_DISPATCHER return self end - --- Defines the method at which new flights will spawn and take-off as part of the defense system. + --- Defines the method at which new flights will spawn and takeoff as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. @@ -2760,16 +2779,16 @@ do -- AI_A2G_DISPATCHER -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off in the air. + -- -- Let new flights takeoff in the air. -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Air ) -- - -- -- Let new flights take-off from the runway. + -- -- Let new flights takeoff from the runway. -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Runway ) -- - -- -- Let new flights take-off from the airbase hot. + -- -- Let new flights takeoff from the airbase hot. -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Hot ) -- - -- -- Let new flights take-off from the airbase cold. + -- -- Let new flights takeoff from the airbase cold. -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Cold ) -- -- @@ -2784,16 +2803,16 @@ do -- AI_A2G_DISPATCHER end - --- Gets the default method at which new flights will spawn and take-off as part of the defense system. + --- Gets the default method at which new flights will spawn and takeoff as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights by default take-off in the air. + -- -- Let new flights by default takeoff in the air. -- local TakeoffMethod = A2GDispatcher:GetDefaultTakeoff() - -- if TakeOffMethod == , AI_A2G_Dispatcher.Takeoff.InAir then + -- if TakeoffMethod == , AI_A2G_Dispatcher.Takeoff.InAir then -- ... -- end -- @@ -2802,7 +2821,7 @@ do -- AI_A2G_DISPATCHER return self.DefenderDefault.Takeoff end - --- Gets the method at which new flights will spawn and take-off as part of the defense system. + --- Gets the method at which new flights will spawn and takeoff as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. @@ -2810,9 +2829,9 @@ do -- AI_A2G_DISPATCHER -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off in the air. + -- -- Let new flights takeoff in the air. -- local TakeoffMethod = A2GDispatcher:GetSquadronTakeoff( "SquadronName" ) - -- if TakeOffMethod == , AI_A2G_Dispatcher.Takeoff.InAir then + -- if TakeoffMethod == , AI_A2G_Dispatcher.Takeoff.InAir then -- ... -- end -- @@ -2823,13 +2842,13 @@ do -- AI_A2G_DISPATCHER end - --- Sets flights to default take-off in the air, as part of the defense system. + --- Sets flights to default takeoff in the air, as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights by default take-off in the air. + -- -- Let new flights by default takeoff in the air. -- A2GDispatcher:SetDefaultTakeoffInAir() -- -- @return #AI_A2G_DISPATCHER @@ -2842,7 +2861,7 @@ do -- AI_A2G_DISPATCHER end - --- Sets flights to take-off in the air, as part of the defense system. + --- Sets flights to takeoff in the air, as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number TakeoffAltitude (optional) The altitude in meters above the ground. If not given, the default takeoff altitude will be used. @@ -2850,7 +2869,7 @@ do -- AI_A2G_DISPATCHER -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off in the air. + -- -- Let new flights takeoff in the air. -- A2GDispatcher:SetSquadronTakeoffInAir( "SquadronName" ) -- -- @return #AI_A2G_DISPATCHER @@ -2867,13 +2886,13 @@ do -- AI_A2G_DISPATCHER end - --- Sets flights by default to take-off from the runway, as part of the defense system. + --- Sets flights by default to takeoff from the runway, as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights by default take-off from the runway. + -- -- Let new flights by default takeoff from the runway. -- A2GDispatcher:SetDefaultTakeoffFromRunway() -- -- @return #AI_A2G_DISPATCHER @@ -2886,14 +2905,14 @@ do -- AI_A2G_DISPATCHER end - --- Sets flights to take-off from the runway, as part of the defense system. + --- Sets flights to takeoff from the runway, as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off from the runway. + -- -- Let new flights takeoff from the runway. -- A2GDispatcher:SetSquadronTakeoffFromRunway( "SquadronName" ) -- -- @return #AI_A2G_DISPATCHER @@ -2906,13 +2925,13 @@ do -- AI_A2G_DISPATCHER end - --- Sets flights by default to take-off from the airbase at a hot location, as part of the defense system. + --- Sets flights by default to takeoff from the airbase at a hot location, as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights by default take-off at a hot parking spot. + -- -- Let new flights by default takeoff at a hot parking spot. -- A2GDispatcher:SetDefaultTakeoffFromParkingHot() -- -- @return #AI_A2G_DISPATCHER @@ -2924,14 +2943,14 @@ do -- AI_A2G_DISPATCHER return self end - --- Sets flights to take-off from the airbase at a hot location, as part of the defense system. + --- Sets flights to takeoff from the airbase at a hot location, as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off in the air. + -- -- Let new flights takeoff in the air. -- A2GDispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" ) -- -- @return #AI_A2G_DISPATCHER @@ -2944,13 +2963,13 @@ do -- AI_A2G_DISPATCHER end - --- Sets flights to by default take-off from the airbase at a cold location, as part of the defense system. + --- Sets flights to by default takeoff from the airbase at a cold location, as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off from a cold parking spot. + -- -- Let new flights takeoff from a cold parking spot. -- A2GDispatcher:SetDefaultTakeoffFromParkingCold() -- -- @return #AI_A2G_DISPATCHER @@ -2963,14 +2982,14 @@ do -- AI_A2G_DISPATCHER end - --- Sets flights to take-off from the airbase at a cold location, as part of the defense system. + --- Sets flights to takeoff from the airbase at a cold location, as part of the defense system. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- - -- -- Let new flights take-off from a cold parking spot. + -- -- Let new flights takeoff from a cold parking spot. -- A2GDispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" ) -- -- @return #AI_A2G_DISPATCHER @@ -2983,9 +3002,9 @@ do -- AI_A2G_DISPATCHER end - --- Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected. + --- Defines the default altitude where aircraft will spawn in the air and takeoff as part of the defense system, when the takeoff in the air method has been selected. -- @param #AI_A2G_DISPATCHER self - -- @param #number TakeoffAltitude The altitude in meters above the ground. + -- @param #number TakeoffAltitude The altitude in meters above ground level (AGL). -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) @@ -3002,16 +3021,16 @@ do -- AI_A2G_DISPATCHER return self end - --- Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected. + --- Defines the default altitude where aircraft will spawn in the air and takeoff as part of the defense system, when the takeoff in the air method has been selected. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number TakeoffAltitude The altitude in meters above the ground. + -- @param #number TakeoffAltitude The altitude in meters above ground level (AGL). -- @usage: -- -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- -- -- Set the default takeoff altitude when taking off in the air. - -- A2GDispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes planes start at 2000 meters above the ground. + -- A2GDispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes aircraft start at 2000 meters above ground level (AGL). -- -- @return #AI_A2G_DISPATCHER -- @@ -3084,7 +3103,7 @@ do -- AI_A2G_DISPATCHER -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) -- -- -- Let new flights by default despawn near the airbase when returning. - -- local LandingMethod = A2GDispatcher:GetDefaultLanding( AI_A2G_Dispatcher.Landing.NearAirbase ) + -- local LandingMethod = A2GDispatcher:GetDefaultLanding() -- if LandingMethod == AI_A2G_Dispatcher.Landing.NearAirbase then -- ... -- end @@ -3226,17 +3245,17 @@ do -- AI_A2G_DISPATCHER return self end - --- Set the default fuel treshold when defenders will RTB or Refuel in the air. - -- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. + --- Set the default fuel threshold when defenders will RTB or Refuel in the air. + -- The fuel threshold is by default set to 15%, which means that an aircraft will stay in the air until 15% of its fuel is remaining. -- @param #AI_A2G_DISPATCHER self - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. + -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. -- @return #AI_A2G_DISPATCHER -- @usage -- -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- A2GDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- function AI_A2G_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold ) @@ -3247,18 +3266,18 @@ do -- AI_A2G_DISPATCHER end - --- Set the fuel treshold for the squadron when defenders will RTB or Refuel in the air. - -- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. + --- Set the fuel threshold for the squadron when defenders will RTB or Refuel in the air. + -- The fuel threshold is by default set to 15%, which means that an aircraft will stay in the air until 15% of its fuel is remaining. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. + -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. -- @return #AI_A2G_DISPATCHER -- @usage -- -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- A2GDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- function AI_A2G_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold ) @@ -3278,7 +3297,7 @@ do -- AI_A2G_DISPATCHER -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- A2GDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- -- -- Now Setup the default tanker. @@ -3301,7 +3320,7 @@ do -- AI_A2G_DISPATCHER -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) -- - -- -- Now Setup the squadron fuel treshold. + -- -- Now Setup the squadron fuel threshold. -- A2GDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- -- -- Now Setup the squadron tanker. @@ -3404,7 +3423,7 @@ do -- AI_A2G_DISPATCHER -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:CountDefendersEngaged( AttackerDetection, AttackerCount ) - -- First, count the active AIGroups Units, targetting the DetectedSet + -- First, count the active AIGroups Units, targeting the DetectedSet local DefendersEngaged = 0 local DefendersTotal = 0 @@ -4279,6 +4298,7 @@ do -- AI_A2G_DISPATCHER return ShortestDistance end + --- Assigns A2G AI Tasks in relation to the detected items. -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:Order( DetectedItem ) @@ -4299,6 +4319,7 @@ do -- AI_A2G_DISPATCHER return ShortestDistance end + --- Shows the tactical display. -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:ShowTacticalDisplay( Detection ) @@ -4400,6 +4421,7 @@ do -- AI_A2G_DISPATCHER end + --- Assigns A2G AI Tasks in relation to the detected items. -- @param #AI_A2G_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. @@ -4638,7 +4660,7 @@ do local PlayersCount = 0 if PlayersNearBy then - local DetectedTreatLevel = DetectedSet:CalculateThreatLevelA2G() + local DetectedThreatLevel = DetectedSet:CalculateThreatLevelA2G() for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT local PlayerName = PlayerUnit:GetPlayerName() @@ -4648,7 +4670,7 @@ do PlayersCount = PlayersCount + 1 local PlayerType = PlayerUnit:GetTypeName() PlayerTypes[PlayerName] = PlayerType - if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then + if DetectedThreatLevel < FriendlyUnitThreatLevel + 2 then end end end @@ -4684,7 +4706,7 @@ do local FriendliesCount = 0 if FriendlyUnitsNearBy then - local DetectedTreatLevel = DetectedSet:CalculateThreatLevelA2G() + local DetectedThreatLevel = DetectedSet:CalculateThreatLevelA2G() for FriendlyUnitName, FriendlyUnitData in pairs( FriendlyUnitsNearBy ) do local FriendlyUnit = FriendlyUnitData -- Wrapper.Unit#UNIT if FriendlyUnit:IsAirPlane() then @@ -4692,7 +4714,7 @@ do FriendliesCount = FriendliesCount + 1 local FriendlyType = FriendlyUnit:GetTypeName() FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and ( FriendlyTypes[FriendlyType] + 1 ) or 1 - if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then + if DetectedThreatLevel < FriendlyUnitThreatLevel + 2 then end end end diff --git a/Moose Development/Moose/AI/AI_A2G_SEAD.lua b/Moose Development/Moose/AI/AI_A2G_SEAD.lua index 6a0a32e3c..49d7ac16c 100644 --- a/Moose Development/Moose/AI/AI_A2G_SEAD.lua +++ b/Moose Development/Moose/AI/AI_A2G_SEAD.lua @@ -42,8 +42,8 @@ -- -- ![Process](..\Presentations\AI_GCI\Dia10.JPG) -- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Process](..\Presentations\AI_GCI\Dia13.JPG) -- diff --git a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua index 7565f7a64..58e4308e4 100644 --- a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua @@ -10,11 +10,11 @@ -- * Setup (CAS) Controlled Air Support squadrons, to attack closeby enemy ground units near friendly installations. -- * Setup (BAI) Battleground Air Interdiction squadrons to attack remote enemy ground units and targets. -- * Define and use a detection network controlled by recce. --- * Define AIR defense squadrons at airbases, farps and carriers. +-- * Define AIR defense squadrons at airbases, FARPs and carriers. -- * Enable airbases for AIR defenses. -- * Add different planes and helicopter templates to squadrons. -- * Assign squadrons to execute a specific engagement type depending on threat level of the detected ground enemy unit composition. --- * Add multiple squadrons to different airbases, farps or carriers. +-- * Add multiple squadrons to different airbases, FARPs or carriers. -- * Define different ranges to engage upon. -- * Establish an automatic in air refuel process for planes using refuel tankers. -- * Setup default settings for all squadrons and AIR defenses. @@ -40,7 +40,7 @@ -- -- AI_AIR_DISPATCHER is the main AIR defense class that models the AIR defense system. -- --- Before you start using the AI_AIR_DISPATCHER, ask youself the following questions. +-- Before you start using the AI_AIR_DISPATCHER, ask yourself the following questions. -- -- -- ## 1. Which coalition am I modeling an AIR defense system for? blue or red? @@ -128,7 +128,7 @@ -- Depending on the defense type, different payloads will be needed. See further points on squadron definition. -- -- --- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On Farps? +-- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On FARPs? -- -- Squadrons are placed at the **home base** on an **airfield**, **carrier** or **farp**. -- Carefully plan where each Squadron will be located as part of the defense system required for mission effective defenses. @@ -354,7 +354,7 @@ do -- AI_AIR_DISPATCHER -- **DetectionSetGroup** is then calling `FilterStart()`, which is starting the dynamic filtering or inclusion of these groups. -- Note that any destroy or new spawn of a group having a name, starting with the above prefix, will be removed or added to the set. -- - -- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is choosen. + -- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is chosen. -- -- The `Detection` object is then passed to the @{#AI_AIR_DISPATCHER.New}() method to indicate the reconnaissance network -- configuration and setup the AIR defense detection mechanism. @@ -647,7 +647,7 @@ do -- AI_AIR_DISPATCHER -- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. -- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. -- - -- You can use these methods to minimize the airbase coodination overhead and to increase the airbase efficiency. + -- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency. -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the -- A2A defense system, as no new CAP or GCI planes can takeoff. -- Note that the method @{#AI_AIR_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. @@ -724,13 +724,13 @@ do -- AI_AIR_DISPATCHER -- -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronEngageLimit}() to limit the amount of aircraft that will engage with the enemy, per squadron. -- - -- ## 4. Set the **fuel treshold**. + -- ## 4. Set the **fuel threshold**. -- - -- When aircraft get **out of fuel** to a certain %-tage, which is by default **15% (0.15)**, there are two possible actions that can be taken: + -- When aircraft get **out of fuel** to a certain %, which is by default **15% (0.15)**, there are two possible actions that can be taken: -- - The aircraft will go RTB, and will be replaced with a new aircraft if possible. -- - The aircraft will refuel at a tanker, if a tanker has been specified for the squadron. -- - -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel treshold** of the aircraft for all squadrons. + -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel threshold** of the aircraft for all squadrons. -- -- ## 6. Other configuration options -- @@ -786,17 +786,17 @@ do -- AI_AIR_DISPATCHER -- -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons. -- - -- ## 10.5. Default RTB fuel treshold. + -- ## 10.5. Default RTB fuel threshold. -- - -- When an airplane gets **out of fuel** to a certain %-tage, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable. + -- When an airplane gets **out of fuel** to a certain %, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable. -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel treshold** of spawned airplanes for all squadrons. + -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel threshold** of spawned airplanes for all squadrons. -- - -- ## 10.6. Default RTB damage treshold. + -- ## 10.6. Default RTB damage threshold. -- - -- When an airplane is **damaged** to a certain %-tage, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable. + -- When an airplane is **damaged** to a certain %, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable. -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage treshold** of spawned airplanes for all squadrons. + -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage threshold** of spawned airplanes for all squadrons. -- -- ## 10.7. Default settings for **patrol**. -- @@ -829,7 +829,7 @@ do -- AI_AIR_DISPATCHER -- -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. -- Then, use the method @{#AI_AIR_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher. - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the %-tage left in the defender airplane tanks when a refuel action is needed. + -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the % left in the defender airplane tanks when a refuel action is needed. -- -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. -- @@ -843,7 +843,7 @@ do -- AI_AIR_DISPATCHER -- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 ) -- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 ) -- - -- -- Set the default tanker for refuelling to "Tanker", when the default fuel treshold has reached 90% fuel left. + -- -- Set the default tanker for refuelling to "Tanker", when the default fuel threshold has reached 90% fuel left. -- A2ADispatcher:SetDefaultFuelThreshold( 0.9 ) -- A2ADispatcher:SetDefaultTanker( "Tanker" ) -- @@ -882,10 +882,7 @@ do -- AI_AIR_DISPATCHER -- As a result, the GCI and CAP will stop! -- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes -- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players. - -- - -- - -- - -- + -- -- @field #AI_AIR_DISPATCHER AI_AIR_DISPATCHER = { ClassName = "AI_AIR_DISPATCHER", @@ -914,10 +911,10 @@ do -- AI_AIR_DISPATCHER --- Enumerator for spawns at airbases -- @type AI_AIR_DISPATCHER.Takeoff -- @extends Wrapper.Group#GROUP.Takeoff - + --- @field #AI_AIR_DISPATCHER.Takeoff Takeoff AI_AIR_DISPATCHER.Takeoff = GROUP.Takeoff - + --- Defnes Landing location. -- @field #AI_AIR_DISPATCHER.Landing AI_AIR_DISPATCHER.Landing = { @@ -925,7 +922,7 @@ do -- AI_AIR_DISPATCHER AtRunway = 2, AtEngineShutdown = 3, } - + --- A defense queue item description -- @type AI_AIR_DISPATCHER.DefenseQueueItem -- @field Squadron @@ -936,7 +933,7 @@ do -- AI_AIR_DISPATCHER -- @field Functional.Detection#DETECTION_BASE AttackerDetection -- @field DefenderGrouping -- @field #string SquadronName The name of the squadron. - + --- Queue of planned defenses to be launched. -- This queue exists because defenses must be launched on FARPS, or in the air, or on an airbase, or on carriers. -- And some of these platforms have very limited amount of "launching" platforms. @@ -945,40 +942,39 @@ do -- AI_AIR_DISPATCHER -- This guarantees that launched defenders are also directly existing ... -- @type AI_AIR_DISPATCHER.DefenseQueue -- @list<#AI_AIR_DISPATCHER.DefenseQueueItem> DefenseQueueItem A list of all defenses being queued ... - + --- @field #AI_AIR_DISPATCHER.DefenseQueue DefenseQueue AI_AIR_DISPATCHER.DefenseQueue = {} - - + --- Defense approach types -- @type #AI_AIR_DISPATCHER.DefenseApproach AI_AIR_DISPATCHER.DefenseApproach = { Random = 1, Distance = 2, } - + --- AI_AIR_DISPATCHER constructor. - -- This is defining the AIR DISPATCHER for one coaliton. + -- This is defining the AIR DISPATCHER for one coalition. -- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units. - -- The Detection object is polymorphic, depending on the type of detection object choosen, the detection will work differently. + -- The Detection object is polymorphic, depending on the type of detection object chosen, the detection will work differently. -- @param #AI_AIR_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network. -- @return #AI_AIR_DISPATCHER self -- @usage - -- - -- -- Setup the Detection, using DETECTION_AREAS. - -- -- First define the SET of GROUPs that are defining the EWR network. - -- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR. - -- DetectionSetGroup = SET_GROUP:New() - -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) - -- DetectionSetGroup:FilterStart() - -- - -- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius. - -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- - -- + -- + -- -- Setup the Detection, using DETECTION_AREAS. + -- -- First define the SET of GROUPs that are defining the EWR network. + -- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR. + -- DetectionSetGroup = SET_GROUP:New() + -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) + -- DetectionSetGroup:FilterStart() + -- + -- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius. + -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) + -- + -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) + -- function AI_AIR_DISPATCHER:New( Detection ) -- Inherits from DETECTION_MANAGER @@ -1435,17 +1431,17 @@ do -- AI_AIR_DISPATCHER end - --- Set the default damage treshold when defenders will RTB. - -- The default damage treshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB. + --- Set the default damage threshold when defenders will RTB. + -- The default damage threshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB. -- @param #AI_AIR_DISPATCHER self - -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the %-tage of the damage treshold before going RTB. + -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the % of the damage threshold before going RTB. -- @return #AI_AIR_DISPATCHER -- @usage -- -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default damage treshold. + -- -- Now Setup the default damage threshold. -- AIRDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged. -- function AI_AIR_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold ) @@ -1989,7 +1985,7 @@ do -- AI_AIR_DISPATCHER --- Defines the default amount of extra planes that will take-off as part of the defense system. -- @param #AI_AIR_DISPATCHER self - -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. @@ -2028,7 +2024,7 @@ do -- AI_AIR_DISPATCHER --- Defines the amount of extra planes that will take-off as part of the defense system. -- @param #AI_AIR_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. @@ -2068,7 +2064,7 @@ do -- AI_AIR_DISPATCHER --- Gets the overhead of planes as part of the defense system, in comparison with the attackers. -- @param #AI_AIR_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @return #number The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- @return #number The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. @@ -2674,17 +2670,17 @@ do -- AI_AIR_DISPATCHER return self end - --- Set the default fuel treshold when defenders will RTB or Refuel in the air. - -- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. + --- Set the default fuel threshold when defenders will RTB or Refuel in the air. + -- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. -- @param #AI_AIR_DISPATCHER self - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. + -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. -- @return #AI_AIR_DISPATCHER -- @usage -- -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- function AI_AIR_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold ) @@ -2695,18 +2691,18 @@ do -- AI_AIR_DISPATCHER end - --- Set the fuel treshold for the squadron when defenders will RTB or Refuel in the air. - -- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. + --- Set the fuel threshold for the squadron when defenders will RTB or Refuel in the air. + -- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. -- @param #AI_AIR_DISPATCHER self -- @param #string SquadronName The name of the squadron. - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. + -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. -- @return #AI_AIR_DISPATCHER -- @usage -- -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- function AI_AIR_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold ) @@ -2726,7 +2722,7 @@ do -- AI_AIR_DISPATCHER -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- - -- -- Now Setup the default fuel treshold. + -- -- Now Setup the default fuel threshold. -- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- -- -- Now Setup the default tanker. @@ -2749,7 +2745,7 @@ do -- AI_AIR_DISPATCHER -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- - -- -- Now Setup the squadron fuel treshold. + -- -- Now Setup the squadron fuel threshold. -- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- -- -- Now Setup the squadron tanker. @@ -2847,7 +2843,7 @@ do -- AI_AIR_DISPATCHER -- @param #AI_AIR_DISPATCHER self function AI_AIR_DISPATCHER:CountDefendersEngaged( AttackerDetection, AttackerCount ) - -- First, count the active AIGroups Units, targetting the DetectedSet + -- First, count the active AIGroups Units, targeting the DetectedSet local DefendersEngaged = 0 local DefendersTotal = 0 diff --git a/Moose Development/Moose/AI/AI_Air_Engage.lua b/Moose Development/Moose/AI/AI_Air_Engage.lua index 00df0ec32..31dbed41a 100644 --- a/Moose Development/Moose/AI/AI_Air_Engage.lua +++ b/Moose Development/Moose/AI/AI_Air_Engage.lua @@ -42,8 +42,8 @@ -- -- ![Process](..\Presentations\AI_GCI\Dia10.JPG) -- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Process](..\Presentations\AI_GCI\Dia13.JPG) -- diff --git a/Moose Development/Moose/AI/AI_Air_Patrol.lua b/Moose Development/Moose/AI/AI_Air_Patrol.lua index 32bd99cea..185e64572 100644 --- a/Moose Development/Moose/AI/AI_Air_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Air_Patrol.lua @@ -39,8 +39,8 @@ -- -- ![Process](..\Presentations\AI_CAP\Dia10.JPG) -- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Process](..\Presentations\AI_CAP\Dia13.JPG) -- @@ -70,7 +70,7 @@ -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. -- * **@{#AI_AIR_PATROL.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. -- * **@{#AI_AIR_PATROL.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. +-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. -- -- ## 3. Set the Range of Engagement -- diff --git a/Moose Development/Moose/AI/AI_BAI.lua b/Moose Development/Moose/AI/AI_BAI.lua index a578c1788..9da590e03 100644 --- a/Moose Development/Moose/AI/AI_BAI.lua +++ b/Moose Development/Moose/AI/AI_BAI.lua @@ -49,7 +49,7 @@ -- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone, -- using a random speed within the given altitude and speed limits. -- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- This cycle will continue until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. +-- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. -- -- ![Route Event](..\Presentations\AI_BAI\Dia5.JPG) -- @@ -87,7 +87,7 @@ -- It will keep patrolling there, until it is notified to RTB or move to another BOMB Zone. -- It can be notified to go RTB through the **RTB** event. -- --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Engage Event](..\Presentations\AI_BAI\Dia12.JPG) -- @@ -117,7 +117,7 @@ -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. -- * **@{#AI_BAI_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}. -- * **@{#AI_BAI_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the BOMB task. --- * **Status**: The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. +-- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. -- -- ## 3. Modify the Engage Zone behaviour to pinpoint a **map object** or **scenery object** -- @@ -602,7 +602,7 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To, self:SetRefreshTimeInterval( 2 ) self:SetDetectionActivated() - self:__Target( -2 ) -- Start Targetting + self:__Target( -2 ) -- Start targeting end end diff --git a/Moose Development/Moose/AI/AI_CAP.lua b/Moose Development/Moose/AI/AI_CAP.lua index a42516617..f89f2f88f 100644 --- a/Moose Development/Moose/AI/AI_CAP.lua +++ b/Moose Development/Moose/AI/AI_CAP.lua @@ -4,7 +4,7 @@ -- -- * Patrol AI airplanes within a given zone. -- * Trigger detected events when enemy airplanes are detected. --- * Manage a fuel treshold to RTB on time. +-- * Manage a fuel threshold to RTB on time. -- * Engage the enemy when detected. -- -- @@ -65,8 +65,8 @@ -- -- ![Process](..\Presentations\AI_CAP\Dia10.JPG) -- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Process](..\Presentations\AI_CAP\Dia13.JPG) -- @@ -96,7 +96,7 @@ -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. -- * **@{#AI_CAP_ZONE.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. -- * **@{#AI_CAP_ZONE.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. +-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. -- -- ## 3. Set the Range of Engagement -- diff --git a/Moose Development/Moose/AI/AI_CAS.lua b/Moose Development/Moose/AI/AI_CAS.lua index f94fb0722..de6ab65c1 100644 --- a/Moose Development/Moose/AI/AI_CAS.lua +++ b/Moose Development/Moose/AI/AI_CAS.lua @@ -49,7 +49,7 @@ -- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone, -- using a random speed within the given altitude and speed limits. -- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- This cycle will continue until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. +-- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. -- -- ![Route Event](..\Presentations\AI_CAS\Dia5.JPG) -- @@ -87,7 +87,7 @@ -- It will keep patrolling there, until it is notified to RTB or move to another CAS Zone. -- It can be notified to go RTB through the **RTB** event. -- --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Engage Event](..\Presentations\AI_CAS\Dia12.JPG) -- @@ -117,7 +117,7 @@ -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. -- * **@{#AI_CAS_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}. -- * **@{#AI_CAS_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the CAS task. --- * **Status**: The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. +-- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. -- -- === -- @@ -520,7 +520,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To, self:SetRefreshTimeInterval( 2 ) self:SetDetectionActivated() - self:__Target( -2 ) -- Start Targetting + self:__Target( -2 ) -- Start targeting end end diff --git a/Moose Development/Moose/AI/AI_Patrol.lua b/Moose Development/Moose/AI/AI_Patrol.lua index 726385f53..9fd07539a 100644 --- a/Moose Development/Moose/AI/AI_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Patrol.lua @@ -4,7 +4,7 @@ -- -- * Patrol AI airplanes within a given zone. -- * Trigger detected events when enemy airplanes are detected. --- * Manage a fuel treshold to RTB on time. +-- * Manage a fuel threshold to RTB on time. -- -- === -- @@ -72,8 +72,8 @@ -- -- ![Process](..\Presentations\AI_PATROL\Dia10.JPG) -- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. +-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. -- -- ![Process](..\Presentations\AI_PATROL\Dia11.JPG) -- @@ -101,7 +101,7 @@ -- * **RTB** ( Group ): Route the AI to the home base. -- * **Detect** ( Group ): The AI is detecting targets. -- * **Detected** ( Group ): The AI has detected new targets. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. +-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. -- -- ## 3. Set or Get the AI controllable -- @@ -133,8 +133,8 @@ -- ## 6. Manage the "out of fuel" in the AI_PATROL_ZONE -- -- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, +-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated. +-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, -- while a new AI is targetted to the AI_PATROL_ZONE. -- Once the time is finished, the old AI will return to the base. -- Use the method @{#AI_PATROL_ZONE.ManageFuel}() to have this proces in place. @@ -142,7 +142,7 @@ -- ## 7. Manage "damage" behaviour of the AI in the AI_PATROL_ZONE -- -- When the AI is damaged, it is required that a new AIControllable 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). +-- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB). -- Use the method @{#AI_PATROL_ZONE.ManageDamage}() to have this proces in place. -- -- === @@ -581,11 +581,11 @@ function AI_PATROL_ZONE:ClearDetectedUnits() end --- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROL_ZONE. +-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated. +-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROL_ZONE. -- Once the time is finished, the old AI will return to the base. -- @param #AI_PATROL_ZONE self --- @param #number PatrolFuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. +-- @param #number PatrolFuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. -- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base. -- @return #AI_PATROL_ZONE self function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime ) @@ -596,14 +596,14 @@ function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFu return self end ---- When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base. +--- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base. -- However, damage cannot be foreseen early on. --- Therefore, when the damage treshold is reached, +-- Therefore, when the damage threshold is reached, -- the AI will return immediately to the home base (RTB). -- Note that for groups, the average damage of the complete group will be calculated. --- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25. +-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25. -- @param #AI_PATROL_ZONE self --- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged. +-- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged. -- @return #AI_PATROL_ZONE self function AI_PATROL_ZONE:ManageDamage( PatrolDamageThreshold ) diff --git a/Moose Development/Moose/Cargo/Cargo.lua b/Moose Development/Moose/Cargo/Cargo.lua index df796aed1..e23e43199 100644 --- a/Moose Development/Moose/Cargo/Cargo.lua +++ b/Moose Development/Moose/Cargo/Cargo.lua @@ -194,7 +194,7 @@ -- * is of type `Workmaterials` -- * will report when a carrier is within 500 meters -- * will board to carriers when the carrier is within 500 meters from the cargo object --- * will dissapear when the cargo is within 25 meters from the carrier during boarding +-- * will disappear when the cargo is within 25 meters from the carrier during boarding -- -- So the overall syntax of the #CARGO naming tag and arguments are: -- @@ -206,27 +206,29 @@ -- * **NR=** Provide the maximum range in meters when the cargo units will be boarded within the carrier during boarding. -- Note that this option is optional, so can be omitted. The default value of the RR is 10 meters. -- --- ## 5.2) The \#CARGO tag to create CARGO_CRATE objects: +-- ## 5.2) The \#CARGO tag to create CARGO_CRATE or CARGO_SLINGLOAD objects: -- -- You can also use the \#CARGO tag on **static** objects, including **static cargo** objects of the mission editor. -- -- For example, the following #CARGO naming in the **static name** of the object, will create a CARGO_CRATE object when the mission starts. -- --- `Static #CARGO(T=Workmaterials,RR=500,NR=25)` +-- `Static #CARGO(T=Workmaterials,C=CRATE,RR=500,NR=25)` -- -- This will create a CARGO_CRATE object: -- -- * with the group name `Static #CARGO` -- * is of type `Workmaterials` +-- * is of category `CRATE` (as opposed to `SLING`) -- * will report when a carrier is within 500 meters -- * will board to carriers when the carrier is within 500 meters from the cargo object --- * will dissapear when the cargo is within 25 meters from the carrier during boarding +-- * will disappear when the cargo is within 25 meters from the carrier during boarding -- -- So the overall syntax of the #CARGO naming tag and arguments are: -- --- `StaticName #CARGO(T=CargoTypeName,RR=Range,NR=Range)` +-- `StaticName #CARGO(T=CargoTypeName,C=Category,RR=Range,NR=Range)` -- -- * **T=** Provide a text that contains the type name of the cargo object. This type name can be used to filter cargo within a SET_CARGO object. +-- * **C=** Provide either `CRATE` or `SLING` to have this static created as a CARGO_CRATE or CARGO_SLINGLOAD respectivly. -- * **RR=** Provide the minimal range in meters when the report to the carrier, and board to the carrier. -- Note that this option is optional, so can be omitted. The default value of the RR is 250 meters. -- * **NR=** Provide the maximum range in meters when the cargo units will be boarded within the carrier during boarding. diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index 5db00678f..2adc50223 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -1,28 +1,28 @@ --- **Core** - The base class within the framework. --- +-- -- === --- +-- -- ## Features: --- +-- -- * The construction and inheritance of MOOSE classes. -- * The class naming and numbering system. -- * The class hierarchy search system. --- * The tracing of information or objects during mission execution for debuggin purposes. +-- * The tracing of information or objects during mission execution for debugging purposes. -- * The subscription to DCS events for event handling in MOOSE objects. -- * Object inspection. --- +-- -- === --- +-- -- All classes within the MOOSE framework are derived from the BASE class. -- Note: The BASE class is an abstract class and is not meant to be used directly. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- ### Contributions: --- +-- ### Contributions: +-- -- === --- +-- -- @module Core.Base -- @image Core_Base.JPG @@ -42,107 +42,107 @@ local _ClassID = 0 --- BASE class -- -- # 1. BASE constructor. --- --- Any class derived from BASE, will use the @{Core.Base#BASE.New} constructor embedded in the @{Core.Base#BASE.Inherit} method. +-- +-- Any class derived from BASE, will use the @{Core.Base#BASE.New} constructor embedded in the @{Core.Base#BASE.Inherit} method. -- See an example at the @{Core.Base#BASE.New} method how this is done. --- +-- -- # 2. Trace information for debugging. --- +-- -- The BASE class contains trace methods to trace progress within a mission execution of a certain object. --- These trace methods are inherited by each MOOSE class interiting BASE, soeach object created from derived class from BASE can use the tracing methods to trace its execution. --- +-- These trace methods are inherited by each MOOSE class inheriting BASE, thus all objects created from +-- a class derived from BASE can use the tracing methods to trace its execution. +-- -- Any type of information can be passed to these tracing methods. See the following examples: --- +-- -- self:E( "Hello" ) --- +-- -- Result in the word "Hello" in the dcs.log. --- +-- -- local Array = { 1, nil, "h", { "a","b" }, "x" } -- self:E( Array ) --- --- Results with the text [1]=1,[3]="h",[4]={[1]="a",[2]="b"},[5]="x"} in the dcs.log. --- +-- +-- Results with the text [1]=1,[3]="h",[4]={[1]="a",[2]="b"},[5]="x"} in the dcs.log. +-- -- local Object1 = "Object1" -- local Object2 = 3 -- local Object3 = { Object 1, Object 2 } -- self:E( { Object1, Object2, Object3 } ) --- +-- -- Results with the text [1]={[1]="Object",[2]=3,[3]={[1]="Object",[2]=3}} in the dcs.log. --- +-- -- local SpawnObject = SPAWN:New( "Plane" ) -- local GroupObject = GROUP:FindByName( "Group" ) -- self:E( { Spawn = SpawnObject, Group = GroupObject } ) --- --- Results with the text [1]={Spawn={....),Group={...}} in the dcs.log. --- +-- +-- Results with the text [1]={Spawn={....),Group={...}} in the dcs.log. +-- -- Below a more detailed explanation of the different method types for tracing. --- +-- -- ## 2.1. Tracing methods categories. -- -- There are basically 3 types of tracing methods available: --- +-- -- * @{#BASE.F}: Used to trace the entrance of a function and its given parameters. An F is indicated at column 44 in the DCS.log file. -- * @{#BASE.T}: Used to trace further logic within a function giving optional variables or parameters. A T is indicated at column 44 in the DCS.log file. -- * @{#BASE.E}: Used to always trace information giving optional variables or parameters. An E is indicated at column 44 in the DCS.log file. --- +-- -- ## 2.2 Tracing levels. -- --- There are 3 tracing levels within MOOSE. +-- There are 3 tracing levels within MOOSE. -- These tracing levels were defined to avoid bulks of tracing to be generated by lots of objects. --- +-- -- As such, the F and T methods have additional variants to trace level 2 and 3 respectively: -- -- * @{#BASE.F2}: Trace the beginning of a function and its given parameters with tracing level 2. -- * @{#BASE.F3}: Trace the beginning of a function and its given parameters with tracing level 3. -- * @{#BASE.T2}: Trace further logic within a function giving optional variables or parameters with tracing level 2. -- * @{#BASE.T3}: Trace further logic within a function giving optional variables or parameters with tracing level 3. --- +-- -- ## 2.3. Trace activation. --- +-- -- Tracing can be activated in several ways: --- +-- -- * Switch tracing on or off through the @{#BASE.TraceOnOff}() method. -- * Activate all tracing through the @{#BASE.TraceAll}() method. -- * Activate only the tracing of a certain class (name) through the @{#BASE.TraceClass}() method. -- * Activate only the tracing of a certain method of a certain class through the @{#BASE.TraceClassMethod}() method. -- * Activate only the tracing of a certain level through the @{#BASE.TraceLevel}() method. --- +-- -- ## 2.4. Check if tracing is on. --- +-- -- The method @{#BASE.IsTrace}() will validate if tracing is activated or not. --- --- +-- -- # 3. DCS simulator Event Handling. --- --- The BASE class provides methods to catch DCS Events. These are events that are triggered from within the DCS simulator, +-- +-- The BASE class provides methods to catch DCS Events. These are events that are triggered from within the DCS simulator, -- and handled through lua scripting. MOOSE provides an encapsulation to handle these events more efficiently. --- +-- -- ## 3.1. Subscribe / Unsubscribe to DCS Events. --- +-- -- At first, the mission designer will need to **Subscribe** to a specific DCS event for the class. -- So, when the DCS event occurs, the class will be notified of that event. -- There are two methods which you use to subscribe to or unsubscribe from an event. --- +-- -- * @{#BASE.HandleEvent}(): Subscribe to a DCS Event. -- * @{#BASE.UnHandleEvent}(): Unsubscribe from a DCS Event. --- +-- -- ## 3.2. Event Handling of DCS Events. --- +-- -- Once the class is subscribed to the event, an **Event Handling** method on the object or class needs to be written that will be called -- when the DCS event occurs. The Event Handling method receives an @{Core.Event#EVENTDATA} structure, which contains a lot of information -- about the event that occurred. --- --- Find below an example of the prototype how to write an event handling function for two units: +-- +-- Find below an example of the prototype how to write an event handling function for two units: -- -- local Tank1 = UNIT:FindByName( "Tank A" ) -- local Tank2 = UNIT:FindByName( "Tank B" ) --- +-- -- -- Here we subscribe to the Dead events. So, if one of these tanks dies, the Tank1 or Tank2 objects will be notified. -- Tank1:HandleEvent( EVENTS.Dead ) -- Tank2:HandleEvent( EVENTS.Dead ) --- +-- -- --- This function is an Event Handling function that will be called when Tank1 is Dead. --- -- @param Wrapper.Unit#UNIT self +-- -- @param Wrapper.Unit#UNIT self -- -- @param Core.Event#EVENTDATA EventData -- function Tank1:OnEventDead( EventData ) -- @@ -150,49 +150,47 @@ local _ClassID = 0 -- end -- -- --- This function is an Event Handling function that will be called when Tank2 is Dead. --- -- @param Wrapper.Unit#UNIT self +-- -- @param Wrapper.Unit#UNIT self -- -- @param Core.Event#EVENTDATA EventData -- function Tank2:OnEventDead( EventData ) -- -- self:SmokeBlue() -- end --- --- --- +-- -- See the @{Event} module for more information about event handling. --- +-- -- # 4. Class identification methods. --- +-- -- BASE provides methods to get more information of each object: --- +-- -- * @{#BASE.GetClassID}(): Gets the ID (number) of the object. Each object created is assigned a number, that is incremented by one. -- * @{#BASE.GetClassName}(): Gets the name of the object, which is the name of the class the object was instantiated from. -- * @{#BASE.GetClassNameAndID}(): Gets the name and ID of the object. --- +-- -- # 5. All objects derived from BASE can have "States". --- --- A mechanism is in place in MOOSE, that allows to let the objects administer **states**. --- States are essentially properties of objects, which are identified by a **Key** and a **Value**. --- --- The method @{#BASE.SetState}() can be used to set a Value with a reference Key to the object. --- To **read or retrieve** a state Value based on a Key, use the @{#BASE.GetState} method. --- +-- +-- A mechanism is in place in MOOSE, that allows to let the objects administer **states**. +-- States are essentially properties of objects, which are identified by a **Key** and a **Value**. +-- +-- The method @{#BASE.SetState}() can be used to set a Value with a reference Key to the object. +-- To **read or retrieve** a state Value based on a Key, use the @{#BASE.GetState} method. +-- -- These two methods provide a very handy way to keep state at long lasting processes. -- Values can be stored within the objects, and later retrieved or changed when needed. -- There is one other important thing to note, the @{#BASE.SetState}() and @{#BASE.GetState} methods -- receive as the **first parameter the object for which the state needs to be set**. -- Thus, if the state is to be set for the same object as the object for which the method is used, then provide the same -- object name to the method. --- +-- -- # 6. Inheritance. --- +-- -- The following methods are available to implement inheritance --- +-- -- * @{#BASE.Inherit}: Inherits from a class. -- * @{#BASE.GetParent}: Returns the parent object from the object it is handling, or nil if there is no parent object. --- +-- -- === --- +-- -- @field #BASE BASE = { ClassName = "BASE", @@ -203,13 +201,12 @@ BASE = { Scheduler = nil, } - --- @field #BASE.__ BASE.__ = {} --- @field #BASE._ BASE._ = { - Schedules = {} --- Contains the Schedulers Active + Schedules = {}, --- Contains the Schedulers Active } --- The Formation Class @@ -217,36 +214,34 @@ BASE._ = { -- @field Cone A cone formation. FORMATION = { Cone = "Cone", - Vee = "Vee" + Vee = "Vee", } - - ---- BASE constructor. --- +--- BASE constructor. +-- -- This is an example how to use the BASE:New() constructor in a new class definition when inheriting from BASE. --- +-- -- function EVENT:New() -- local self = BASE:Inherit( self, BASE:New() ) -- #EVENT -- return self -- end --- +-- -- @param #BASE self -- @return #BASE function BASE:New() --local self = routines.utils.deepCopy( self ) -- Create a new self instance local self = UTILS.DeepCopy(self) - _ClassID = _ClassID + 1 - self.ClassID = _ClassID - - -- This is for "private" methods... - -- When a __ is passed to a method as "self", the __index will search for the method on the public method list too! --- if rawget( self, "__" ) then - --setmetatable( self, { __index = self.__ } ) --- end - - return self + _ClassID = _ClassID + 1 + self.ClassID = _ClassID + + -- This is for "private" methods... + -- When a __ is passed to a method as "self", the __index will search for the method on the public method list too! + -- if rawget( self, "__" ) then + -- setmetatable( self, { __index = self.__ } ) + -- end + + return self end --- This is the worker method to inherit from a parent class. @@ -257,29 +252,28 @@ end function BASE:Inherit( Child, Parent ) -- Create child. - local Child = routines.utils.deepCopy( Child ) + local Child = routines.utils.deepCopy( Child ) - if Child ~= nil then + if Child ~= nil then - -- This is for "private" methods... - -- When a __ is passed to a method as "self", the __index will search for the method on the public method list of the same object too! + -- This is for "private" methods... + -- When a __ is passed to a method as "self", the __index will search for the method on the public method list of the same object too! if rawget( Child, "__" ) then - setmetatable( Child, { __index = Child.__ } ) + setmetatable( Child, { __index = Child.__ } ) setmetatable( Child.__, { __index = Parent } ) else setmetatable( Child, { __index = Parent } ) end - - --Child:_SetDestructor() - end - - return Child -end + -- Child:_SetDestructor() + end + + return Child +end local function getParent( Child ) local Parent = nil - + if Child.ClassName == 'BASE' then Parent = nil else @@ -287,46 +281,44 @@ local function getParent( Child ) Parent = getmetatable( Child.__ ).__index else Parent = getmetatable( Child ).__index - end + end end return Parent end - ---- This is the worker method to retrieve the Parent class. +--- This is the worker method to retrieve the Parent class. -- Note that the Parent class must be passed to call the parent class method. --- +-- -- self:GetParent(self):ParentMethod() --- --- +-- +-- -- @param #BASE self -- @param #BASE Child This is the Child class from which the Parent class needs to be retrieved. -- @param #BASE FromClass (Optional) The class from which to get the parent. -- @return #BASE function BASE:GetParent( Child, FromClass ) - local Parent -- BASE class has no parent if Child.ClassName == 'BASE' then Parent = nil else - - --self:E({FromClass = FromClass}) - --self:E({Child = Child.ClassName}) + + -- self:E({FromClass = FromClass}) + -- self:E({Child = Child.ClassName}) if FromClass then - while( Child.ClassName ~= "BASE" and Child.ClassName ~= FromClass.ClassName ) do + while (Child.ClassName ~= "BASE" and Child.ClassName ~= FromClass.ClassName) do Child = getParent( Child ) - --self:E({Child.ClassName}) + -- self:E({Child.ClassName}) end - end + end if Child.ClassName == 'BASE' then Parent = nil else Parent = getParent( Child ) end end - --self:E({Parent.ClassName}) + -- self:E({Parent.ClassName}) return Parent end @@ -340,7 +332,7 @@ end -- * ZONE:New( 'some zone' ):IsInstanceOf( 'BASE' ) will return true -- -- * ZONE:New( 'some zone' ):IsInstanceOf( 'GROUP' ) will return false --- +-- -- @param #BASE self -- @param ClassName is the name of the class or the class itself to run the check against -- @return #boolean @@ -348,32 +340,32 @@ function BASE:IsInstanceOf( ClassName ) -- Is className NOT a string ? if type( ClassName ) ~= 'string' then - + -- Is className a Moose class ? if type( ClassName ) == 'table' and ClassName.ClassName ~= nil then - + -- Get the name of the Moose class as a string ClassName = ClassName.ClassName - - -- className is neither a string nor a Moose class, throw an error + + -- className is neither a string nor a Moose class, throw an error else - + -- I'm not sure if this should take advantage of MOOSE logging function, or throw an error for pcall - local err_str = 'className parameter should be a string; parameter received: '..type( ClassName ) + local err_str = 'className parameter should be a string; parameter received: ' .. type( ClassName ) self:E( err_str ) -- error( err_str ) return false - + end end - + ClassName = string.upper( ClassName ) if string.upper( self.ClassName ) == ClassName then return true end - local Parent = getParent(self) + local Parent = getParent( self ) while Parent do @@ -389,7 +381,7 @@ function BASE:IsInstanceOf( ClassName ) end --- Get the ClassName + ClassID of the class instance. --- The ClassName + ClassID is formatted as '%s#%09d'. +-- The ClassName + ClassID is formatted as '%s#%09d'. -- @param #BASE self -- @return #string The ClassName + ClassID of the class instance. function BASE:GetClassNameAndID() @@ -416,22 +408,21 @@ do -- Event Handling -- @param #BASE self -- @return Core.Event#EVENT function BASE:EventDispatcher() - + return _EVENTDISPATCHER end - - + --- Get the Class @{Event} processing Priority. - -- The Event processing Priority is a number from 1 to 10, + -- The Event processing Priority is a number from 1 to 10, -- reflecting the order of the classes subscribed to the Event to be processed. -- @param #BASE self -- @return #number The @{Event} processing Priority. function BASE:GetEventPriority() return self._.EventPriority or 5 end - + --- Set the Class @{Event} processing Priority. - -- The Event processing Priority is a number from 1 to 10, + -- The Event processing Priority is a number from 1 to 10, -- reflecting the order of the classes subscribed to the Event to be processed. -- @param #BASE self -- @param #number EventPriority The @{Event} processing Priority. @@ -439,37 +430,37 @@ do -- Event Handling function BASE:SetEventPriority( EventPriority ) self._.EventPriority = EventPriority end - + --- Remove all subscribed events -- @param #BASE self -- @return #BASE function BASE:EventRemoveAll() - + self:EventDispatcher():RemoveAll( self ) - + return self end - + --- Subscribe to a DCS Event. -- @param #BASE self -- @param Core.Event#EVENTS EventID Event ID. -- @param #function EventFunction (optional) The function to be called when the event occurs for the unit. -- @return #BASE function BASE:HandleEvent( EventID, EventFunction ) - + self:EventDispatcher():OnEventGeneric( EventFunction, self, EventID ) - + return self end - + --- UnSubscribe to a DCS event. -- @param #BASE self -- @param Core.Event#EVENTS EventID Event ID. -- @return #BASE function BASE:UnHandleEvent( EventID ) - + self:EventDispatcher():RemoveEvent( self, EventID ) - + return self end @@ -620,7 +611,7 @@ do -- Event Handling --- Occurs when any unit begins firing a weapon that has a high rate of fire. Most common with aircraft cannons (GAU-8), autocannons, and machine guns. -- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes. -- initiator : The unit that is doing the shooting. - -- target: The unit that is being targeted. + -- target: The unit that is being targeted. -- @function [parent=#BASE] OnEventShootingStart -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. @@ -653,16 +644,15 @@ do -- Event Handling -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. - --- Unknown precisely what creates this event, likely tied into newer damage model. Will update this page when new information become available. - -- + -- -- * initiator: The unit that had the failure. - -- + -- -- @function [parent=#BASE] OnEventDetailedFailure -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. - --- Occurs when any modification to the "Score" as seen on the debrief menu would occur. + --- Occurs when any modification to the "Score" as seen on the debrief menu would occur. -- There is no information on what values the score was changed to. Event is likely similar to player_comment in this regard. -- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes. -- @function [parent=#BASE] OnEventScore @@ -675,12 +665,12 @@ do -- Event Handling -- * initiator: The unit that killed the target -- * target: Target Object -- * weapon: Weapon Object - -- + -- -- @function [parent=#BASE] OnEventKill -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. - --- Occurs when any modification to the "Score" as seen on the debrief menu would occur. + --- Occurs when any modification to the "Score" as seen on the debrief menu would occur. -- There is no information on what values the score was changed to. Event is likely similar to player_comment in this regard. -- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes. -- @function [parent=#BASE] OnEventScore @@ -691,7 +681,7 @@ do -- Event Handling -- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes. -- -- * initiator: The unit that is was destroyed. - -- + -- -- @function [parent=#BASE] OnEventUnitLost -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. @@ -702,7 +692,7 @@ do -- Event Handling -- * initiator: Static object representing the ejected pilot. Place : Aircraft that the pilot ejected from. -- * place: may not return as a valid object if the aircraft has crashed into the ground and no longer exists. -- * subplace: is always 0 for unknown reasons. - -- + -- -- @function [parent=#BASE] OnEventLandingAfterEjection -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. @@ -743,7 +733,6 @@ do -- Event Handling -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. - --- Occurs when a player enters a slot and takes control of an aircraft. -- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes. -- **NOTE**: This is a workaround of a long standing DCS bug with the PLAYER_ENTER_UNIT event. @@ -753,7 +742,6 @@ do -- Event Handling -- @param Core.Event#EVENTDATA EventData The EventData structure. end - --- Creation of a Birth Event. -- @param #BASE self @@ -763,34 +751,35 @@ end -- @param place -- @param subplace function BASE:CreateEventBirth( EventTime, Initiator, IniUnitName, place, subplace ) - self:F( { 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 - } + local Event = { + id = world.event.S_EVENT_BIRTH, + time = EventTime, + initiator = Initiator, + IniUnitName = IniUnitName, + place = place, + subplace = subplace, + } - world.onEvent( Event ) + world.onEvent( Event ) end --- Creation of a Crash Event. -- @param #BASE self -- @param DCS#Time EventTime The time stamp of the event. -- @param DCS#Object Initiator The initiating object of the event. -function BASE:CreateEventCrash( EventTime, Initiator ) - self:F( { EventTime, Initiator } ) +function BASE:CreateEventCrash( EventTime, Initiator, IniObjectCategory ) + self:F( { EventTime, Initiator } ) - local Event = { - id = world.event.S_EVENT_CRASH, - time = EventTime, - initiator = Initiator, - } + local Event = { + id = world.event.S_EVENT_CRASH, + time = EventTime, + initiator = Initiator, + IniObjectCategory = IniObjectCategory, + } - world.onEvent( Event ) + world.onEvent( Event ) end --- Creation of a Crash Event. @@ -837,7 +826,7 @@ function BASE:CreateEventRemoveUnit( EventTime, Initiator ) id = EVENTS.RemoveUnit, time = EventTime, initiator = Initiator, - } + } world.onEvent( Event ) end @@ -853,7 +842,7 @@ function BASE:CreateEventTakeoff( EventTime, Initiator ) id = world.event.S_EVENT_TAKEOFF, time = EventTime, initiator = Initiator, - } + } world.onEvent( Event ) end @@ -876,32 +865,32 @@ end --- The main event handling function... This function captures all events generated for the class. -- @param #BASE self -- @param DCS#Event event -function BASE:onEvent(event) +function BASE:onEvent( event ) - if self then - - for EventID, EventObject in pairs(self.Events) do - if EventObject.EventEnabled then + if self then - if event.id == EventObject.Event then + for EventID, EventObject in pairs( self.Events ) do + if EventObject.EventEnabled 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 + 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 -- Scheduling @@ -958,10 +947,10 @@ do -- Scheduling 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 } ) if not self.Scheduler then @@ -999,9 +988,8 @@ do -- Scheduling end - --- Set a state or property of the Object given a Key and a Value. --- Note that if the Object is destroyed, nillified or garbage collected, then the Values and Keys will also be gone. +-- Note that if the Object is destroyed, set to nil, or garbage collected, then the Values and Keys will also be gone. -- @param #BASE self -- @param Object The object that will hold the Value set by the Key. -- @param Key The key that is used as a reference of the value. Note that the key can be a #string, but it can also be any other type! @@ -1013,13 +1001,12 @@ function BASE:SetState( Object, Key, Value ) self.States[ClassNameAndID] = self.States[ClassNameAndID] or {} self.States[ClassNameAndID][Key] = Value - + return self.States[ClassNameAndID][Key] end - --- Get a Value given a Key from the Object. --- Note that if the Object is destroyed, nillified or garbage collected, then the Values and Keys will also be gone. +-- Note that if the Object is destroyed, set to nil, or garbage collected, then the Values and Keys will also be gone. -- @param #BASE self -- @param Object The object that holds the Value set by the Key. -- @param Key The key that is used to retrieve the value. Note that the key can be a #string, but it can also be any other type! @@ -1032,7 +1019,7 @@ function BASE:GetState( Object, Key ) local Value = self.States[ClassNameAndID][Key] or false return Value end - + return nil end @@ -1071,8 +1058,6 @@ function BASE:TraceOff() self:TraceOnOff( false ) end - - --- Set trace on or off -- Note that when trace is off, no BASE.Debug statement is performed, increasing performance! -- When Moose is loaded statically, (as one file), tracing is switched off by default. @@ -1081,28 +1066,29 @@ end -- @param #BASE self -- @param #boolean TraceOnOff Switch the tracing on or off. -- @usage --- -- Switch the tracing On --- BASE:TraceOnOff( true ) --- --- -- Switch the tracing Off --- BASE:TraceOnOff( false ) +-- +-- -- Switch the tracing On +-- BASE:TraceOnOff( true ) +-- +-- -- Switch the tracing Off +-- BASE:TraceOnOff( false ) +-- function BASE:TraceOnOff( TraceOnOff ) - if TraceOnOff==false then + if TraceOnOff == false then self:I( "Tracing in MOOSE is OFF" ) _TraceOnOff = false else - self:I( "Tracing in MOOSE is ON" ) + self:I( "Tracing in MOOSE is ON" ) _TraceOnOff = true end end - --- Enquires if tracing is on (for the class). -- @param #BASE self -- @return #boolean function BASE:IsTrace() - if BASE.Debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then + if BASE.Debug and (_TraceAll == true) or (_TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName]) then return true else return false @@ -1121,13 +1107,13 @@ end -- @param #BASE self -- @param #boolean TraceAll true = trace all methods in MOOSE. function BASE:TraceAll( TraceAll ) - - if TraceAll==false then - _TraceAll=false + + if TraceAll == false then + _TraceAll = false else _TraceAll = true end - + if _TraceAll then self:I( "Tracing all methods in MOOSE " ) else @@ -1162,16 +1148,16 @@ end -- @param Arguments A #table or any field. function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam ) - if BASE.Debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then + if BASE.Debug and (_TraceAll == true) or (_TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName]) then local DebugInfoCurrent = DebugInfoCurrentParam and DebugInfoCurrentParam or BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = DebugInfoFromParam and DebugInfoFromParam or BASE.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 @@ -1181,7 +1167,7 @@ function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam ) if DebugInfoFrom then LineFrom = DebugInfoFrom.currentline end - env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)" , LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) + env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) end end end @@ -1194,14 +1180,13 @@ function BASE:F( Arguments ) if BASE.Debug and _TraceOnOff then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) - + if _TraceLevel >= 1 then self:_F( Arguments, DebugInfoCurrent, DebugInfoFrom ) end - end + end end - --- Trace a function call level 2. Must be at the beginning of the function logic. -- @param #BASE self -- @param Arguments A #table or any field. @@ -1210,11 +1195,11 @@ function BASE:F2( Arguments ) if BASE.Debug and _TraceOnOff then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) - + if _TraceLevel >= 2 then self:_F( Arguments, DebugInfoCurrent, DebugInfoFrom ) end - end + end end --- Trace a function call level 3. Must be at the beginning of the function logic. @@ -1225,11 +1210,11 @@ function BASE:F3( Arguments ) if BASE.Debug and _TraceOnOff then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) - + if _TraceLevel >= 3 then self:_F( Arguments, DebugInfoCurrent, DebugInfoFrom ) end - end + end end --- Trace a function logic. @@ -1237,28 +1222,28 @@ end -- @param Arguments A #table or any field. function BASE:_T( Arguments, DebugInfoCurrentParam, DebugInfoFromParam ) - if BASE.Debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then + if BASE.Debug and (_TraceAll == true) or (_TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName]) then local DebugInfoCurrent = DebugInfoCurrentParam and DebugInfoCurrentParam or BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = DebugInfoFromParam and DebugInfoFromParam or BASE.Debug.getinfo( 3, "l" ) - - local Function = "function" - if DebugInfoCurrent.name then - Function = DebugInfoCurrent.name - end + + 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:%30s%05d.%s" , LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) ) + local LineFrom = 0 + if DebugInfoFrom then + LineFrom = DebugInfoFrom.currentline + end + env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s", LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) ) end - end + end end --- Trace a function logic level 1. Can be anywhere within the function logic. @@ -1269,14 +1254,13 @@ function BASE:T( Arguments ) if BASE.Debug and _TraceOnOff then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) - + if _TraceLevel >= 1 then self:_T( Arguments, DebugInfoCurrent, DebugInfoFrom ) end - end + end end - --- Trace a function logic level 2. Can be anywhere within the function logic. -- @param #BASE self -- @param Arguments A #table or any field. @@ -1285,7 +1269,7 @@ function BASE:T2( Arguments ) if BASE.Debug and _TraceOnOff then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) - + if _TraceLevel >= 2 then self:_T( Arguments, DebugInfoCurrent, DebugInfoFrom ) end @@ -1300,7 +1284,7 @@ function BASE:T3( Arguments ) if BASE.Debug and _TraceOnOff then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) - + if _TraceLevel >= 3 then self:_T( Arguments, DebugInfoCurrent, DebugInfoFrom ) end @@ -1313,27 +1297,26 @@ end function BASE:E( Arguments ) if BASE.Debug then - local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) - local DebugInfoFrom = BASE.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:%30s%05d.%s(%s)" , LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) - else - env.info( string.format( "%1s:%30s%05d(%s)" , "E", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) ) - end - -end + local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) + local DebugInfoFrom = BASE.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:%30s%05d.%s(%s)", LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) + else + env.info( string.format( "%1s:%30s%05d(%s)", "E", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) ) + end + +end --- Log an information which will be traced always. Can be anywhere within the function logic. -- @param #BASE self @@ -1343,38 +1326,35 @@ function BASE:I( Arguments ) if BASE.Debug then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) - + local Function = "function" if DebugInfoCurrent.name then Function = DebugInfoCurrent.name end - + local LineCurrent = DebugInfoCurrent.currentline - local LineFrom = -1 + local LineFrom = -1 if DebugInfoFrom then LineFrom = DebugInfoFrom.currentline end - - env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)" , LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) + + env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) else - env.info( string.format( "%1s:%30s%05d(%s)" , "I", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) ) + env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) ) end - + end - - --- old stuff ---function BASE:_Destructor() +-- function BASE:_Destructor() -- --self:E("_Destructor") -- -- --self:EventRemoveAll() ---end - +-- end -- THIS IS WHY WE NEED LUA 5.2 ... ---function BASE:_SetDestructor() +-- function BASE:_SetDestructor() -- -- -- TODO: Okay, this is really technical... -- -- When you set a proxy to a table to catch __gc, weak tables don't behave like weak... @@ -1394,5 +1374,5 @@ end -- -- table is about to be garbage-collected - then the __gc hook -- -- will be invoked and the destructor called -- rawset( self, '__proxy', proxy ) --- ---end \ No newline at end of file +-- +-- end diff --git a/Moose Development/Moose/Core/Beacon.lua b/Moose Development/Moose/Core/Beacon.lua index eee6503df..ef7a5f3b6 100644 --- a/Moose Development/Moose/Core/Beacon.lua +++ b/Moose Development/Moose/Core/Beacon.lua @@ -97,11 +97,11 @@ BEACON.Type={ -- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band. -- @field #number VOR Very High Frequency Omni-Directional Range -- @field #number ILS_LOCALIZER ILS localizer --- @field #number ILS_GLIDESLOPE ILS glideslope. +-- @field #number ILS_GLIDESLOPE ILS glide slope. -- @field #number PRGM_LOCALIZER PRGM localizer. --- @field #number PRGM_GLIDESLOPE PRGM glideslope. +-- @field #number PRGM_GLIDESLOPE PRGM glide slope. -- @field #number BROADCAST_STATION Broadcast station. --- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon. +-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omni-directional range (VOR) beacon and a tactical air navigation system (TACAN) beacon. -- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band. -- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band. -- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME). @@ -211,7 +211,7 @@ function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration) -- Start beacon. self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing) - -- Stop sheduler. + -- Stop scheduler. if Duration then self.Positionable:DeactivateBeacon(Duration) end @@ -237,7 +237,7 @@ function BEACON:ActivateICLS(Channel, Callsign, Duration) -- Start beacon. self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign) - -- Stop sheduler + -- Stop scheduler if Duration then -- Schedule the stop of the BEACON if asked by the MD self.Positionable:DeactivateBeacon(Duration) end @@ -354,8 +354,8 @@ end --- Activates a general purpose Radio Beacon -- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency. --- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8. --- They can home in on these specific frequencies : +-- Although any frequency could be used, only a few DCS Modules can home on radio beacons at the time of writing, i.e. the Mi-8, Huey, Gazelle etc. +-- The following e.g. can home in on these specific frequencies : -- * **Mi8** -- * R-828 -> 20-60MHz -- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index e80f36558..60f1413b7 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -31,7 +31,6 @@ -- @module Core.Database -- @image Core_Database.JPG - --- @type DATABASE -- @field #string ClassName Name of the class. -- @field #table Templates Templates: Units, Groups, Statics, ClientsByName, ClientsByID. @@ -51,7 +50,7 @@ -- * PLAYERS -- * CARGOS -- --- On top, for internal MOOSE administration purposes, the DATBASE administers the Unit and Group TEMPLATES as defined within the Mission Editor. +-- On top, for internal MOOSE administration purposes, the DATABASE administers the Unit and Group TEMPLATES as defined within the Mission Editor. -- -- The singleton object **_DATABASE** is automatically created by MOOSE, that administers all objects within the mission. -- Moose refers to **_DATABASE** within the framework extensively, but you can also refer to the _DATABASE object within your missions if required. @@ -90,22 +89,19 @@ DATABASE = { FLIGHTCONTROLS = {}, } -local _DATABASECoalition = - { - [1] = "Red", - [2] = "Blue", - [3] = "Neutral", - } - -local _DATABASECategory = - { - ["plane"] = Unit.Category.AIRPLANE, - ["helicopter"] = Unit.Category.HELICOPTER, - ["vehicle"] = Unit.Category.GROUND_UNIT, - ["ship"] = Unit.Category.SHIP, - ["static"] = Unit.Category.STRUCTURE, - } +local _DATABASECoalition = { + [1] = "Red", + [2] = "Blue", + [3] = "Neutral", +} +local _DATABASECategory = { + ["plane"] = Unit.Category.AIRPLANE, + ["helicopter"] = Unit.Category.HELICOPTER, + ["vehicle"] = Unit.Category.GROUND_UNIT, + ["ship"] = Unit.Category.SHIP, + ["static"] = Unit.Category.STRUCTURE, +} --- Creates a new DATABASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #DATABASE self @@ -130,7 +126,7 @@ function DATABASE:New() self:HandleEvent( EVENTS.DeleteCargo ) self:HandleEvent( EVENTS.NewZone ) self:HandleEvent( EVENTS.DeleteZone ) - --self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event. + -- self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event. self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) self:_RegisterTemplates() @@ -155,7 +151,6 @@ function DATABASE:FindUnit( UnitName ) return UnitFound end - --- Adds a Unit based on the Unit Name in the DATABASE. -- @param #DATABASE self -- @param #string DCSUnitName Unit name. @@ -173,7 +168,6 @@ function DATABASE:AddUnit( DCSUnitName ) return self.UNITS[DCSUnitName] end - --- Deletes a Unit from the DATABASE based on the Unit Name. -- @param #DATABASE self function DATABASE:DeleteUnit( DCSUnitName ) @@ -194,7 +188,6 @@ function DATABASE:AddStatic( DCSStaticName ) return nil end - --- Deletes a Static from the DATABASE based on the Static Name. -- @param #DATABASE self function DATABASE:DeleteStatic( DCSStaticName ) @@ -224,7 +217,6 @@ function DATABASE:AddAirbase( AirbaseName ) return self.AIRBASES[AirbaseName] end - --- Deletes a Airbase from the DATABASE based on the Airbase Name. -- @param #DATABASE self -- @param #string AirbaseName The name of the airbase @@ -233,17 +225,6 @@ function DATABASE:DeleteAirbase( AirbaseName ) self.AIRBASES[AirbaseName] = nil end ---- Finds an AIRBASE based on the AirbaseName. --- @param #DATABASE self --- @param #string AirbaseName --- @return Wrapper.Airbase#AIRBASE The found AIRBASE. -function DATABASE:FindAirbase( AirbaseName ) - - local AirbaseFound = self.AIRBASES[AirbaseName] - return AirbaseFound -end - - do -- Zones --- Finds a @{Zone} based on the zone name. @@ -267,7 +248,6 @@ do -- Zones end end - --- Deletes a @{Zone} from the DATABASE based on the zone name. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. @@ -276,13 +256,12 @@ do -- Zones self.ZONES[ZoneName] = nil end - --- Private method that registers new ZONE_BASE derived objects within the DATABASE Object. -- @param #DATABASE self -- @return #DATABASE self function DATABASE:_RegisterZones() - for ZoneID, ZoneData in pairs(env.mission.triggers.zones) do + for ZoneID, ZoneData in pairs( env.mission.triggers.zones ) do local ZoneName = ZoneData.name -- Color @@ -374,7 +353,6 @@ do -- Zones end - end -- zone do -- Zone_Goal @@ -400,7 +378,6 @@ do -- Zone_Goal end end - --- Deletes a @{Zone} from the DATABASE based on the zone name. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. @@ -422,7 +399,6 @@ do -- cargo end end - --- Deletes a Cargo from the DATABASE based on the Cargo Name. -- @param #DATABASE self -- @param #string CargoName The name of the airbase @@ -464,38 +440,38 @@ do -- cargo for CargoGroupName, CargoGroup in pairs( Groups ) do if self:IsCargo( CargoGroupName ) then - local CargoInfo = CargoGroupName:match("#CARGO(.*)") - local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)") - local CargoName1 = CargoGroupName:match("(.*)#CARGO%(.*%)") - local CargoName2 = CargoGroupName:match(".*#CARGO%(.*%)(.*)") - local CargoName = CargoName1 .. ( CargoName2 or "" ) - local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?") - local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName - local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") ) - local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") ) + local CargoInfo = CargoGroupName:match( "#CARGO(.*)" ) + local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)" ) + local CargoName1 = CargoGroupName:match( "(.*)#CARGO%(.*%)" ) + local CargoName2 = CargoGroupName:match( ".*#CARGO%(.*%)(.*)" ) + local CargoName = CargoName1 .. (CargoName2 or "") + local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?" ) + local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?" ) or CargoName + local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?" ) ) + local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?" ) ) - self:I({"Register CargoGroup:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) + self:I( { "Register CargoGroup:", Type = Type, Name = Name, LoadRadius = LoadRadius, NearRadius = NearRadius } ) CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius ) end end for CargoStaticName, CargoStatic in pairs( self.STATICS ) do if self:IsCargo( CargoStaticName ) then - local CargoInfo = CargoStaticName:match("#CARGO(.*)") - local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)") - local CargoName = CargoStaticName:match("(.*)#CARGO") - local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?") - local Category = CargoParam and CargoParam:match( "C=([%a%d ]+),?") - local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName - local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") ) - local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") ) + local CargoInfo = CargoStaticName:match( "#CARGO(.*)" ) + local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)" ) + local CargoName = CargoStaticName:match( "(.*)#CARGO" ) + local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?" ) + local Category = CargoParam and CargoParam:match( "C=([%a%d ]+),?" ) + local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?" ) or CargoName + local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?" ) ) + local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?" ) ) if Category == "SLING" then - self:I({"Register CargoSlingload:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) + self:I( { "Register CargoSlingload:", Type = Type, Name = Name, LoadRadius = LoadRadius, NearRadius = NearRadius } ) CARGO_SLINGLOAD:New( CargoStatic, Type, Name, LoadRadius, NearRadius ) else if Category == "CRATE" then - self:I({"Register CargoCrate:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) + self:I( { "Register CargoCrate:", Type = Type, Name = Name, LoadRadius = LoadRadius, NearRadius = NearRadius } ) CARGO_CRATE:New( CargoStatic, Type, Name, LoadRadius, NearRadius ) end end @@ -516,7 +492,6 @@ function DATABASE:FindClient( ClientName ) return ClientFound end - --- Adds a CLIENT based on the ClientName in the DATABASE. -- @param #DATABASE self -- @param #string ClientName Name of the Client unit. @@ -530,7 +505,6 @@ function DATABASE:AddClient( ClientName ) return self.CLIENTS[ClientName] end - --- Finds a GROUP based on the GroupName. -- @param #DATABASE self -- @param #string GroupName @@ -541,7 +515,6 @@ function DATABASE:FindGroup( GroupName ) return GroupFound end - --- Adds a GROUP based on the GroupName in the DATABASE. -- @param #DATABASE self function DATABASE:AddGroup( GroupName ) @@ -589,7 +562,6 @@ function DATABASE:GetPlayers() return self.PLAYERS end - --- Get the player table from the DATABASE, which contains all UNIT objects. -- The player table contains all UNIT objects of the player with the key the name of the player (PlayerName). -- @param #DATABASE self @@ -602,7 +574,6 @@ function DATABASE:GetPlayerUnits() return self.PLAYERUNITS end - --- Get the player table from the DATABASE which have joined in the mission historically. -- The player table contains all UNIT objects with the key the name of the player (PlayerName). -- @param #DATABASE self @@ -615,7 +586,6 @@ function DATABASE:GetPlayersJoined() return self.PLAYERSJOINED end - --- Instantiate new Groups within the DCSRTE. -- This method expects EXACTLY the same structure as a structure within the ME, and needs 2 additional fields defined: -- SpawnCountryID, SpawnCategoryID @@ -638,7 +608,7 @@ function DATABASE:Spawn( SpawnTemplate ) SpawnTemplate.CountryID = nil SpawnTemplate.CategoryID = nil - self:_RegisterGroupTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID ) + self:_RegisterGroupTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID ) self:T3( SpawnTemplate ) coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate ) @@ -720,7 +690,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category for unit_num, UnitTemplate in pairs( GroupTemplate.units ) do - UnitTemplate.name = env.getValueDictByKey(UnitTemplate.name) + UnitTemplate.name = env.getValueDictByKey( UnitTemplate.name ) self.Templates.Units[UnitTemplate.name] = {} self.Templates.Units[UnitTemplate.name].UnitName = UnitTemplate.name @@ -740,17 +710,16 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate end - UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName + UnitNames[#UnitNames + 1] = self.Templates.Units[UnitTemplate.name].UnitName end -- Debug info. - self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName, + self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName, Coalition = self.Templates.Groups[GroupTemplateName].CoalitionID, - Category = self.Templates.Groups[GroupTemplateName].CategoryID, - Country = self.Templates.Groups[GroupTemplateName].CountryID, - Units = UnitNames - } - ) + Category = self.Templates.Groups[GroupTemplateName].CategoryID, + Country = self.Templates.Groups[GroupTemplateName].CountryID, + Units = UnitNames, + } ) end --- Get group template. @@ -797,9 +766,8 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category self:T( { Static = self.Templates.Statics[StaticTemplateName].StaticName, Coalition = self.Templates.Statics[StaticTemplateName].CoalitionID, Category = self.Templates.Statics[StaticTemplateName].CategoryID, - Country = self.Templates.Statics[StaticTemplateName].CountryID - } - ) + Country = self.Templates.Statics[StaticTemplateName].CountryID, + } ) self:AddStatic( StaticTemplateName ) @@ -815,7 +783,7 @@ function DATABASE:GetStaticGroupTemplate( StaticName ) local StaticTemplate = self.Templates.Statics[StaticName].GroupTemplate return StaticTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID else - self:E("ERROR: Static group template does NOT exist for static "..tostring(StaticName)) + self:E( "ERROR: Static group template does NOT exist for static " .. tostring( StaticName ) ) return nil end end @@ -842,7 +810,7 @@ function DATABASE:GetGroupNameFromUnitName( UnitName ) if self.Templates.Units[UnitName] then return self.Templates.Units[UnitName].GroupName else - self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName)) + self:E( "ERROR: Unit template does not exist for unit " .. tostring( UnitName ) ) return nil end end @@ -916,8 +884,6 @@ function DATABASE:GetCategoryFromAirbase( AirbaseName ) return self.AIRBASES[AirbaseName]:GetCategory() end - - --- Private method that registers all alive players in the mission. -- @param #DATABASE self -- @return #DATABASE self @@ -941,8 +907,7 @@ function DATABASE:_RegisterPlayers() return self end - ---- Private method that registers all Groups and Units within in the mission. +--- Private method that registers all Groups and Units within the mission. -- @param #DATABASE self -- @return #DATABASE self function DATABASE:_RegisterGroupsAndUnits() @@ -959,7 +924,7 @@ function DATABASE:_RegisterGroupsAndUnits() local DCSGroupName = DCSGroup:getName() -- Add group. - self:I(string.format("Register Group: %s", tostring(DCSGroupName))) + self:I( string.format( "Register Group: %s", tostring( DCSGroupName ) ) ) self:AddGroup( DCSGroupName ) -- Loop over units in group. @@ -969,12 +934,12 @@ function DATABASE:_RegisterGroupsAndUnits() local DCSUnitName = DCSUnit:getName() -- Add unit. - self:I(string.format("Register Unit: %s", tostring(DCSUnitName))) + self:I( string.format( "Register Unit: %s", tostring( DCSUnitName ) ) ) self:AddUnit( DCSUnitName ) end else - self:E({"Group does not exist: ", DCSGroup}) + self:E( { "Group does not exist: ", DCSGroup } ) end end @@ -983,7 +948,7 @@ function DATABASE:_RegisterGroupsAndUnits() return self end ---- Private method that registers all Units of skill Client or Player within in the mission. +--- Private method that registers all Units of skill Client or Player within the mission. -- @param #DATABASE self -- @return #DATABASE self function DATABASE:_RegisterClients() @@ -1001,7 +966,7 @@ end -- @param #DATABASE self function DATABASE:_RegisterStatics() - local CoalitionsData={GroupsRed=coalition.getStaticObjects(coalition.side.RED), GroupsBlue=coalition.getStaticObjects(coalition.side.BLUE), GroupsNeutral=coalition.getStaticObjects(coalition.side.NEUTRAL)} + local CoalitionsData = { GroupsRed = coalition.getStaticObjects( coalition.side.RED ), GroupsBlue = coalition.getStaticObjects( coalition.side.BLUE ), GroupsNeutral = coalition.getStaticObjects( coalition.side.NEUTRAL ) } for CoalitionId, CoalitionData in pairs( CoalitionsData ) do for DCSStaticId, DCSStatic in pairs( CoalitionData ) do @@ -1009,10 +974,10 @@ function DATABASE:_RegisterStatics() if DCSStatic:isExist() then local DCSStaticName = DCSStatic:getName() - self:I(string.format("Register Static: %s", tostring(DCSStaticName))) + self:I( string.format( "Register Static: %s", tostring( DCSStaticName ) ) ) self:AddStatic( DCSStaticName ) else - self:E( { "Static does not exist: ", DCSStatic } ) + self:E( { "Static does not exist: ", DCSStatic } ) end end end @@ -1031,7 +996,7 @@ function DATABASE:_RegisterAirbases() local DCSAirbaseName = DCSAirbase:getName() -- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now. - local airbaseID=DCSAirbase:getID() + local airbaseID = DCSAirbase:getID() -- Add and register airbase. local airbase=self:AddAirbase( DCSAirbaseName ) @@ -1043,7 +1008,7 @@ function DATABASE:_RegisterAirbases() local text=string.format("Register %s: %s (UID=%d), Runways=%d, Parking=%d [", AIRBASE.CategoryName[airbase.category], tostring(DCSAirbaseName), airbaseUID, #airbase.runways, airbase.NparkingTotal) for _,terminalType in pairs(AIRBASE.TerminalType) do if airbase.NparkingTerminal and airbase.NparkingTerminal[terminalType] then - text=text..string.format("%d=%d ", terminalType, airbase.NparkingTerminal[terminalType]) + text = text .. string.format( "%d=%d ", terminalType, airbase.NparkingTerminal[terminalType] ) end end text=text.."]" @@ -1054,7 +1019,6 @@ function DATABASE:_RegisterAirbases() return self end - --- Events --- Handles the OnBirth event for the alive units set. @@ -1077,10 +1041,10 @@ function DATABASE:_EventOnBirth( Event ) self:AddGroup( Event.IniDCSGroupName ) -- Add airbase if it was spawned later in the mission. - local DCSAirbase = Airbase.getByName(Event.IniDCSUnitName) + local DCSAirbase = Airbase.getByName( Event.IniDCSUnitName ) if DCSAirbase then - self:I(string.format("Adding airbase %s", tostring(Event.IniDCSUnitName))) - self:AddAirbase(Event.IniDCSUnitName) + self:I( string.format( "Adding airbase %s", tostring( Event.IniDCSUnitName ) ) ) + self:AddAirbase( Event.IniDCSUnitName ) end end @@ -1108,7 +1072,7 @@ function DATABASE:_EventOnBirth( Event ) -- Add client in case it does not exist already. if not client then - client=self:AddClient(Event.IniDCSUnitName) + client = self:AddClient( Event.IniDCSUnitName ) end -- Add player. @@ -1132,8 +1096,8 @@ function DATABASE:_EventOnBirth( Event ) end -end +end --- Handles the OnDead or OnCrash event for alive units set. -- @param #DATABASE self @@ -1180,7 +1144,7 @@ function DATABASE:_EventOnDeadOrCrash( Event ) -- Delete unit. if self.UNITS[Event.IniDCSUnitName] then - self:DeleteUnit(Event.IniDCSUnitName) + self:DeleteUnit( Event.IniDCSUnitName ) end -- Remove client players. @@ -1194,9 +1158,9 @@ function DATABASE:_EventOnDeadOrCrash( Event ) end -- Add airbase if it was spawned later in the mission. - local airbase=self.AIRBASES[Event.IniDCSUnitName] --Wrapper.Airbase#AIRBASE + local airbase = self.AIRBASES[Event.IniDCSUnitName] -- Wrapper.Airbase#AIRBASE if airbase and (airbase:IsHelipad() or airbase:IsShip()) then - self:DeleteAirbase(Event.IniDCSUnitName) + self:DeleteAirbase( Event.IniDCSUnitName ) end end @@ -1205,7 +1169,6 @@ function DATABASE:_EventOnDeadOrCrash( Event ) self:AccountDestroys( Event ) end - --- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event @@ -1237,13 +1200,12 @@ function DATABASE:_EventOnPlayerEnterUnit( Event ) Settings:SetPlayerMenu( Event.IniUnit ) else - self:E("ERROR: getPlayerName() returned nil for event PlayerEnterUnit") + self:E( "ERROR: getPlayerName() returned nil for event PlayerEnterUnit" ) end end end end - --- Handles the OnPlayerLeaveUnit event to clean the active players table. -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event @@ -1270,9 +1232,9 @@ function DATABASE:_EventOnPlayerLeaveUnit( Event ) self:DeletePlayer(Event.IniUnit, PlayerName) -- Client stuff. - local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT + local client = self.CLIENTS[Event.IniDCSUnitName] -- Wrapper.Client#CLIENT if client then - client:RemovePlayer(PlayerName) + client:RemovePlayer( PlayerName ) end end @@ -1292,22 +1254,22 @@ function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) local function CoRoutine() local Count = 0 for ObjectID, Object in pairs( Set ) do - self:T2( Object ) - IteratorFunction( Object, unpack( arg ) ) - Count = Count + 1 --- if Count % 100 == 0 then --- coroutine.yield( false ) --- end + self:T2( Object ) + IteratorFunction( Object, unpack( arg ) ) + Count = Count + 1 + -- if Count % 100 == 0 then + -- coroutine.yield( false ) + -- end end return true end --- local co = coroutine.create( CoRoutine ) + -- local co = coroutine.create( CoRoutine ) local co = CoRoutine local function Schedule() --- local status, res = coroutine.resume( co ) + -- local status, res = coroutine.resume( co ) local status, res = co() self:T3( { status, res } ) @@ -1323,18 +1285,17 @@ function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) return false end - --local Scheduler = SCHEDULER:New( self, Schedule, {}, 0.001, 0.001, 0 ) + -- local Scheduler = SCHEDULER:New( self, Schedule, {}, 0.001, 0.001, 0 ) Schedule() return self end - --- Iterate the DATABASE and call an iterator function for each **alive** STATIC, providing the STATIC and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a STATIC parameter. -- @return #DATABASE self -function DATABASE:ForEachStatic( IteratorFunction, FinalizeFunction, ... ) --R2.1 +function DATABASE:ForEachStatic( IteratorFunction, FinalizeFunction, ... ) -- R2.1 self:F2( arg ) self:ForEach( IteratorFunction, FinalizeFunction, arg, self.STATICS ) @@ -1342,7 +1303,6 @@ function DATABASE:ForEachStatic( IteratorFunction, FinalizeFunction, ... ) --R2 return self end - --- Iterate the DATABASE and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a UNIT parameter. @@ -1355,7 +1315,6 @@ function DATABASE:ForEachUnit( IteratorFunction, FinalizeFunction, ... ) return self end - --- Iterate the DATABASE and call an iterator function for each **alive** GROUP, providing the GROUP and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a GROUP parameter. @@ -1368,7 +1327,6 @@ function DATABASE:ForEachGroup( IteratorFunction, FinalizeFunction, ... ) return self end - --- Iterate the DATABASE and call an iterator function for each **ALIVE** player, providing the player name and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept the player name. @@ -1381,7 +1339,6 @@ function DATABASE:ForEachPlayer( IteratorFunction, FinalizeFunction, ... ) return self end - --- Iterate the DATABASE and call an iterator function for each player who has joined the mission, providing the Unit of the player and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a UNIT parameter. @@ -1406,7 +1363,6 @@ function DATABASE:ForEachPlayerUnit( IteratorFunction, FinalizeFunction, ... ) return self end - --- Iterate the DATABASE and call an iterator function for each CLIENT, providing the CLIENT to the function and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called object in the database. The function needs to accept a CLIENT parameter. @@ -1431,7 +1387,6 @@ function DATABASE:ForEachCargo( IteratorFunction, ... ) return self end - --- Handles the OnEventNewCargo event. -- @param #DATABASE self -- @param Core.Event#EVENTDATA EventData @@ -1443,7 +1398,6 @@ function DATABASE:OnEventNewCargo( EventData ) end end - --- Handles the OnEventDeleteCargo. -- @param #DATABASE self -- @param Core.Event#EVENTDATA EventData @@ -1455,7 +1409,6 @@ function DATABASE:OnEventDeleteCargo( EventData ) end end - --- Handles the OnEventNewZone event. -- @param #DATABASE self -- @param Core.Event#EVENTDATA EventData @@ -1467,7 +1420,6 @@ function DATABASE:OnEventNewZone( EventData ) end end - --- Handles the OnEventDeleteZone. -- @param #DATABASE self -- @param Core.Event#EVENTDATA EventData @@ -1479,8 +1431,6 @@ function DATABASE:OnEventDeleteZone( EventData ) end end - - --- Gets the player settings -- @param #DATABASE self -- @param #string PlayerName @@ -1490,7 +1440,6 @@ function DATABASE:GetPlayerSettings( PlayerName ) return self.PLAYERSETTINGS[PlayerName] end - --- Sets the player settings -- @param #DATABASE self -- @param #string PlayerName @@ -1532,9 +1481,9 @@ end function DATABASE:FindOpsGroup(groupname) -- Get group and group name. - if type(groupname)=="string" then + if type( groupname ) == "string" then else - groupname=groupname:GetName() + groupname = groupname:GetName() end --env.info("Getting OPSGROUP "..tostring(groupname)) @@ -1571,16 +1520,16 @@ end --- Add a flight control to the data base. -- @param #DATABASE self -- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol -function DATABASE:AddFlightControl(flightcontrol) +function DATABASE:AddFlightControl( flightcontrol ) self:F2( { flightcontrol } ) - self.FLIGHTCONTROLS[flightcontrol.airbasename]=flightcontrol + self.FLIGHTCONTROLS[flightcontrol.airbasename] = flightcontrol end --- Get a flight control object from the data base. -- @param #DATABASE self -- @param #string airbasename Name of the associated airbase. -- @return Ops.FlightControl#FLIGHTCONTROL The FLIGHTCONTROL object.s -function DATABASE:GetFlightControl(airbasename) +function DATABASE:GetFlightControl( airbasename ) return self.FLIGHTCONTROLS[airbasename] end @@ -1672,90 +1621,91 @@ 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" ) +--- Account the Hits of the Players. +-- @param #DATABASE self +-- @param Core.Event#EVENTDATA Event +function DATABASE:AccountHits( Event ) + self:F( { Event } ) - -- What is he hitting? - if Event.TgtCategory then + 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.WeaponCoalition 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.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.WeaponCoalition 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 + 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 } ) +--- 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 + 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 + if Event.IniDCSUnit then - TargetUnit = Event.IniUnit - TargetUnitName = Event.IniDCSUnitName - TargetGroup = Event.IniDCSGroup - TargetGroupName = Event.IniDCSGroupName - TargetPlayerName = Event.IniPlayerName + 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 + TargetCoalition = Event.IniCoalition + -- TargetCategory = TargetUnit:getCategory() + -- TargetCategory = TargetUnit:getDesc().category -- Workaround + TargetCategory = Event.IniCategory + TargetType = Event.IniTypeName - TargetUnitType = TargetType + TargetUnitType = TargetType - self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } ) - end - - 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 + self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } ) end + + 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/Event.lua b/Moose Development/Moose/Core/Event.lua index 10b934c25..05d7ae41f 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -14,7 +14,7 @@ -- ![Objects](..\Presentations\EVENT\Dia2.JPG) -- -- Within a running mission, various DCS events occur. Units are dynamically created, crash, die, shoot stuff, get hit etc. --- This module provides a mechanism to dispatch those events occuring within your running mission, to the different objects orchestrating your mission. +-- This module provides a mechanism to dispatch those events occurring within your running mission, to the different objects orchestrating your mission. -- -- ![Objects](..\Presentations\EVENT\Dia3.JPG) -- @@ -32,11 +32,11 @@ -- -- ![Objects](..\Presentations\EVENT\Dia5.JPG) -- --- There are 5 levels of kind of objects that the _EVENTDISPATCHER services: +-- There are 5 types/levels of objects that the _EVENTDISPATCHER services: -- -- * _DATABASE object: The core of the MOOSE objects. Any object that is created, deleted or updated, is done in this database. --- * SET_ derived classes: Subsets of the _DATABASE object. These subsets are updated by the _EVENTDISPATCHER as the second priority. --- * UNIT objects: UNIT objects can subscribe to DCS events. Each DCS event will be directly published to teh subscribed UNIT object. +-- * SET_ derived classes: These are subsets of the _DATABASE object. These subsets are updated by the _EVENTDISPATCHER as the second priority. +-- * UNIT objects: UNIT objects can subscribe to DCS events. Each DCS event will be directly published to the subscribed UNIT object. -- * GROUP objects: GROUP objects can subscribe to DCS events. Each DCS event will be directly published to the subscribed GROUP object. -- * Any other object: Various other objects can subscribe to DCS events. Each DCS event triggered will be published to each subscribed object. -- diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index 3638153ef..89f42e88f 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -1,9 +1,9 @@ --- **Core** - FSM (Finite State Machine) are objects that model and control long lasting business processes and workflow. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Provide a base class to model your own state machines. -- * Trigger events synchronously. -- * Trigger events asynchronously. @@ -13,66 +13,65 @@ -- - to handle controllables (groups and units). -- - to handle tasks. -- - to handle processes. --- +-- -- === --- +-- -- A Finite State Machine (FSM) models a process flow that transitions between various **States** through triggered **Events**. --- --- A FSM can only be in one of a finite number of states. --- The machine is in only one state at a time; the state it is in at any given time is called the **current state**. --- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**. --- An **FSM implementation** is defined by **a list of its states**, **its initial state**, and **the triggering events** for **each possible transition**. --- An FSM implementation is composed out of **two parts**, a set of **state transition rules**, and an implementation set of **state transition handlers**, implementing those transitions. --- --- The FSM class supports a **hierarchical implementation of a Finite State Machine**, +-- +-- A FSM can only be in one of a finite number of states. +-- The machine is in only one state at a time; the state it is in at any given time is called the **current state**. +-- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**. +-- A **FSM implementation** is defined by **a list of its states**, **its initial state**, and **the triggering events** for **each possible transition**. +-- A FSM implementation is composed out of **two parts**, a set of **state transition rules**, and an implementation set of **state transition handlers**, implementing those transitions. +-- +-- The FSM class supports a **hierarchical implementation of a Finite State Machine**, -- that is, it allows to **embed existing FSM implementations in a master FSM**. -- FSM hierarchies allow for efficient FSM re-use, **not having to re-invent the wheel every time again** when designing complex processes. --- +-- -- ![Workflow Example](..\Presentations\FSM\Dia2.JPG) --- +-- -- The above diagram shows a graphical representation of a FSM implementation for a **Task**, which guides a Human towards a Zone, -- orders him to destroy x targets and account the results. --- Other examples of ready made FSM could be: --- --- * route a plane to a zone flown by a human --- * detect targets by an AI and report to humans --- * account for destroyed targets by human players --- * handle AI infantry to deploy from or embark to a helicopter or airplane or vehicle --- * let an AI patrol a zone --- --- The **MOOSE framework** uses extensively the FSM class and derived FSM\_ classes, +-- Other examples of ready made FSM could be: +-- +-- * Route a plane to a zone flown by a human. +-- * Detect targets by an AI and report to humans. +-- * Account for destroyed targets by human players. +-- * Handle AI infantry to deploy from or embark to a helicopter or airplane or vehicle. +-- * Let an AI patrol a zone. +-- +-- The **MOOSE framework** extensively uses the FSM class and derived FSM\_ classes, -- because **the goal of MOOSE is to simplify mission design complexity for mission building**. -- By efficiently utilizing the FSM class and derived classes, MOOSE allows mission designers to quickly build processes. --- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, +-- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, -- and tailored** by mission designers through **the implementation of Transition Handlers**. -- Each of these FSM implementation classes start either with: --- --- * an acronym **AI\_**, which indicates an FSM implementation directing **AI controlled** @{GROUP} and/or @{UNIT}. These AI\_ classes derive the @{#FSM_CONTROLLABLE} class. --- * an acronym **TASK\_**, which indicates an FSM implementation executing a @{TASK} executed by Groups of players. These TASK\_ classes derive the @{#FSM_TASK} class. +-- +-- * an acronym **AI\_**, which indicates a FSM implementation directing **AI controlled** @{GROUP} and/or @{UNIT}. These AI\_ classes derive the @{#FSM_CONTROLLABLE} class. +-- * an acronym **TASK\_**, which indicates a FSM implementation executing a @{TASK} executed by Groups of players. These TASK\_ classes derive the @{#FSM_TASK} class. -- * an acronym **ACT\_**, which indicates an Sub-FSM implementation, directing **Humans actions** that need to be done in a @{TASK}, seated in a @{CLIENT} (slot) or a @{UNIT} (CA join). These ACT\_ classes derive the @{#FSM_PROCESS} class. --- +-- -- Detailed explanations and API specifics are further below clarified and FSM derived class specifics are described in those class documentation sections. --- --- ##__Dislaimer:__ +-- +-- ##__Disclaimer:__ -- The FSM class development is based on a finite state machine implementation made by Conroy Kyle. -- The state machine can be found on [github](https://github.com/kyleconroy/lua-state-machine) -- I've reworked this development (taken the concept), and created a **hierarchical state machine** out of it, embedded within the DCS simulator. -- Additionally, I've added extendability and created an API that allows seamless FSM implementation. --- --- The following derived classes are available in the MOOSE framework, that implement a specialised form of a FSM: --- +-- +-- The following derived classes are available in the MOOSE framework, that implement a specialized form of a FSM: +-- -- * @{#FSM_TASK}: Models Finite State Machines for @{Task}s. -- * @{#FSM_PROCESS}: Models Finite State Machines for @{Task} actions, which control @{Client}s. -- * @{#FSM_CONTROLLABLE}: Models Finite State Machines for @{Wrapper.Controllable}s, which are @{Wrapper.Group}s, @{Wrapper.Unit}s, @{Client}s. -- * @{#FSM_SET}: Models Finite State Machines for @{Set}s. Note that these FSMs control multiple objects!!! So State concerns here -- for multiple objects or the position of the state machine in the process. --- +-- -- === --- --- +-- -- ### Author: **FlightControl** -- ### Contributions: **funkyfranky** --- +-- -- === -- -- @module Core.Fsm @@ -88,195 +87,194 @@ do -- FSM -- @field #table Scores Scores. -- @field #string current Current state name. -- @extends Core.Base#BASE - - + --- A Finite State Machine (FSM) models a process flow that transitions between various **States** through triggered **Events**. - -- + -- -- A FSM can only be in one of a finite number of states. - -- The machine is in only one state at a time; the state it is in at any given time is called the **current state**. - -- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**. + -- The machine is in only one state at a time; the state it is in at any given time is called the **current state**. + -- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**. -- An **FSM implementation** is defined by **a list of its states**, **its initial state**, and **the triggering events** for **each possible transition**. -- An FSM implementation is composed out of **two parts**, a set of **state transition rules**, and an implementation set of **state transition handlers**, implementing those transitions. - -- - -- The FSM class supports a **hierarchical implementation of a Finite State Machine**, + -- + -- The FSM class supports a **hierarchical implementation of a Finite State Machine**, -- that is, it allows to **embed existing FSM implementations in a master FSM**. -- FSM hierarchies allow for efficient FSM re-use, **not having to re-invent the wheel every time again** when designing complex processes. - -- + -- -- ![Workflow Example](..\Presentations\FSM\Dia2.JPG) - -- + -- -- The above diagram shows a graphical representation of a FSM implementation for a **Task**, which guides a Human towards a Zone, -- orders him to destroy x targets and account the results. - -- Other examples of ready made FSM could be: - -- + -- Other examples of ready made FSM could be: + -- -- * route a plane to a zone flown by a human -- * detect targets by an AI and report to humans -- * account for destroyed targets by human players - -- * handle AI infantry to deploy from or embark to a helicopter or airplane or vehicle + -- * handle AI infantry to deploy from or embark to a helicopter or airplane or vehicle -- * let an AI patrol a zone - -- - -- The **MOOSE framework** uses extensively the FSM class and derived FSM\_ classes, + -- + -- The **MOOSE framework** uses extensively the FSM class and derived FSM\_ classes, -- because **the goal of MOOSE is to simplify mission design complexity for mission building**. -- By efficiently utilizing the FSM class and derived classes, MOOSE allows mission designers to quickly build processes. - -- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, + -- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, -- and tailored** by mission designers through **the implementation of Transition Handlers**. -- Each of these FSM implementation classes start either with: - -- + -- -- * an acronym **AI\_**, which indicates an FSM implementation directing **AI controlled** @{GROUP} and/or @{UNIT}. These AI\_ classes derive the @{#FSM_CONTROLLABLE} class. -- * an acronym **TASK\_**, which indicates an FSM implementation executing a @{TASK} executed by Groups of players. These TASK\_ classes derive the @{#FSM_TASK} class. -- * an acronym **ACT\_**, which indicates an Sub-FSM implementation, directing **Humans actions** that need to be done in a @{TASK}, seated in a @{CLIENT} (slot) or a @{UNIT} (CA join). These ACT\_ classes derive the @{#FSM_PROCESS} class. - -- + -- -- ![Transition Rules and Transition Handlers and Event Triggers](..\Presentations\FSM\Dia3.JPG) - -- + -- -- The FSM class is the base class of all FSM\_ derived classes. It implements the main functionality to define and execute Finite State Machines. -- The derived FSM\_ classes extend the Finite State Machine functionality to run a workflow process for a specific purpose or component. - -- + -- -- Finite State Machines have **Transition Rules**, **Transition Handlers** and **Event Triggers**. - -- - -- The **Transition Rules** define the "Process Flow Boundaries", that is, + -- + -- The **Transition Rules** define the "Process Flow Boundaries", that is, -- the path that can be followed hopping from state to state upon triggered events. - -- If an event is triggered, and there is no valid path found for that event, + -- If an event is triggered, and there is no valid path found for that event, -- an error will be raised and the FSM will stop functioning. - -- + -- -- The **Transition Handlers** are special methods that can be defined by the mission designer, following a defined syntax. -- If the FSM object finds a method of such a handler, then the method will be called by the FSM, passing specific parameters. -- The method can then define its own custom logic to implement the FSM workflow, and to conduct other actions. - -- + -- -- The **Event Triggers** are methods that are defined by the FSM, which the mission designer can use to implement the workflow. -- Most of the time, these Event Triggers are used within the Transition Handler methods, so that a workflow is created running through the state machine. - -- + -- -- As explained above, a FSM supports **Linear State Transitions** and **Hierarchical State Transitions**, and both can be mixed to make a comprehensive FSM implementation. - -- The below documentation has a seperate chapter explaining both transition modes, taking into account the **Transition Rules**, **Transition Handlers** and **Event Triggers**. - -- + -- The below documentation has a separate chapter explaining both transition modes, taking into account the **Transition Rules**, **Transition Handlers** and **Event Triggers**. + -- -- ## FSM Linear Transitions - -- + -- -- Linear Transitions are Transition Rules allowing an FSM to transition from one or multiple possible **From** state(s) towards a **To** state upon a Triggered **Event**. - -- The Lineair transition rule evaluation will always be done from the **current state** of the FSM. + -- The Linear transition rule evaluation will always be done from the **current state** of the FSM. -- If no valid Transition Rule can be found in the FSM, the FSM will log an error and stop. - -- + -- -- ### FSM Transition Rules - -- - -- The FSM has transition rules that it follows and validates, as it walks the process. + -- + -- The FSM has transition rules that it follows and validates, as it walks the process. -- These rules define when an FSM can transition from a specific state towards an other specific state upon a triggered event. - -- - -- The method @{#FSM.AddTransition}() specifies a new possible Transition Rule for the FSM. - -- + -- + -- The method @{#FSM.AddTransition}() specifies a new possible Transition Rule for the FSM. + -- -- The initial state can be defined using the method @{#FSM.SetStartState}(). The default start state of an FSM is "None". - -- + -- -- Find below an example of a Linear Transition Rule definition for an FSM. - -- + -- -- local Fsm3Switch = FSM:New() -- #FsmDemo -- FsmSwitch:SetStartState( "Off" ) -- FsmSwitch:AddTransition( "Off", "SwitchOn", "On" ) -- FsmSwitch:AddTransition( "Off", "SwitchMiddle", "Middle" ) -- FsmSwitch:AddTransition( "On", "SwitchOff", "Off" ) -- FsmSwitch:AddTransition( "Middle", "SwitchOff", "Off" ) - -- + -- -- The above code snippet models a 3-way switch Linear Transition: - -- + -- -- * It can be switched **On** by triggering event **SwitchOn**. -- * It can be switched to the **Middle** position, by triggering event **SwitchMiddle**. -- * It can be switched **Off** by triggering event **SwitchOff**. -- * Note that once the Switch is **On** or **Middle**, it can only be switched **Off**. - -- + -- -- #### Some additional comments: - -- + -- -- Note that Linear Transition Rules **can be declared in a few variations**: - -- + -- -- * The From states can be **a table of strings**, indicating that the transition rule will be valid **if the current state** of the FSM will be **one of the given From states**. -- * The From state can be a **"*"**, indicating that **the transition rule will always be valid**, regardless of the current state of the FSM. - -- - -- The below code snippet shows how the two last lines can be rewritten and consensed. - -- + -- + -- The below code snippet shows how the two last lines can be rewritten and condensed. + -- -- FsmSwitch:AddTransition( { "On", "Middle" }, "SwitchOff", "Off" ) - -- + -- -- ### Transition Handling - -- + -- -- ![Transition Handlers](..\Presentations\FSM\Dia4.JPG) - -- - -- An FSM transitions in **4 moments** when an Event is being triggered and processed. - -- The mission designer can define for each moment specific logic within methods implementations following a defined API syntax. + -- + -- An FSM transitions in **4 moments** when an Event is being triggered and processed. + -- The mission designer can define for each moment specific logic within methods implementations following a defined API syntax. -- These methods define the flow of the FSM process; because in those methods the FSM Internal Events will be triggered. -- -- * To handle **State** transition moments, create methods starting with OnLeave or OnEnter concatenated with the State name. -- * To handle **Event** transition moments, create methods starting with OnBefore or OnAfter concatenated with the Event name. - -- + -- -- **The OnLeave and OnBefore transition methods may return false, which will cancel the transition!** - -- + -- -- Transition Handler methods need to follow the above specified naming convention, but are also passed parameters from the FSM. -- These parameters are on the correct order: From, Event, To: - -- + -- -- * From = A string containing the From state. -- * Event = A string containing the Event name that was triggered. -- * To = A string containing the To state. - -- + -- -- On top, each of these methods can have a variable amount of parameters passed. See the example in section [1.1.3](#1.1.3\)-event-triggers). - -- + -- -- ### Event Triggers - -- + -- -- ![Event Triggers](..\Presentations\FSM\Dia5.JPG) - -- - -- The FSM creates for each Event two **Event Trigger methods**. + -- + -- The FSM creates for each Event two **Event Trigger methods**. -- There are two modes how Events can be triggered, which is **synchronous** and **asynchronous**: - -- + -- -- * The method **FSM:Event()** triggers an Event that will be processed **synchronously** or **immediately**. -- * The method **FSM:__Event( __seconds__ )** triggers an Event that will be processed **asynchronously** over time, waiting __x seconds__. - -- - -- The destinction between these 2 Event Trigger methods are important to understand. An asynchronous call will "log" the Event Trigger to be executed at a later time. + -- + -- The distinction between these 2 Event Trigger methods are important to understand. An asynchronous call will "log" the Event Trigger to be executed at a later time. -- Processing will just continue. Synchronous Event Trigger methods are useful to change states of the FSM immediately, but may have a larger processing impact. - -- + -- -- The following example provides a little demonstration on the difference between synchronous and asynchronous Event Triggering. - -- + -- -- function FSM:OnAfterEvent( From, Event, To, Amount ) - -- self:T( { Amount = Amount } ) + -- self:T( { Amount = Amount } ) -- end - -- + -- -- local Amount = 1 - -- FSM:__Event( 5, Amount ) - -- + -- FSM:__Event( 5, Amount ) + -- -- Amount = Amount + 1 -- FSM:Event( Text, Amount ) - -- + -- -- In this example, the **:OnAfterEvent**() Transition Handler implementation will get called when **Event** is being triggered. - -- Before we go into more detail, let's look at the last 4 lines of the example. + -- Before we go into more detail, let's look at the last 4 lines of the example. -- The last line triggers synchronously the **Event**, and passes Amount as a parameter. - -- The 3rd last line of the example triggers asynchronously **Event**. + -- The 3rd last line of the example triggers asynchronously **Event**. -- Event will be processed after 5 seconds, and Amount is given as a parameter. - -- + -- -- The output of this little code fragment will be: - -- + -- -- * Amount = 2 -- * Amount = 2 - -- + -- -- Because ... When Event was asynchronously processed after 5 seconds, Amount was set to 2. So be careful when processing and passing values and objects in asynchronous processing! - -- + -- -- ### Linear Transition Example - -- + -- -- This example is fully implemented in the MOOSE test mission on GITHUB: [FSM-100 - Transition Explanation](https://github.com/FlightControl-Master/MOOSE/blob/master/Moose%20Test%20Missions/FSM%20-%20Finite%20State%20Machine/FSM-100%20-%20Transition%20Explanation/FSM-100%20-%20Transition%20Explanation.lua) - -- + -- -- It models a unit standing still near Batumi, and flaring every 5 seconds while switching between a Green flare and a Red flare. -- The purpose of this example is not to show how exciting flaring is, but it demonstrates how a Linear Transition FSM can be build. -- Have a look at the source code. The source code is also further explained below in this section. - -- + -- -- The example creates a new FsmDemo object from class FSM. -- It will set the start state of FsmDemo to state **Green**. -- Two Linear Transition Rules are created, where upon the event **Switch**, -- the FsmDemo will transition from state **Green** to **Red** and from **Red** back to **Green**. - -- + -- -- ![Transition Example](..\Presentations\FSM\Dia6.JPG) - -- + -- -- local FsmDemo = FSM:New() -- #FsmDemo -- FsmDemo:SetStartState( "Green" ) -- FsmDemo:AddTransition( "Green", "Switch", "Red" ) -- FsmDemo:AddTransition( "Red", "Switch", "Green" ) - -- + -- -- In the above example, the FsmDemo could flare every 5 seconds a Green or a Red flare into the air. -- The next code implements this through the event handling method **OnAfterSwitch**. - -- + -- -- ![Transition Flow](..\Presentations\FSM\Dia7.JPG) - -- + -- -- function FsmDemo:OnAfterSwitch( From, Event, To, FsmUnit ) -- self:T( { From, Event, To, FsmUnit } ) - -- + -- -- if From == "Green" then -- FsmUnit:Flare(FLARECOLOR.Green) -- else @@ -286,22 +284,22 @@ do -- FSM -- end -- self:__Switch( 5, FsmUnit ) -- Trigger the next Switch event to happen in 5 seconds. -- end - -- + -- -- FsmDemo:__Switch( 5, FsmUnit ) -- Trigger the first Switch event to happen in 5 seconds. - -- + -- -- The OnAfterSwitch implements a loop. The last line of the code fragment triggers the Switch Event within 5 seconds. -- Upon the event execution (after 5 seconds), the OnAfterSwitch method is called of FsmDemo (cfr. the double point notation!!! ":"). - -- The OnAfterSwitch method receives from the FSM the 3 transition parameter details ( From, Event, To ), + -- The OnAfterSwitch method receives from the FSM the 3 transition parameter details ( From, Event, To ), -- and one additional parameter that was given when the event was triggered, which is in this case the Unit that is used within OnSwitchAfter. - -- + -- -- function FsmDemo:OnAfterSwitch( From, Event, To, FsmUnit ) - -- + -- -- For debugging reasons the received parameters are traced within the DCS.log. - -- + -- -- self:T( { From, Event, To, FsmUnit } ) - -- + -- -- The method will check if the From state received is either "Green" or "Red" and will flare the respective color from the FsmUnit. - -- + -- -- if From == "Green" then -- FsmUnit:Flare(FLARECOLOR.Green) -- else @@ -309,77 +307,75 @@ do -- FSM -- FsmUnit:Flare(FLARECOLOR.Red) -- end -- end - -- + -- -- It is important that the Switch event is again triggered, otherwise, the FsmDemo would stop working after having the first Event being handled. - -- + -- -- FsmDemo:__Switch( 5, FsmUnit ) -- Trigger the next Switch event to happen in 5 seconds. - -- + -- -- The below code fragment extends the FsmDemo, demonstrating multiple **From states declared as a table**, adding a **Linear Transition Rule**. -- The new event **Stop** will cancel the Switching process. -- The transition for event Stop can be executed if the current state of the FSM is either "Red" or "Green". - -- + -- -- local FsmDemo = FSM:New() -- #FsmDemo -- FsmDemo:SetStartState( "Green" ) -- FsmDemo:AddTransition( "Green", "Switch", "Red" ) -- FsmDemo:AddTransition( "Red", "Switch", "Green" ) -- FsmDemo:AddTransition( { "Red", "Green" }, "Stop", "Stopped" ) - -- + -- -- The transition for event Stop can also be simplified, as any current state of the FSM is valid. - -- + -- -- FsmDemo:AddTransition( "*", "Stop", "Stopped" ) - -- + -- -- So... When FsmDemo:Stop() is being triggered, the state of FsmDemo will transition from Red or Green to Stopped. -- And there is no transition handling method defined for that transition, thus, no new event is being triggered causing the FsmDemo process flow to halt. - -- + -- -- ## FSM Hierarchical Transitions - -- + -- -- Hierarchical Transitions allow to re-use readily available and implemented FSMs. - -- This becomes in very useful for mission building, where mission designers build complex processes and workflows, + -- This becomes in very useful for mission building, where mission designers build complex processes and workflows, -- combining smaller FSMs to one single FSM. - -- - -- The FSM can embed **Sub-FSMs** that will execute and return **multiple possible Return (End) States**. + -- + -- The FSM can embed **Sub-FSMs** that will execute and return **multiple possible Return (End) States**. -- Depending upon **which state is returned**, the main FSM can continue the flow **triggering specific events**. - -- - -- The method @{#FSM.AddProcess}() adds a new Sub-FSM to the FSM. + -- + -- The method @{#FSM.AddProcess}() adds a new Sub-FSM to the FSM. -- -- === - -- + -- -- @field #FSM - -- FSM = { ClassName = "FSM", } - + --- Creates a new FSM object. -- @param #FSM self -- @return #FSM function FSM:New() - + -- Inherits from BASE 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 - - + --- Sets the start state of the FSM. -- @param #FSM self -- @param #string State A string defining the start state. @@ -387,15 +383,14 @@ do -- FSM self._StartState = State self.current = State end - - + --- Returns the start state of the FSM. -- @param #FSM self -- @return #string A string containing the start state. function FSM:GetStartState() return self._StartState or {} end - + --- Add a new transition rule to the FSM. -- A transition rule defines when and if the FSM can transition from a state towards another state upon a triggered event. -- @param #FSM self @@ -403,12 +398,12 @@ do -- FSM -- @param #string Event The Event name. -- @param #string To The To state. function FSM:AddTransition( From, Event, To ) - + local Transition = {} Transition.From = From Transition.Event = Event Transition.To = To - + -- Debug message. --self:T3( Transition ) @@ -416,14 +411,13 @@ do -- FSM self:_eventmap( self.Events, Transition ) end - --- Returns a table of the transition rules defined within the FSM. -- @param #FSM self -- @return #table Transitions. - function FSM:GetTransitions() + function FSM:GetTransitions() return self._Transitions or {} end - + --- Set the default @{Process} template with key ProcessName providing the ProcessClass and the process object when it is assigned to a @{Wrapper.Controllable} by the task. -- @param #FSM self -- @param #table From Can contain a string indicating the From state or a table of strings containing multiple From states. @@ -440,66 +434,64 @@ do -- FSM 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 - - + --- Returns a table of the SubFSM rules defined within the FSM. -- @param #FSM self -- @return #table Sub processes. function FSM:GetProcesses() - + self:F( { Processes = self._Processes } ) - + 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:SetProcess( From, Event, Fsm ) - + for ProcessID, Process in pairs( self:GetProcesses() ) do if Process.From == From and Process.Event == Event then Process.fsm = Fsm return true end end - + error( "Sub-Process from state " .. From .. " with event " .. Event .. " not found!" ) end - + --- Adds an End state. -- @param #FSM self -- @param #string State The FSM state. - function FSM:AddEndState( State ) + function FSM:AddEndState( State ) self._EndStates[State] = State self.endstates[State] = State end - + --- Returns the End states. -- @param #FSM self -- @return #table End states. - function FSM:GetEndStates() + function FSM:GetEndStates() return self._EndStates or {} end - - + --- Adds a score for the FSM to be achieved. -- @param #FSM self -- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process). @@ -508,14 +500,14 @@ do -- FSM -- @return #FSM self 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 - + --- Adds a score for the FSM_PROCESS to be achieved. -- @param #FSM self -- @param #string From is the From State of the main process. @@ -526,9 +518,9 @@ do -- FSM -- @return #FSM self 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 @@ -537,33 +529,33 @@ do -- FSM return Process end - + --- Returns a table with the scores defined. -- @param #FSM self -- @return #table Scores. - function FSM:GetScores() + function FSM:GetScores() return self._Scores or {} end - + --- Returns a table with the Subs defined. -- @param #FSM self -- @return #table Sub processes. - function FSM:GetSubs() + function FSM:GetSubs() return self.options.subs end - + --- Load call backs. -- @param #FSM self - -- @param #table CallBackTable Table of call backs. + -- @param #table CallBackTable Table of call backs. function FSM:LoadCallBacks( CallBackTable ) - + for name, callback in pairs( CallBackTable or {} ) do self[name] = callback end - + end - - --- Event map. + + --- Event map. -- @param #FSM self -- @param #table Events Events. -- @param #table EventStructure Event structure. @@ -583,28 +575,28 @@ do -- FSM end - --- Sub maps. + --- Sub maps. -- @param #FSM self -- @param #table subs Subs. -- @param #table sub Sub. - -- @param #string name Name. + -- @param #string name Name. function FSM:_submap( subs, sub, name ) - + subs[sub.From] = subs[sub.From] or {} subs[sub.From][sub.Event] = subs[sub.From][sub.Event] or {} - + -- Make the reference table weak. -- setmetatable( subs[sub.From][sub.Event], { __mode = "k" } ) - + 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 {} -- these events need to be given to find the correct continue event ... if none given, the processing will stop. subs[sub.From][sub.Event][sub].name = name subs[sub.From][sub.Event][sub].fsmparent = self - + end - + --- Call handler. -- @param #FSM self -- @param #string step Step "onafter", "onbefore", "onenter", "onleave". @@ -613,12 +605,12 @@ do -- FSM -- @param #string EventName Event name. -- @return Value. function FSM:_call_handler( step, trigger, params, EventName ) - --env.info(string.format("FF T=%.3f _call_handler step=%s, trigger=%s, event=%s", timer.getTime(), step, trigger, EventName)) + -- env.info(string.format("FF T=%.3f _call_handler step=%s, trigger=%s, event=%s", timer.getTime(), step, trigger, EventName)) local handler = step .. trigger - + if self[handler] then - + --[[ if step == "onafter" or step == "OnAfter" then self:T( ":::>" .. step .. params[2] .. " : " .. params[1] .. " >> " .. params[2] .. ">" .. step .. params[2] .. "()" .. " >> " .. params[3] ) @@ -632,7 +624,7 @@ do -- FSM self:T( ":::>" .. step .. " : " .. params[1] .. " >> " .. params[2] .. " >> " .. params[3] ) end ]] - + self._EventSchedules[EventName] = nil -- Error handler. @@ -640,49 +632,50 @@ do -- FSM env.info( "Error in SCHEDULER function:" .. errmsg ) if BASE.Debug ~= nil then env.info( BASE.Debug.traceback() ) - end + end return errmsg end - - --return self[handler](self, unpack( params )) - + + -- return self[handler](self, unpack( params )) + -- Protected call. - local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) - return Value + local Result, Value = xpcall( function() + return self[handler]( self, unpack( params ) ) + end, ErrorHandler ) + return Value end - + end - + --- Handler. -- @param #FSM self -- @param #string EventName Event name. -- @param ... Arguments. function FSM._handler( self, EventName, ... ) - + local Can, To = self:can( EventName ) - + if To == "*" then To = self.current end - + if Can then - + -- From state. local From = self.current - + -- Parameters. - local Params = { From, EventName, To, ... } + local Params = { From, EventName, To, ... } + if self["onleave" .. From] or + self["OnLeave" .. From] or + self["onbefore" .. EventName] or + self["OnBefore" .. EventName] or + self["onafter" .. EventName] or + self["OnAfter" .. EventName] or + self["onenter" .. To] or + self["OnEnter" .. To] then - if self["onleave".. From] or - self["OnLeave".. From] or - self["onbefore".. EventName] or - self["OnBefore".. EventName] or - self["onafter".. EventName] or - self["OnAfter".. EventName] or - self["onenter".. To] or - self["OnEnter".. To] then - if self:_call_handler( "onbefore", EventName, Params, EventName ) == false then self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** onbefore" .. EventName ) return false @@ -691,7 +684,7 @@ do -- FSM self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** OnBefore" .. EventName ) return false else - if self:_call_handler( "onleave", From, Params, EventName ) == false then + if self:_call_handler( "onleave", From, Params, EventName ) == false then self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** onleave" .. From ) return false else @@ -699,115 +692,115 @@ do -- FSM self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** OnLeave" .. From ) return false end - end + end end end - + else - + local ClassName = self:GetClassName() - + if ClassName == "FSM" then self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To ) end - + if ClassName == "FSM_TASK" then self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** Task: " .. self.TaskName ) end - + if ClassName == "FSM_CONTROLLABLE" then - self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** TaskUnit: " .. self.Controllable.ControllableName .. " *** " ) - end - + self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** TaskUnit: " .. self.Controllable.ControllableName .. " *** " ) + end + if ClassName == "FSM_PROCESS" then - self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** Task: " .. self.Task:GetName() .. ", TaskUnit: " .. self.Controllable.ControllableName .. " *** " ) - end + self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** Task: " .. self.Task:GetName() .. ", TaskUnit: " .. self.Controllable.ControllableName .. " *** " ) + end end - + -- New current state. self.current = To - + local execute = true - + local subtable = self:_gosub( From, EventName ) - + for _, sub in pairs( subtable ) do - - --if sub.nextevent then + + -- if sub.nextevent then -- self:F2( "nextevent = " .. sub.nextevent ) -- self[sub.nextevent]( self ) - --end - + -- end + self:T( "*** FSM *** Sub *** " .. 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:T( "*** FSM *** End *** " .. 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("onstate", "change", Params, EventName ) - + + 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( "onstate", "change", Params, EventName ) + fsmparent[Event]( fsmparent ) - + execute = false end - + if execute then - - self:_call_handler("onafter", EventName, Params, EventName ) - self:_call_handler("OnAfter", EventName, Params, EventName ) - - self:_call_handler("onenter", To, Params, EventName ) - self:_call_handler("OnEnter", To, Params, EventName ) - - self:_call_handler("onstate", "change", Params, EventName ) - + + self:_call_handler( "onafter", EventName, Params, EventName ) + self:_call_handler( "OnAfter", EventName, Params, EventName ) + + self:_call_handler( "onenter", To, Params, EventName ) + self:_call_handler( "OnEnter", To, Params, EventName ) + + self:_call_handler( "onstate", "change", Params, EventName ) + end else self:T( "*** FSM *** NO Transition *** " .. self.current .. " --> " .. EventName .. " --> ? " ) end - + return nil end --- Delayed transition. -- @param #FSM self - -- @param #string EventName Event name. + -- @param #string EventName Event name. -- @return #function Function. function FSM:_delayed_transition( EventName ) - + return function( self, DelaySeconds, ... ) - + -- Debug. self:T3( "Delayed Event: " .. EventName ) local CallID = 0 if DelaySeconds ~= nil then - + if DelaySeconds < 0 then -- Only call the event ONCE! - + DelaySeconds = math.abs( DelaySeconds ) - + if not self._EventSchedules[EventName] then - + -- Call _handler. CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1, nil, nil, nil, 4, true ) - + -- Set call ID. self._EventSchedules[EventName] = CallID - + -- Debug output. self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID))) else @@ -815,7 +808,7 @@ do -- FSM -- reschedule end else - + CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1, nil, nil, nil, 4, true ) self:T2(string.format("Event %s delayed by %.3f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID))) @@ -823,23 +816,25 @@ do -- FSM else error( "FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this." ) end - + -- Debug. --self:T3( { CallID = CallID } ) end - + end --- Create transition. -- @param #FSM self - -- @param #string EventName Event name. - -- @return #function Function. + -- @param #string EventName Event name. + -- @return #function Function. function FSM:_create_transition( EventName ) - return function( self, ... ) return self._handler( self, EventName , ... ) end + return function( self, ... ) + return self._handler( self, EventName, ... ) + end end - + --- Go sub. - -- @param #FSM self + -- @param #FSM self -- @param #string ParentFrom Parent from state. -- @param #string ParentEvent Parent event name. -- @return #table Subs. @@ -860,21 +855,21 @@ do -- FSM -- @return #string Event name. 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] } ) + -- self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } ) FSMParent.current = Current local ParentFrom = FSMParent.current - --self:T( { ParentFrom, self.ReturnEvents } ) + -- self:T( { ParentFrom, self.ReturnEvents } ) local Event = self.ReturnEvents[Current] - --self:T( { Event } ) + -- self:T( { Event } ) if Event then return FSMParent, Event else - --self:T( { "Could not find parent event name for state ", ParentFrom } ) + -- self:T( { "Could not find parent event name for state ", ParentFrom } ) end end - + return nil end @@ -883,13 +878,13 @@ do -- FSM -- @param #table Map Map. -- @param #table Event Event table. function FSM:_add_to_map( Map, Event ) - self:F3( { Map, Event } ) - - if type(Event.From) == 'string' then - Map[Event.From] = Event.To + 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 + for _, From in ipairs( Event.From ) do + Map[From] = Event.To end end @@ -905,11 +900,11 @@ do -- FSM --- Get current state. -- @param #FSM self - -- @return #string Current FSM state. + -- @return #string Current FSM state. function FSM:GetCurrentState() return self.current end - + --- Check if FSM is in state. -- @param #FSM self -- @param #string State State name. @@ -931,14 +926,14 @@ do -- FSM -- @param #string e Event name. -- @return #boolean If true, FSM can do the event. -- @return #string To state. - function FSM:can(e) - + function FSM:can( e ) + local Event = self.Events[e] - - --self:F3( { self.current, Event } ) - + + -- self:F3( { self.current, Event } ) + local To = Event and Event.map[self.current] or Event.map['*'] - + return To ~= nil, To end @@ -946,8 +941,8 @@ do -- FSM -- @param #FSM self -- @param #string e Event name. -- @return #boolean If true, FSM cannot do the event. - function FSM:cannot(e) - return not self:can(e) + function FSM:cannot( e ) + return not self:can( e ) end end @@ -957,32 +952,32 @@ do -- FSM_CONTROLLABLE --- @type FSM_CONTROLLABLE -- @field Wrapper.Controllable#CONTROLLABLE Controllable -- @extends Core.Fsm#FSM - + --- Models Finite State Machines for @{Wrapper.Controllable}s, which are @{Wrapper.Group}s, @{Wrapper.Unit}s, @{Client}s. - -- + -- -- === - -- + -- -- @field #FSM_CONTROLLABLE FSM_CONTROLLABLE = { ClassName = "FSM_CONTROLLABLE", } - + --- Creates a new FSM_CONTROLLABLE object. -- @param #FSM_CONTROLLABLE self -- @param #table FSMT Finite State Machine Table -- @param Wrapper.Controllable#CONTROLLABLE Controllable (optional) The CONTROLLABLE object that the FSM_CONTROLLABLE governs. -- @return #FSM_CONTROLLABLE function FSM_CONTROLLABLE:New( Controllable ) - + -- Inherits from BASE local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_CONTROLLABLE - + if Controllable then self:SetControllable( Controllable ) end - + self:AddTransition( "*", "Stop", "Stopped" ) - + --- OnBefore Transition Handler for Event Stop. -- @function [parent=#FSM_CONTROLLABLE] OnBeforeStop -- @param #FSM_CONTROLLABLE self @@ -991,7 +986,7 @@ do -- FSM_CONTROLLABLE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnAfter Transition Handler for Event Stop. -- @function [parent=#FSM_CONTROLLABLE] OnAfterStop -- @param #FSM_CONTROLLABLE self @@ -999,16 +994,16 @@ do -- FSM_CONTROLLABLE -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - + --- Synchronous Event Trigger for Event Stop. -- @function [parent=#FSM_CONTROLLABLE] Stop -- @param #FSM_CONTROLLABLE self - + --- Asynchronous Event Trigger for Event Stop. -- @function [parent=#FSM_CONTROLLABLE] __Stop -- @param #FSM_CONTROLLABLE self -- @param #number Delay The delay in seconds. - + --- OnLeave Transition Handler for State Stopped. -- @function [parent=#FSM_CONTROLLABLE] OnLeaveStopped -- @param #FSM_CONTROLLABLE self @@ -1017,7 +1012,7 @@ do -- FSM_CONTROLLABLE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnEnter Transition Handler for State Stopped. -- @function [parent=#FSM_CONTROLLABLE] OnEnterStopped -- @param #FSM_CONTROLLABLE self @@ -1036,51 +1031,53 @@ do -- FSM_CONTROLLABLE -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - function FSM_CONTROLLABLE:OnAfterStop(Controllable,From,Event,To) - + function FSM_CONTROLLABLE:OnAfterStop( Controllable, From, Event, To ) + -- Clear all pending schedules self.CallScheduler:Clear() end - + --- Sets the CONTROLLABLE object that the FSM_CONTROLLABLE governs. -- @param #FSM_CONTROLLABLE self -- @param Wrapper.Controllable#CONTROLLABLE FSMControllable -- @return #FSM_CONTROLLABLE function FSM_CONTROLLABLE:SetControllable( FSMControllable ) - --self:F( FSMControllable:GetName() ) + -- self:F( FSMControllable:GetName() ) self.Controllable = FSMControllable end - + --- Gets the CONTROLLABLE object that the FSM_CONTROLLABLE governs. -- @param #FSM_CONTROLLABLE self -- @return Wrapper.Controllable#CONTROLLABLE function FSM_CONTROLLABLE:GetControllable() return self.Controllable end - + function FSM_CONTROLLABLE:_call_handler( step, trigger, params, EventName ) - + local handler = step .. trigger - + local ErrorHandler = function( errmsg ) - + env.info( "Error in SCHEDULER function:" .. errmsg ) if BASE.Debug ~= nil then env.info( BASE.Debug.traceback() ) end - + return errmsg end - + if self[handler] then self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** TaskUnit: " .. self.Controllable:GetName() ) self._EventSchedules[EventName] = nil - local Result, Value = xpcall( function() return self[handler]( self, self.Controllable, unpack( params ) ) end, ErrorHandler ) + local Result, Value = xpcall( function() + return self[handler]( self, self.Controllable, unpack( params ) ) + end, ErrorHandler ) return Value - --return self[handler]( self, self.Controllable, unpack( params ) ) + -- return self[handler]( self, self.Controllable, unpack( params ) ) end end - + end do -- FSM_PROCESS @@ -1088,50 +1085,47 @@ do -- FSM_PROCESS --- @type FSM_PROCESS -- @field Tasking.Task#TASK Task -- @extends Core.Fsm#FSM_CONTROLLABLE - - + --- FSM_PROCESS class models Finite State Machines for @{Task} actions, which control @{Client}s. -- -- === -- -- @field #FSM_PROCESS FSM_PROCESS -- - FSM_PROCESS = { - ClassName = "FSM_PROCESS", - } - + FSM_PROCESS = { ClassName = "FSM_PROCESS" } + --- Creates a new FSM_PROCESS object. -- @param #FSM_PROCESS self -- @return #FSM_PROCESS function FSM_PROCESS:New( Controllable, Task ) - + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_PROCESS - --self:F( Controllable ) - + -- self:F( Controllable ) + self:Assign( Controllable, Task ) - + return self end - + function FSM_PROCESS:Init( FsmProcess ) self:T( "No Initialisation" ) - end + end function FSM_PROCESS:_call_handler( step, trigger, params, EventName ) - + local handler = step .. trigger - + local ErrorHandler = function( errmsg ) - + env.info( "Error in FSM_PROCESS call handler:" .. errmsg ) if BASE.Debug ~= nil then env.info( BASE.Debug.traceback() ) end - + return errmsg end - + if self[handler] then if handler ~= "onstatechange" then self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** Task: " .. self.Task:GetName() .. ", TaskUnit: " .. self.Controllable:GetName() ) @@ -1139,53 +1133,54 @@ do -- FSM_PROCESS self._EventSchedules[EventName] = nil local Result, Value if self.Controllable and self.Controllable:IsAlive() == true then - Result, Value = xpcall( function() return self[handler]( self, self.Controllable, self.Task, unpack( params ) ) end, ErrorHandler ) + Result, Value = xpcall( function() + return self[handler]( self, self.Controllable, self.Task, unpack( params ) ) + end, ErrorHandler ) end return Value - --return self[handler]( self, self.Controllable, unpack( params ) ) + -- return self[handler]( self, self.Controllable, unpack( params ) ) end end - + --- Creates a new FSM_PROCESS object based on this FSM_PROCESS. -- @param #FSM_PROCESS self -- @return #FSM_PROCESS function FSM_PROCESS:Copy( Controllable, Task ) --self:T3( { self:GetClassNameAndID() } ) - local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS - + NewFsm:Assign( Controllable, Task ) -- Polymorphic call to initialize the new FSM_PROCESS based on self FSM_PROCESS NewFsm:Init( self ) - + -- Set Start State NewFsm:SetStartState( self:GetStartState() ) - + -- Copy Transitions for TransitionID, Transition in pairs( self:GetTransitions() ) do NewFsm:AddTransition( Transition.From, Transition.Event, Transition.To ) end - + -- Copy Processes for ProcessID, Process in pairs( self:GetProcesses() ) do - --self:E( { Process:GetName() } ) + -- self:E( { Process:GetName() } ) local FsmProcess = NewFsm:AddProcess( Process.From, Process.Event, Process.fsm:Copy( Controllable, Task ), Process.ReturnEvents ) end - + -- Copy End States for EndStateID, EndState in pairs( self:GetEndStates() ) do --self:T3( EndState ) NewFsm:AddEndState( EndState ) end - + -- Copy the score tables for ScoreID, Score in pairs( self:GetScores() ) do --self:T3( Score ) NewFsm:AddScore( ScoreID, Score.ScoreText, Score.Score ) end - + return NewFsm end @@ -1197,7 +1192,7 @@ do -- FSM_PROCESS self:F( "Clearing Schedules" ) self.CallScheduler:Clear() - + -- Copy Processes for ProcessID, Process in pairs( self:GetProcesses() ) do if Process.fsm then @@ -1205,98 +1200,94 @@ do -- FSM_PROCESS Process.fsm = nil end end - + return self end - + --- Sets the task of the process. -- @param #FSM_PROCESS self -- @param Tasking.Task#TASK Task -- @return #FSM_PROCESS function FSM_PROCESS:SetTask( Task ) - + self.Task = Task - + return self end - + --- Gets the task of the process. -- @param #FSM_PROCESS self -- @return Tasking.Task#TASK function FSM_PROCESS:GetTask() - + return self.Task end - + --- Gets the mission of the process. -- @param #FSM_PROCESS self -- @return Tasking.Mission#MISSION function FSM_PROCESS:GetMission() - + return self.Task.Mission end - + --- Gets the mission of the process. -- @param #FSM_PROCESS self -- @return Tasking.CommandCenter#COMMANDCENTER function FSM_PROCESS:GetCommandCenter() - + return self:GetTask():GetMission():GetCommandCenter() end - --- TODO: Need to check and fix that an FSM_PROCESS is only for a UNIT. Not for a GROUP. - + + -- TODO: Need to check and fix that an FSM_PROCESS is only for a UNIT. Not for a GROUP. + --- Send a message of the @{Task} to the Group of the Unit. -- @param #FSM_PROCESS self function FSM_PROCESS:Message( Message ) self:F( { Message = Message } ) - + local CC = self:GetCommandCenter() local TaskGroup = self.Controllable:GetGroup() - + local PlayerName = self.Controllable:GetPlayerName() -- Only for a unit PlayerName = PlayerName and " (" .. PlayerName .. ")" or "" -- If PlayerName is nil, then keep it nil, otherwise add brackets. local Callsign = self.Controllable:GetCallsign() local Prefix = Callsign and " @ " .. Callsign .. PlayerName or "" - + Message = Prefix .. ": " .. Message CC:MessageToGroup( Message, TaskGroup ) end - - - --- Assign the process to a @{Wrapper.Unit} and activate the process. -- @param #FSM_PROCESS self -- @param Task.Tasking#TASK Task -- @param Wrapper.Unit#UNIT ProcessUnit -- @return #FSM_PROCESS self function FSM_PROCESS:Assign( ProcessUnit, Task ) - --self:T( { Task:GetName(), ProcessUnit:GetName() } ) - + -- self:T( { Task:GetName(), ProcessUnit:GetName() } ) + self:SetControllable( ProcessUnit ) self:SetTask( Task ) - - --self.ProcessGroup = ProcessUnit:GetGroup() - + + -- self.ProcessGroup = ProcessUnit:GetGroup() + return self end - --- function FSM_PROCESS:onenterAssigned( ProcessUnit, Task, From, Event, To ) --- --- if From( "Planned" ) then --- self:T( "*** FSM *** Assign *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To ) --- self.Task:Assign() --- end --- end - + + -- function FSM_PROCESS:onenterAssigned( ProcessUnit, Task, From, Event, To ) + -- + -- if From( "Planned" ) then + -- self:T( "*** FSM *** Assign *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To ) + -- self.Task:Assign() + -- end + -- end + function FSM_PROCESS:onenterFailed( ProcessUnit, Task, From, Event, To ) self:T( "*** FSM *** Failed *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To ) - + self.Task:Fail() end - --- StateMachine callback function for a FSM_PROCESS -- @param #FSM_PROCESS self -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit @@ -1304,20 +1295,20 @@ do -- FSM_PROCESS -- @param #string From -- @param #string To function FSM_PROCESS:onstatechange( ProcessUnit, Task, From, Event, To ) - + if From ~= To then self:T( "*** FSM *** Change *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To ) end - --- if self:IsTrace() then --- MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() --- self:F2( { Scores = self._Scores, To = To } ) --- end - + + -- if self:IsTrace() then + -- MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() + -- self:F2( { Scores = self._Scores, To = To } ) + -- end + -- TODO: This needs to be reworked with a callback functions allocated within Task, and set within the mission script from the Task Objects... if self._Scores[To] then - - local Task = self.Task + + local Task = self.Task local Scoring = Task:GetScoring() if Scoring then Scoring:_AddMissionTaskScore( Task.Mission, ProcessUnit, self._Scores[To].ScoreText, self._Scores[To].Score ) @@ -1333,49 +1324,51 @@ do -- FSM_TASK -- @type FSM_TASK -- @field Tasking.Task#TASK Task -- @extends #FSM - + --- Models Finite State Machines for @{Tasking.Task}s. - -- + -- -- === - -- + -- -- @field #FSM_TASK FSM_TASK - -- + -- FSM_TASK = { ClassName = "FSM_TASK", } - + --- Creates a new FSM_TASK object. -- @param #FSM_TASK self -- @param #string TaskName The name of the task. -- @return #FSM_TASK function FSM_TASK:New( TaskName ) - + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_TASK - + self["onstatechange"] = self.OnStateChange self.TaskName = TaskName - + return self end - + function FSM_TASK:_call_handler( step, trigger, params, EventName ) local handler = step .. trigger - + local ErrorHandler = function( errmsg ) - + env.info( "Error in SCHEDULER function:" .. errmsg ) if BASE.Debug ~= nil then env.info( BASE.Debug.traceback() ) end - + return errmsg end if self[handler] then self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** Task: " .. self.TaskName ) self._EventSchedules[EventName] = nil - --return self[handler]( self, unpack( params ) ) - local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) + -- return self[handler]( self, unpack( params ) ) + local Result, Value = xpcall( function() + return self[handler]( self, unpack( params ) ) + end, ErrorHandler ) return Value end end @@ -1389,35 +1382,33 @@ do -- FSM_SET -- @field Core.Set#SET_BASE Set -- @extends Core.Fsm#FSM - --- FSM_SET class models Finite State Machines for @{Set}s. Note that these FSMs control multiple objects!!! So State concerns here -- for multiple objects or the position of the state machine in the process. - -- + -- -- === - -- + -- -- @field #FSM_SET FSM_SET - -- FSM_SET = { ClassName = "FSM_SET", } - + --- Creates a new FSM_SET object. -- @param #FSM_SET self -- @param #table FSMT Finite State Machine Table -- @param Set_SET_BASE FSMSet (optional) The Set object that the FSM_SET governs. -- @return #FSM_SET function FSM_SET:New( FSMSet ) - + -- Inherits from BASE self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_SET - + if FSMSet then self:Set( FSMSet ) end - + return self end - + --- Sets the SET_BASE object that the FSM_SET governs. -- @param #FSM_SET self -- @param Core.Set#SET_BASE FSMSet @@ -1426,16 +1417,16 @@ do -- FSM_SET self:F( FSMSet ) self.Set = FSMSet end - + --- Gets the SET_BASE object that the FSM_SET governs. -- @param #FSM_SET self -- @return Core.Set#SET_BASE function FSM_SET:Get() return self.Set end - - function FSM_SET:_call_handler( step, trigger, params, EventName ) - local handler = step .. trigger + + function FSM_SET:_call_handler( step, trigger, params, EventName ) + local handler = step .. trigger if self[handler] then self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] ) self._EventSchedules[EventName] = nil diff --git a/Moose Development/Moose/Core/Goal.lua b/Moose Development/Moose/Core/Goal.lua index c145d6de4..bc33246f6 100644 --- a/Moose Development/Moose/Core/Goal.lua +++ b/Moose Development/Moose/Core/Goal.lua @@ -1,89 +1,87 @@ --- **Core** - Models the process to achieve goal(s). -- -- === --- +-- -- ## Features: --- +-- -- * Define the goal. -- * Monitor the goal achievement. -- * Manage goal contribution by players. --- +-- -- === --- +-- -- Classes that implement a goal achievement, will derive from GOAL to implement the ways how the achievements can be realized. --- +-- -- === --- +-- -- ### Author: **FlightControl** -- ### Contributions: **funkyfranky** --- +-- -- === --- +-- -- @module Core.Goal -- @image Core_Goal.JPG - do -- Goal --- @type GOAL -- @extends Core.Fsm#FSM - --- 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. - -- + -- -- # 3. Player contributions. - -- + -- -- Goals are most of the time achieved by players. These player achievements can be registered as part of the goal achievement. -- Use @{#GOAL.AddPlayerContribution}() to add a player contribution to the goal. -- The player contributions are based on a points system, an internal counter per player. - -- So once the goal has been achieved, the player contributions can be queried using @{#GOAL.GetPlayerContributions}(), + -- So once the goal has been achieved, the player contributions can be queried using @{#GOAL.GetPlayerContributions}(), -- that retrieves all contributions done by the players. For one player, the contribution can be queried using @{#GOAL.GetPlayerContribution}(). -- The total amount of player contributions can be queried using @{#GOAL.GetTotalContributions}(). - -- + -- -- # 4. Goal achievement. - -- + -- -- Once the goal is achieved, the mission designer will need to trigger the goal achievement using the **Achieved** event. -- The underlying 2 examples will achieve the goals for the `Goal` object: - -- + -- -- Goal:Achieved() -- Achieve the goal immediately. -- Goal:__Achieved( 30 ) -- Achieve the goal within 30 seconds. - -- + -- -- # 5. Check goal achievement. - -- + -- -- The method @{#GOAL.IsAchieved}() will return true if the goal is achieved (the trigger **Achieved** was executed). -- You can use this method to check asynchronously if a goal has been achieved, for example using a scheduler. - -- + -- -- @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( {} ) @@ -104,11 +102,10 @@ do -- Goal -- @param #string From -- @param #string Event -- @param #string To - - + self:SetStartState( "Pending" ) - self:AddTransition( "*", "Achieved", "Achieved" ) - + self:AddTransition( "*", "Achieved", "Achieved" ) + --- Achieved Handler OnBefore for GOAL -- @function [parent=#GOAL] OnBeforeAchieved -- @param #GOAL self @@ -116,47 +113,44 @@ do -- Goal -- @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 - - + --- Add a new contribution by a player. -- @param #GOAL self -- @param #string PlayerName The name of the player. function GOAL:AddPlayerContribution( PlayerName ) - self:F({PlayerName}) + self:F( { 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 + return self.Players[PlayerName] or 0 end - --- Get the players who contributed to achieve the goal. -- The result is a list of players, sorted by the name of the players. -- @param #GOAL self @@ -165,7 +159,6 @@ do -- Goal return self.Players or {} end - --- Gets the total contributions that happened to achieve the goal. -- The result is a number. -- @param #GOAL self @@ -173,9 +166,7 @@ do -- Goal function GOAL:GetTotalContributions() return self.TotalContributions or 0 end - - - + --- Validates if the goal is achieved. -- @param #GOAL self -- @return #boolean true if the goal is achieved. @@ -183,4 +174,4 @@ do -- Goal return self:Is( "Achieved" ) end -end \ No newline at end of file +end diff --git a/Moose Development/Moose/Core/Menu.lua b/Moose Development/Moose/Core/Menu.lua index 2cbf1d6b5..8a6edf657 100644 --- a/Moose Development/Moose/Core/Menu.lua +++ b/Moose Development/Moose/Core/Menu.lua @@ -277,7 +277,6 @@ do -- MENU_BASE end end - do -- MENU_COMMAND_BASE --- @type MENU_COMMAND_BASE -- @field #function MenuCallHandler diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 067d609fb..888ae3779 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -1,9 +1,9 @@ --- **Core** - Informs the players using messages during a simulation. --- +-- -- === --- +-- -- ## Features: --- +-- -- * A more advanced messaging system using the DCS message system. -- * Time messages. -- * Send messages based on a message type, which has a pre-defined duration that can be tweaked in SETTINGS. @@ -13,7 +13,7 @@ -- * Send messages to a specific unit or client. -- -- === --- +-- -- @module Core.Message -- @image Core_Message.JPG @@ -24,14 +24,14 @@ --- Message System to display Messages to Clients, Coalitions or All. -- Messages are shown on the display panel for an amount of seconds, and will then disappear. -- Messages can contain a category which is indicating the category of the message. --- +-- -- ## MESSAGE construction --- +-- -- Messages are created with @{#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet. -- To send messages, you need to use the To functions. --- +-- -- ## Send messages to an audience --- +-- -- Messages are sent: -- -- * To a @{Client} using @{#MESSAGE.ToClient}(). @@ -41,26 +41,26 @@ -- * To the red coalition using @{#MESSAGE.ToRed}(). -- * To the blue coalition using @{#MESSAGE.ToBlue}(). -- * To all Players using @{#MESSAGE.ToAll}(). --- +-- -- ## Send conditionally to an audience --- +-- -- Messages can be sent conditionally to an audience (when a condition is true): --- +-- -- * To all players using @{#MESSAGE.ToAllIf}(). -- * To a coalition using @{#MESSAGE.ToCoalitionIf}(). --- +-- -- === --- +-- -- ### Author: **FlightControl** --- ### Contributions: --- +-- ### Contributions: +-- -- === --- +-- -- @field #MESSAGE MESSAGE = { - ClassName = "MESSAGE", - MessageCategory = 0, - MessageID = 0, + ClassName = "MESSAGE", + MessageCategory = 0, + MessageID = 0, } --- Message Types @@ -70,10 +70,9 @@ MESSAGE.Type = { Information = "Information", Briefing = "Briefing Report", Overview = "Overview Report", - Detailed = "Detailed Report" + Detailed = "Detailed Report", } - --- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients. -- @param self -- @param #string MessageText is the text of the Message. @@ -82,52 +81,52 @@ MESSAGE.Type = { -- @param #boolean ClearScreen (optional) Clear all previous messages if true. -- @return #MESSAGE -- @usage --- -- Create a series of new Messages. --- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score". --- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win". --- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". --- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" ) --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" ) --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score") +-- +-- -- Create a series of new Messages. +-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score". +-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win". +-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". +-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". +-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" ) +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" ) +-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ) +-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score") +-- function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( { MessageText, MessageDuration, MessageCategory } ) - + local self = BASE:Inherit( self, BASE:New() ) + self:F( { MessageText, MessageDuration, MessageCategory } ) self.MessageType = nil - + -- When no MessageCategory is given, we don't show it as a title... - if MessageCategory and MessageCategory ~= "" then - if MessageCategory:sub(-1) ~= "\n" then + if MessageCategory and MessageCategory ~= "" then + if MessageCategory:sub( -1 ) ~= "\n" then self.MessageCategory = MessageCategory .. ": " else - self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n" + self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n" end else self.MessageCategory = "" end - - self.ClearScreen=false - if ClearScreen~=nil then - self.ClearScreen=ClearScreen + + self.ClearScreen = false + if ClearScreen ~= nil then + self.ClearScreen = ClearScreen 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 + self.MessageDuration = MessageDuration or 5 + self.MessageTime = timer.getTime() + self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 ) - return self + self.MessageSent = false + self.MessageGroup = false + self.MessageCoalition = false + + return self end - ---- Creates a new MESSAGE object of a certain type. --- Note that these MESSAGE objects are not yet displayed on the display panel. +--- Creates a new MESSAGE object of a certain type. +-- Note that these MESSAGE objects are not yet displayed on the display panel. -- You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients. -- The message display times are automatically defined based on the timing settings in the @{Settings} menu. -- @param self @@ -136,65 +135,65 @@ end -- @param #boolean ClearScreen (optional) Clear all previous messages. -- @return #MESSAGE -- @usage +-- -- MessageAll = MESSAGE:NewType( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", MESSAGE.Type.Information ) -- MessageRED = MESSAGE:NewType( "To the RED Players: You receive a penalty because you've killed one of your own units", MESSAGE.Type.Information ) -- MessageClient1 = MESSAGE:NewType( "Congratulations, you've just hit a target", MESSAGE.Type.Update ) -- MessageClient2 = MESSAGE:NewType( "Congratulations, you've just killed a target", MESSAGE.Type.Update ) +-- function MESSAGE:NewType( MessageText, MessageType, ClearScreen ) local self = BASE:Inherit( self, BASE:New() ) self:F( { MessageText } ) - + self.MessageType = MessageType - - self.ClearScreen=false - if ClearScreen~=nil then - self.ClearScreen=ClearScreen + + self.ClearScreen = false + if ClearScreen ~= nil then + self.ClearScreen = ClearScreen end self.MessageTime = timer.getTime() - self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1) - + self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 ) + return self end - - ---- Clears all previous messages from the screen before the new message is displayed. Not that this must come before all functions starting with ToX(), e.g. ToAll(), ToGroup() etc. +--- Clears all previous messages from the screen before the new message is displayed. Not that this must come before all functions starting with ToX(), e.g. ToAll(), ToGroup() etc. -- @param #MESSAGE self -- @return #MESSAGE function MESSAGE:Clear() self:F() - self.ClearScreen=true + self.ClearScreen = true return self end - - --- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player". -- @param #MESSAGE self -- @param Wrapper.Client#CLIENT Client is the Group of the Client. -- @param Core.Settings#SETTINGS Settings Settings used to display the message. -- @return #MESSAGE -- @usage --- -- Send the 2 messages created with the @{New} method to the Client Group. --- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1. --- ClientGroup = Group.getByName( "ClientGroup" ) -- --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- or --- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- or --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ) --- MessageClient1:ToClient( ClientGroup ) --- MessageClient2:ToClient( ClientGroup ) +-- -- Send the 2 messages created with the @{New} method to the Client Group. +-- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1. +-- ClientGroup = Group.getByName( "ClientGroup" ) +-- +-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) +-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) +-- or +-- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) +-- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) +-- or +-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ) +-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ) +-- MessageClient1:ToClient( ClientGroup ) +-- MessageClient2:ToClient( ClientGroup ) +-- function MESSAGE:ToClient( Client, Settings ) - self:F( Client ) + self:F( Client ) - if Client and Client:GetClientGroupID() then + if Client and Client:GetClientGroupID() then if self.MessageType then local Settings = Settings or ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS @@ -215,7 +214,7 @@ function MESSAGE:ToClient( Client, Settings ) return self end ---- Sends a MESSAGE to a Group. +--- Sends a MESSAGE to a Group. -- @param #MESSAGE self -- @param Wrapper.Group#GROUP Group to which the message is displayed. -- @return #MESSAGE Message object. @@ -223,16 +222,40 @@ 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 -- Core.Settings#SETTINGS + self.MessageDuration = Settings:GetMessageTime( self.MessageType ) + self.MessageCategory = "" -- self.MessageType .. ": " + 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, self.ClearScreen ) + end + end + + return self +end + +--- Sends a MESSAGE to a Unit. +-- @param #MESSAGE self +-- @param Wrapper.Unit#UNIT Unit to which the message is displayed. +-- @return #MESSAGE Message object. +function MESSAGE:ToUnit( Unit, Settings ) + self:F( Unit.IdentifiableName ) + + if Unit then if self.MessageType then - local Settings = Settings or ( Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS + local Settings = Settings or ( Unit and _DATABASE:GetPlayerSettings( Unit:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS self.MessageDuration = Settings:GetMessageTime( self.MessageType ) self.MessageCategory = "" -- self.MessageType .. ": " 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, self.ClearScreen ) + trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen ) end end @@ -264,58 +287,64 @@ function MESSAGE:ToUnit( Unit, Settings ) end --- Sends a MESSAGE to the Blue coalition. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the BLUE coalition. --- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() --- or --- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() --- or --- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageBLUE:ToBlue() -function MESSAGE:ToBlue() - self:F() - - self:ToCoalition( coalition.side.BLUE ) - - return self -end - ---- Sends a MESSAGE to the Red Coalition. -- @param #MESSAGE self -- @return #MESSAGE -- @usage --- -- Send a message created with the @{New} method to the RED coalition. --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() --- or --- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() --- or --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageRED:ToRed() -function MESSAGE:ToRed( ) - self:F() +-- +-- -- Send a message created with the @{New} method to the BLUE coalition. +-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() +-- or +-- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() +-- or +-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) +-- MessageBLUE:ToBlue() +-- +function MESSAGE:ToBlue() + self:F() - self:ToCoalition( coalition.side.RED ) - - return self + self:ToCoalition( coalition.side.BLUE ) + + return self end ---- Sends a MESSAGE to a Coalition. +--- Sends a MESSAGE to the Red Coalition. +-- @param #MESSAGE self +-- @return #MESSAGE +-- @usage +-- +-- -- Send a message created with the @{New} method to the RED coalition. +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() +-- or +-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() +-- or +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) +-- MessageRED:ToRed() +-- +function MESSAGE:ToRed() + self:F() + + self:ToCoalition( coalition.side.RED ) + + return self +end + +--- Sends a MESSAGE to a Coalition. -- @param #MESSAGE self -- @param #DCS.coalition.side CoalitionSide @{#DCS.coalition.side} to which the message is displayed. -- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display. -- @return #MESSAGE Message object. -- @usage --- -- Send a message created with the @{New} method to the RED coalition. --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) --- or --- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) --- or --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageRED:ToCoalition( coalition.side.RED ) +-- +-- -- Send a message created with the @{New} method to the RED coalition. +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) +-- or +-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) +-- or +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) +-- MessageRED:ToCoalition( coalition.side.RED ) +-- function MESSAGE:ToCoalition( CoalitionSide, Settings ) - self:F( CoalitionSide ) + self:F( CoalitionSide ) if self.MessageType then local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS @@ -323,20 +352,20 @@ function MESSAGE:ToCoalition( CoalitionSide, Settings ) self.MessageCategory = "" -- self.MessageType .. ": " end - if CoalitionSide then + 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.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen ) + self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration ) + trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen ) end - end - - return self + end + + return self end ---- Sends a MESSAGE to a Coalition if the given Condition is true. +--- Sends a MESSAGE to a Coalition if the given Condition is true. -- @param #MESSAGE self -- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}. --- @param #boolean Condition Sends the message only if the condition is true. +-- @param #boolean Condition Sends the message only if the condition is true. -- @return #MESSAGE self function MESSAGE:ToCoalitionIf( CoalitionSide, Condition ) self:F( CoalitionSide ) @@ -344,7 +373,7 @@ function MESSAGE:ToCoalitionIf( CoalitionSide, Condition ) if Condition and Condition == true then self:ToCoalition( CoalitionSide ) end - + return self end @@ -353,14 +382,16 @@ end -- @param Core.Settings#Settings Settings (Optional) Settings for message display. -- @return #MESSAGE -- @usage --- -- Send a message created to all players. --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() --- or --- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() --- or --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ) --- MessageAll:ToAll() -function MESSAGE:ToAll(Settings) +-- +-- -- Send a message created to all players. +-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() +-- or +-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() +-- or +-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ) +-- MessageAll:ToAll() +-- +function MESSAGE:ToAll( Settings ) self:F() if self.MessageType then @@ -370,14 +401,13 @@ function MESSAGE:ToAll(Settings) 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, self.ClearScreen ) + 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, self.ClearScreen ) end return self end - --- Sends a MESSAGE to all players if the given Condition is true. -- @param #MESSAGE self -- @return #MESSAGE @@ -387,5 +417,5 @@ function MESSAGE:ToAllIf( Condition ) self:ToAll() end - return self + return self end diff --git a/Moose Development/Moose/Core/Report.lua b/Moose Development/Moose/Core/Report.lua index bd860996b..d8225adcc 100644 --- a/Moose Development/Moose/Core/Report.lua +++ b/Moose Development/Moose/Core/Report.lua @@ -1,13 +1,13 @@ --- **Core** - Provides a handy means to create messages and reports. -- -- === --- +-- -- ## Features: --- +-- -- * Create text blocks that are formatted. -- * Create automatic indents. -- * Variate the delimiters between reporting lines. --- +-- -- === -- -- ### Authors: FlightControl : Design & Programming @@ -15,7 +15,6 @@ -- @module Core.Report -- @image Core_Report.JPG - --- @type REPORT -- @extends Core.Base#BASE @@ -36,7 +35,7 @@ function REPORT:New( Title ) self.Report = {} - self:SetTitle( Title or "" ) + self:SetTitle( Title or "" ) self:SetIndent( 3 ) return self @@ -45,28 +44,26 @@ end --- Has the REPORT Text? -- @param #REPORT self -- @return #boolean -function REPORT:HasText() --R2.1 - +function REPORT:HasText() -- R2.1 + return #self.Report > 0 end - --- Set indent of a REPORT. -- @param #REPORT self -- @param #number Indent -- @return #REPORT -function REPORT:SetIndent( Indent ) --R2.1 +function REPORT:SetIndent( Indent ) -- R2.1 self.Indent = Indent return self end - --- Add a new line to a REPORT. -- @param #REPORT self -- @param #string Text -- @return #REPORT function REPORT:Add( Text ) - self.Report[#self.Report+1] = Text + self.Report[#self.Report + 1] = Text return self end @@ -76,17 +73,17 @@ end -- @param #string Separator (optional) The start of each report line can begin with an optional separator character. This can be a "-", or "#", or "*". You're free to choose what you find the best. -- @return #REPORT function REPORT:AddIndent( Text, Separator ) - self.Report[#self.Report+1] = ( ( Separator and Separator .. string.rep( " ", self.Indent - 1 ) ) or string.rep(" ", self.Indent ) ) .. Text:gsub("\n","\n"..string.rep( " ", self.Indent ) ) + self.Report[#self.Report + 1] = ((Separator and Separator .. string.rep( " ", self.Indent - 1 )) or string.rep( " ", self.Indent )) .. Text:gsub( "\n", "\n" .. string.rep( " ", self.Indent ) ) return self end ---- Produces the text of the report, taking into account an optional delimeter, which is \n by default. +--- Produces the text of the report, taking into account an optional delimiter, which is \n by default. -- @param #REPORT self -- @param #string Delimiter (optional) A delimiter text. -- @return #string The report text. 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 "" + local ReportText = (self.Title ~= "" and self.Title .. Delimiter or self.Title) .. table.concat( self.Report, Delimiter ) or "" return ReportText end @@ -95,7 +92,7 @@ end -- @param #string Title The title of the report. -- @return #REPORT function REPORT:SetTitle( Title ) - self.Title = Title + self.Title = Title return self end diff --git a/Moose Development/Moose/Core/ScheduleDispatcher.lua b/Moose Development/Moose/Core/ScheduleDispatcher.lua index 2ba64baa0..6a96d92ff 100644 --- a/Moose Development/Moose/Core/ScheduleDispatcher.lua +++ b/Moose Development/Moose/Core/ScheduleDispatcher.lua @@ -1,36 +1,36 @@ --- **Core** -- SCHEDULEDISPATCHER dispatches the different schedules. --- +-- -- === --- +-- -- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects. --- +-- -- This class is tricky and needs some thorough explanation. -- SCHEDULE classes are used to schedule functions for objects, or as persistent objects. -- The SCHEDULEDISPATCHER class ensures that: --- +-- -- - Scheduled functions are planned according the SCHEDULER object parameters. -- - Scheduled functions are repeated when requested, according the SCHEDULER object parameters. -- - Scheduled functions are automatically removed when the schedule is finished, according the SCHEDULER object parameters. --- +-- -- The SCHEDULEDISPATCHER class will manage SCHEDULER object in memory during garbage collection: --- +-- -- - When a SCHEDULER object is not attached to another object (that is, it's first :Schedule() parameter is nil), then the SCHEDULER object is _persistent_ within memory. -- - When a SCHEDULER object *is* attached to another object, then the SCHEDULER object is _not persistent_ within memory after a garbage collection! --- --- The none persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collectged, when the parent object is also desroyed or nillified and garbage collected. --- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object, +-- +-- The non-persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collected when the parent object is destroyed, or set to nil and garbage collected. +-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object, -- these will not be executed anymore when the SCHEDULER object has been destroyed. --- +-- -- The SCHEDULEDISPATCHER allows multiple scheduled functions to be planned and executed for one SCHEDULER object. -- The SCHEDULER object therefore keeps a table of "CallID's", which are returned after each planning of a new scheduled function by the SCHEDULEDISPATCHER. --- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method. +-- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method. -- The Schedule() method returns the CallID that is the reference ID for each planned schedule. --- +-- -- === --- +-- -- ### Contributions: - -- ### Authors: FlightControl : Design & Programming --- +-- -- @module Core.ScheduleDispatcher -- @image Core_Schedule_Dispatcher.JPG @@ -38,7 +38,7 @@ -- @type SCHEDULEDISPATCHER -- @field #string ClassName Name of the class. -- @field #number CallID Call ID counter. --- @field #table PersistentSchedulers Persistant schedulers. +-- @field #table PersistentSchedulers Persistent schedulers. -- @field #table ObjectSchedulers Schedulers that only exist as long as the master object exists. -- @field #table Schedule Meta table setmetatable( {}, { __mode = "k" } ). -- @extends Core.Base#BASE @@ -46,11 +46,11 @@ --- The SCHEDULEDISPATCHER structure -- @type SCHEDULEDISPATCHER SCHEDULEDISPATCHER = { - ClassName = "SCHEDULEDISPATCHER", - CallID = 0, - PersistentSchedulers = {}, - ObjectSchedulers = {}, - Schedule = nil, + ClassName = "SCHEDULEDISPATCHER", + CallID = 0, + PersistentSchedulers = {}, + ObjectSchedulers = {}, + Schedule = nil, } --- Player data table holding all important parameters of each player. @@ -58,7 +58,7 @@ SCHEDULEDISPATCHER = { -- @field #function Function The schedule function to be called. -- @field #table Arguments Schedule function arguments. -- @field #number Start Start time in seconds. --- @field #number Repeat Repeat time intervall in seconds. +-- @field #number Repeat Repeat time interval in seconds. -- @field #number Randomize Randomization factor [0,1]. -- @field #number Stop Stop time in seconds. -- @field #number StartTime Time in seconds when the scheduler is created. @@ -77,7 +77,7 @@ end --- Add a Schedule to the ScheduleDispatcher. -- The development of this method was really tidy. --- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is nillified. +-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is set to nil. -- Nothing of this code should be modified without testing it thoroughly. -- @param #SCHEDULEDISPATCHER self -- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object. @@ -85,7 +85,7 @@ end -- @param #table ScheduleArguments Table of arguments passed to the ScheduleFunction. -- @param #number Start Start time in seconds. -- @param #number Repeat Repeat interval in seconds. --- @param #number Randomize Radomization factor [0,1]. +-- @param #number Randomize Randomization factor [0,1]. -- @param #number Stop Stop time in seconds. -- @param #number TraceLevel Trace level [0,3]. -- @param Core.Fsm#FSM Fsm Finite state model. @@ -95,32 +95,32 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr -- Increase counter. self.CallID = self.CallID + 1 - + -- Create ID. - local CallID = self.CallID .. "#" .. ( Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "" ) or "" - - self:T2(string.format("Adding schedule #%d CallID=%s", self.CallID, CallID)) + local CallID = self.CallID .. "#" .. (Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "") or "" + + self:T2( string.format( "Adding schedule #%d CallID=%s", self.CallID, CallID ) ) -- Initialize PersistentSchedulers self.PersistentSchedulers = self.PersistentSchedulers or {} -- Initialize the ObjectSchedulers array, which is a weakly coupled table. -- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array. - self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } ) - + self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } ) + if Scheduler.MasterObject then --env.info("FF Object Scheduler") self.ObjectSchedulers[CallID] = Scheduler - self:F3( { CallID = CallID, ObjectScheduler = tostring(self.ObjectSchedulers[CallID]), MasterObject = tostring(Scheduler.MasterObject) } ) + self:F3( { CallID = CallID, ObjectScheduler = tostring( self.ObjectSchedulers[CallID] ), MasterObject = tostring( Scheduler.MasterObject ) } ) else --env.info("FF Persistent Scheduler") 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] = {} --#SCHEDULEDISPATCHER.ScheduleData + self.Schedule[Scheduler][CallID] = {} -- #SCHEDULEDISPATCHER.ScheduleData self.Schedule[Scheduler][CallID].Function = ScheduleFunction self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 ) @@ -128,8 +128,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr self.Schedule[Scheduler][CallID].Repeat = Repeat or 0 self.Schedule[Scheduler][CallID].Randomize = Randomize or 0 self.Schedule[Scheduler][CallID].Stop = Stop - - + -- This section handles the tracing of the scheduled calls. -- Because these calls will be executed with a delay, we inspect the place where these scheduled calls are initiated. -- The Info structure contains the output of the debug.getinfo() calls, which inspects the call stack for the function name, line number and source name. @@ -151,10 +150,10 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr -- Therefore, in the call stack, at the TraceLevel these functions are mentioned as "tail calls", and the Info.name field will be nil as a result. -- To obtain the correct function name for FSM object calls, the function is mentioned in the call stack at a higher stack level. -- So when function name stored in Info.name is nil, then I inspect the function name within the call stack one level higher. - -- So this little piece of code does its magic wonderfully, preformance overhead is neglectible, as scheduled calls don't happen that often. + -- So this little piece of code does its magic wonderfully, performance overhead is negligible, as scheduled calls don't happen that often. local Info = {} - + if debug then TraceLevel = TraceLevel or 2 Info = debug.getinfo( TraceLevel, "nlS" ) @@ -168,7 +167,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr --- Function passed to the DCS timer.scheduleFunction() self.Schedule[Scheduler][CallID].CallHandler = function( Params ) - + local CallID = Params.CallID local Info = Params.Info or {} local Source = Info.source or "?" @@ -182,27 +181,27 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr end return errmsg end - - -- Get object or persistant scheduler object. - local Scheduler = self.ObjectSchedulers[CallID] --Core.Scheduler#SCHEDULER + + -- Get object or persistent scheduler object. + local Scheduler = self.ObjectSchedulers[CallID] -- Core.Scheduler#SCHEDULER if not Scheduler then Scheduler = self.PersistentSchedulers[CallID] end - - --self:T3( { Scheduler = Scheduler } ) - + + -- self:T3( { Scheduler = Scheduler } ) + if Scheduler then - local MasterObject = tostring(Scheduler.MasterObject) - - -- Schedule object. - local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData - - --self:T3( { Schedule = Schedule } ) + local MasterObject = tostring( Scheduler.MasterObject ) - local SchedulerObject = Scheduler.MasterObject --Scheduler.SchedulerObject Now is this the Maste or Scheduler object? + -- Schedule object. + local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData + + -- self:T3( { Schedule = Schedule } ) + + local SchedulerObject = Scheduler.MasterObject -- Scheduler.SchedulerObject Now is this the Master or Scheduler object? local ShowTrace = Scheduler.ShowTrace - + local ScheduleFunction = Schedule.Function local ScheduleArguments = Schedule.Arguments or {} local Start = Schedule.Start @@ -210,12 +209,11 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr local Randomize = Schedule.Randomize or 0 local Stop = Schedule.Stop or 0 local ScheduleID = Schedule.ScheduleID - - - local Prefix = ( Repeat == 0 ) and "--->" or "+++>" - + + local Prefix = (Repeat == 0) and "--->" or "+++>" + local Status, Result - --self:E( { SchedulerObject = SchedulerObject } ) + -- self:E( { SchedulerObject = SchedulerObject } ) if SchedulerObject then local function Timer() if ShowTrace then @@ -230,40 +228,39 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr if ShowTrace then self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" ) end - return ScheduleFunction( unpack( ScheduleArguments ) ) + return ScheduleFunction( unpack( ScheduleArguments ) ) end Status, Result = xpcall( Timer, ErrorHandler ) end - + local CurrentTime = timer.getTime() local StartTime = Schedule.StartTime -- Debug info. - self:F3( { CallID=CallID, ScheduleID=ScheduleID, 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.0001 -- Accuracy - --self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } ) + self:F3( { CallID = CallID, ScheduleID = ScheduleID, 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.0001 -- Accuracy + -- self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } ) return ScheduleTime -- returns the next time the function needs to be called. else self:Stop( Scheduler, CallID ) end - + else self:Stop( Scheduler, CallID ) end else self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" ) end - + return nil end - + self:Start( Scheduler, CallID, Info ) - + return CallID end @@ -287,33 +284,33 @@ end -- @param #string Info (Optional) Debug info. function SCHEDULEDISPATCHER:Start( Scheduler, CallID, Info ) self:F2( { Start = CallID, Scheduler = Scheduler } ) - + if CallID then - - local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData - + + local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData + -- Only start when there is no ScheduleID defined! -- This prevents to "Start" the scheduler twice with the same CallID... if not Schedule.ScheduleID then - + -- Current time in seconds. - local Tnow=timer.getTime() - - Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started. - + local Tnow = timer.getTime() + + Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started. + -- Start DCS schedule function https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction - Schedule.ScheduleID = timer.scheduleFunction(Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start) - - self:T(string.format("Starting scheduledispatcher Call ID=%s ==> Schedule ID=%s", tostring(CallID), tostring(Schedule.ScheduleID))) + Schedule.ScheduleID = timer.scheduleFunction( Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start ) + + self:T( string.format( "Starting SCHEDULEDISPATCHER Call ID=%s ==> Schedule ID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) ) end - + else - + -- Recursive. for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do self:Start( Scheduler, CallID, Info ) -- Recursive end - + end end @@ -325,29 +322,29 @@ function SCHEDULEDISPATCHER:Stop( Scheduler, CallID ) self:F2( { Stop = CallID, Scheduler = Scheduler } ) if CallID then - - local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData - + + local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData + -- Only stop when there is a ScheduleID defined for the CallID. So, when the scheduler was stopped before, do nothing. if Schedule.ScheduleID then - - self:T(string.format("scheduledispatcher stopping scheduler CallID=%s, ScheduleID=%s", tostring(CallID), tostring(Schedule.ScheduleID))) - + + self:T( string.format( "SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) ) + -- Remove schedule function https://wiki.hoggitworld.com/view/DCS_func_removeFunction - timer.removeFunction(Schedule.ScheduleID) - + timer.removeFunction( Schedule.ScheduleID ) + Schedule.ScheduleID = nil - + else - self:T(string.format("Error no ScheduleID for CallID=%s", tostring(CallID))) + self:T( string.format( "Error no ScheduleID for CallID=%s", tostring( CallID ) ) ) end - + else - + for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do self:Stop( Scheduler, CallID ) -- Recursive end - + end end @@ -362,7 +359,7 @@ function SCHEDULEDISPATCHER:Clear( Scheduler ) end end ---- Shopw tracing info. +--- Show tracing info. -- @param #SCHEDULEDISPATCHER self -- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object. function SCHEDULEDISPATCHER:ShowTrace( Scheduler ) diff --git a/Moose Development/Moose/Core/Scheduler.lua b/Moose Development/Moose/Core/Scheduler.lua index d9d18896c..e228fe628 100644 --- a/Moose Development/Moose/Core/Scheduler.lua +++ b/Moose Development/Moose/Core/Scheduler.lua @@ -3,15 +3,15 @@ -- === -- -- ## Features: --- +-- -- * Schedule functions over time, --- * optionally in an optional specified time interval, --- * optionally **repeating** with a specified time repeat interval, --- * optionally **randomizing** with a specified time interval randomization factor, --- * optionally **stop** the repeating after a specified time interval. +-- * optionally in an optional specified time interval, +-- * optionally **repeating** with a specified time repeat interval, +-- * optionally **randomizing** with a specified time interval randomization factor, +-- * optionally **stop** the repeating after a specified time interval. -- -- === --- +-- -- # Demo Missions -- -- ### [SCHEDULER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCH%20-%20Scheduler) @@ -19,23 +19,23 @@ -- ### [SCHEDULER Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCH%20-%20Scheduler) -- -- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases) --- --- === --- --- # YouTube Channel --- --- ### [SCHEDULER YouTube Channel (none)]() --- +-- -- === -- --- ### Contributions: --- +-- # YouTube Channel +-- +-- ### [SCHEDULER YouTube Channel (none)]() +-- +-- === +-- +-- ### Contributions: +-- -- * FlightControl : Concept & Testing --- --- ### Authors: --- +-- +-- ### Authors: +-- -- * FlightControl : Design & Programming --- +-- -- === -- -- @module Core.Scheduler @@ -48,62 +48,61 @@ -- @field #boolean ShowTrace Trace info if true. -- @extends Core.Base#BASE - --- Creates and handles schedules over time, which allow to execute code at specific time intervals with randomization. --- +-- -- A SCHEDULER can manage **multiple** (repeating) schedules. Each planned or executing schedule has a unique **ScheduleID**. -- The ScheduleID is returned when the method @{#SCHEDULER.Schedule}() is called. -- It is recommended to store the ScheduleID in a variable, as it is used in the methods @{SCHEDULER.Start}() and @{SCHEDULER.Stop}(), -- which can start and stop specific repeating schedules respectively within a SCHEDULER object. -- -- ## SCHEDULER constructor --- +-- -- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters: --- +-- -- The @{#SCHEDULER.New}() method returns 2 variables: --- +-- -- 1. The SCHEDULER object reference. -- 2. The first schedule planned in the SCHEDULER object. --- +-- -- To clarify the different appliances, lets have a look at the following examples: --- +-- -- ### Construct a SCHEDULER object without a persistent schedule. --- +-- -- * @{#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection. --- +-- -- MasterObject = SCHEDULER:New() -- SchedulerID = MasterObject:Schedule( nil, ScheduleFunction, {} ) --- +-- -- The above example creates a new MasterObject, but does not schedule anything. -- A separate schedule is created by using the MasterObject using the method :Schedule..., which returns a ScheduleID --- +-- -- ### Construct a SCHEDULER object without a volatile schedule, but volatile to the Object existence... --- --- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is nillified or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection. --- +-- +-- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is set to nil or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection. +-- -- ZoneObject = ZONE:New( "ZoneName" ) -- MasterObject = SCHEDULER:New( ZoneObject ) -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} ) -- ... -- ZoneObject = nil -- garbagecollect() --- +-- -- The above example creates a new MasterObject, but does not schedule anything, and is bound to the existence of ZoneObject, which is a ZONE. -- A separate schedule is created by using the MasterObject using the method :Schedule()..., which returns a ScheduleID -- Later in the logic, the ZoneObject is put to nil, and garbage is collected. -- As a result, the MasterObject will cancel any planned schedule. --- +-- -- ### Construct a SCHEDULER object with a persistent schedule. --- +-- -- * @{#SCHEDULER.New}( nil, Function, FunctionArguments, Start, ... ): Setup a new persistent SCHEDULER object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters. --- +-- -- MasterObject, SchedulerID = SCHEDULER:New( nil, ScheduleFunction, {} ) --- +-- -- The above example creates a new MasterObject, and does schedule the first schedule as part of the call. -- Note that 2 variables are returned here: MasterObject, ScheduleID... --- +-- -- ### Construct a SCHEDULER object without a schedule, but volatile to the Object existence... --- +-- -- * @{#SCHEDULER.New}( Object, Function, FunctionArguments, Start, ... ): Setup a new SCHEDULER object, linked to Object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters. -- -- ZoneObject = ZONE:New( "ZoneName" ) @@ -112,13 +111,13 @@ -- ... -- ZoneObject = nil -- garbagecollect() --- +-- -- The above example creates a new MasterObject, and schedules a method call (ScheduleFunction), -- and is bound to the existence of ZoneObject, which is a ZONE object (ZoneObject). -- Both a MasterObject and a SchedulerID variable are returned. -- Later in the logic, the ZoneObject is put to nil, and garbage is collected. -- As a result, the MasterObject will cancel the planned schedule. --- +-- -- ## SCHEDULER timer stopping and (re-)starting. -- -- The SCHEDULER can be stopped and restarted with the following methods: @@ -133,70 +132,70 @@ -- MasterObject:Stop( SchedulerID ) -- ... -- MasterObject:Start( SchedulerID ) --- +-- -- The above example creates a new MasterObject, and does schedule the first schedule as part of the call. --- Note that 2 variables are returned here: MasterObject, ScheduleID... --- Later in the logic, the repeating schedule with SchedulerID is stopped. --- A bit later, the repeating schedule with SchedulerId is (re)-started. --- +-- Note that 2 variables are returned here: MasterObject, ScheduleID... +-- Later in the logic, the repeating schedule with SchedulerID is stopped. +-- A bit later, the repeating schedule with SchedulerId is (re)-started. +-- -- ## Create a new schedule --- --- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled. +-- +-- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled. -- This method is used by the :New() constructor when a new schedule is planned. --- +-- -- Consider the following code fragment of the SCHEDULER object creation. --- +-- -- ZoneObject = ZONE:New( "ZoneName" ) -- MasterObject = SCHEDULER:New( ZoneObject ) --- --- Several parameters can be specified that influence the behaviour of a Schedule. --- +-- +-- Several parameters can be specified that influence the behavior of a Schedule. +-- -- ### A single schedule, immediately executed --- +-- -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} ) --- --- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milleseconds ... --- +-- +-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milliseconds ... +-- -- ### A single schedule, planned over time --- +-- -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10 ) --- +-- -- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds ... --- +-- -- ### A schedule with a repeating time interval, planned over time --- +-- -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60 ) --- --- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, +-- +-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, -- and repeating 60 every seconds ... --- +-- -- ### A schedule with a repeating time interval, planned over time, with time interval randomization --- +-- -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5 ) --- --- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, +-- +-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, -- and repeating 60 seconds, with a 50% time interval randomization ... --- So the repeating time interval will be randomized using the **0.5**, --- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat, +-- So the repeating time interval will be randomized using the **0.5**, +-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat, -- which is in this example between **30** and **90** seconds. --- +-- -- ### A schedule with a repeating time interval, planned over time, with time interval randomization, and stop after a time interval --- +-- -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5, 300 ) --- --- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, +-- +-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, -- The schedule will repeat every 60 seconds. --- So the repeating time interval will be randomized using the **0.5**, --- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat, +-- So the repeating time interval will be randomized using the **0.5**, +-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat, -- which is in this example between **30** and **90** seconds. -- The schedule will stop after **300** seconds. --- +-- -- @field #SCHEDULER SCHEDULER = { - ClassName = "SCHEDULER", - Schedules = {}, - MasterObject = nil, - ShowTrace = nil, + ClassName = "SCHEDULER", + Schedules = {}, + MasterObject = nil, + ShowTrace = nil, } --- SCHEDULER constructor. @@ -211,15 +210,15 @@ SCHEDULER = { -- @return #SCHEDULER self. -- @return #table The ScheduleID of the planned schedule. function SCHEDULER:New( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) - + local self = BASE:Inherit( self, BASE:New() ) -- #SCHEDULER self:F2( { Start, Repeat, RandomizeFactor, Stop } ) local ScheduleID = nil - + self.MasterObject = MasterObject self.ShowTrace = false - + if SchedulerFunction then ScheduleID = self:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, 3 ) end @@ -235,7 +234,7 @@ end -- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. -- @param #number Repeat Specifies the time interval in seconds when the scheduler will call the event function. -- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat. --- @param #number Stop Time interval in seconds after which the scheduler will be stoppe. +-- @param #number Stop Time interval in seconds after which the scheduler will be stopped. -- @param #number TraceLevel Trace level [0,3]. Default 3. -- @param Core.Fsm#FSM Fsm Finite state model. -- @return #string The Schedule ID of the planned schedule. @@ -245,28 +244,27 @@ function SCHEDULER:Schedule( MasterObject, SchedulerFunction, SchedulerArguments -- Debug info. local ObjectName = "-" - if MasterObject and MasterObject.ClassName and MasterObject.ClassID then + if MasterObject and MasterObject.ClassName and MasterObject.ClassID then ObjectName = MasterObject.ClassName .. MasterObject.ClassID end - self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } ) - + self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } ) + -- Set master object. self.MasterObject = MasterObject - + -- Add schedule. - local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( - self, - SchedulerFunction, - SchedulerArguments, - Start, - Repeat, - RandomizeFactor, - Stop, - TraceLevel or 3, - Fsm - ) - - self.Schedules[#self.Schedules+1] = ScheduleID + local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( self, + SchedulerFunction, + SchedulerArguments, + Start, + Repeat, + RandomizeFactor, + Stop, + TraceLevel or 3, + Fsm + ) + + self.Schedules[#self.Schedules + 1] = ScheduleID return ScheduleID end @@ -276,7 +274,7 @@ end -- @param #string ScheduleID (Optional) The Schedule ID of the planned (repeating) schedule. function SCHEDULER:Start( ScheduleID ) self:F3( { ScheduleID } ) - self:T(string.format("Starting scheduler ID=%s", tostring(ScheduleID))) + self:T( string.format( "Starting scheduler ID=%s", tostring( ScheduleID ) ) ) _SCHEDULEDISPATCHER:Start( self, ScheduleID ) end @@ -285,7 +283,7 @@ end -- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule. function SCHEDULER:Stop( ScheduleID ) self:F3( { ScheduleID } ) - self:T(string.format("Stopping scheduler ID=%s", tostring(ScheduleID))) + self:T( string.format( "Stopping scheduler ID=%s", tostring( ScheduleID ) ) ) _SCHEDULEDISPATCHER:Stop( self, ScheduleID ) end @@ -294,15 +292,15 @@ end -- @param #string ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. function SCHEDULER:Remove( ScheduleID ) self:F3( { ScheduleID } ) - self:T(string.format("Removing scheduler ID=%s", tostring(ScheduleID))) + self:T( string.format( "Removing scheduler ID=%s", tostring( ScheduleID ) ) ) _SCHEDULEDISPATCHER:RemoveSchedule( self, ScheduleID ) end --- Clears all pending schedules. -- @param #SCHEDULER self function SCHEDULER:Clear() - self:F3( ) - self:T(string.format("Clearing scheduler")) + self:F3() + self:T( string.format( "Clearing scheduler" ) ) _SCHEDULEDISPATCHER:Clear( self ) end diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index fea437e70..1f43c2659 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -44,7 +44,6 @@ -- @module Core.Set -- @image Core_Sets.JPG - do -- SET_BASE --- @type SET_BASE @@ -55,7 +54,6 @@ do -- SET_BASE -- @field Core.Scheduler#SCHEDULER CallScheduler -- @extends Core.Base#BASE - --- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects. -- A SET provides iterators to iterate the SET, but will **temporarily** yield the ForEach iterator loop at defined **"intervals"** to the mail simulator loop. -- In this way, large loops can be done while not blocking the simulator main processing loop. @@ -79,12 +77,11 @@ do -- SET_BASE List = {}, Index = {}, Database = nil, - CallScheduler=nil, - TimeInterval=nil, - YieldInterval=nil, + CallScheduler = nil, + TimeInterval = nil, + YieldInterval = nil, } - --- Creates a new SET_BASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_BASE self -- @return #SET_BASE @@ -109,8 +106,7 @@ do -- SET_BASE -- @param #string ObjectName The name of the object. -- @param Object The object. - - self:AddTransition( "*", "Added", "*" ) + self:AddTransition( "*", "Added", "*" ) --- Removed Handler OnAfter for SET_BASE -- @function [parent=#SET_BASE] OnAfterRemoved @@ -121,7 +117,7 @@ do -- SET_BASE -- @param #string ObjectName The name of the object. -- @param Object The object. - self:AddTransition( "*", "Removed", "*" ) + self:AddTransition( "*", "Removed", "*" ) self.YieldInterval = 10 self.TimeInterval = 0.001 @@ -148,8 +144,6 @@ do -- SET_BASE return self end - - --- Finds an @{Core.Base#BASE} object based on the object Name. -- @param #SET_BASE self -- @param #string ObjectName @@ -160,7 +154,6 @@ do -- SET_BASE return ObjectFound end - --- Gets the Set. -- @param #SET_BASE self -- @return #SET_BASE self @@ -173,7 +166,7 @@ do -- SET_BASE --- Gets a list of the Names of the Objects in the Set. -- @param #SET_BASE self -- @return #SET_BASE self - function SET_BASE:GetSetNames() -- R2.3 + function SET_BASE:GetSetNames() -- R2.3 self:F2() local Names = {} @@ -185,11 +178,10 @@ do -- SET_BASE return Names end - --- Gets a list of the Objects in the Set. -- @param #SET_BASE self -- @return #SET_BASE self - function SET_BASE:GetSetObjects() -- R2.3 + function SET_BASE:GetSetObjects() -- R2.3 self:F2() local Objects = {} @@ -201,11 +193,10 @@ do -- SET_BASE return Objects end - --- Removes a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. -- @param #SET_BASE self -- @param #string ObjectName - -- @param NoTriggerEvent (optional) When `true`, the :Remove() method will not trigger a **Removed** event. + -- @param NoTriggerEvent (Optional) When `true`, the :Remove() method will not trigger a **Removed** event. function SET_BASE:Remove( ObjectName, NoTriggerEvent ) self:F2( { ObjectName = ObjectName } ) @@ -229,7 +220,6 @@ do -- SET_BASE end end - --- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using a given ObjectName as the index. -- @param #SET_BASE self -- @param #string ObjectName The name of the object. @@ -299,16 +289,16 @@ do -- SET_BASE -- @param #SET_BASE self -- @param Core.Set#SET_BASE SetB Set *B*. -- @return Core.Set#SET_BASE The union set, i.e. contains objects that are in set *A* **or** in set *B*. - function SET_BASE:GetSetUnion(SetB) + function SET_BASE:GetSetUnion( SetB ) - local union=SET_BASE:New() + local union = SET_BASE:New() - for _,ObjectA in pairs(self.Set) do - union:AddObject(ObjectA) + for _, ObjectA in pairs( self.Set ) do + union:AddObject( ObjectA ) end - for _,ObjectB in pairs(SetB.Set) do - union:AddObject(ObjectB) + for _, ObjectB in pairs( SetB.Set ) do + union:AddObject( ObjectB ) end return union @@ -318,6 +308,7 @@ do -- SET_BASE -- @param #SET_BASE self -- @param Core.Set#SET_BASE SetB Set other set, called *B*. -- @return Core.Set#SET_BASE A set of objects that is included in set *A* **and** in set *B*. + function SET_BASE:GetSetIntersection(SetB) local intersection=SET_BASE:New() @@ -337,7 +328,7 @@ do -- SET_BASE -- @param #SET_BASE self -- @param Core.Set#SET_BASE SetB Set other set, called *B*. -- @return Core.Set#SET_BASE The set of objects that are in set *B* but **not** in this set *A*. - function SET_BASE:GetSetComplement(SetB) + function SET_BASE:GetSetComplement( SetB ) local complement = self:GetSetUnion(SetB) local intersection = self:GetSetIntersection(SetB) @@ -354,11 +345,11 @@ do -- SET_BASE -- @param Core.Set#SET_BASE SetA First set. -- @param Core.Set#SET_BASE SetB Set to be merged into first set. -- @return Core.Set#SET_BASE The set of objects that are included in SetA and SetB. - function SET_BASE:CompareSets(SetA, SetB) + function SET_BASE:CompareSets( SetA, SetB ) - for _,ObjectB in pairs(SetB.Set) do - if SetA:IsIncludeObject(ObjectB) then - SetA:Add(ObjectB) + for _, ObjectB in pairs( SetB.Set ) do + if SetA:IsIncludeObject( ObjectB ) then + SetA:Add( ObjectB ) end end @@ -405,12 +396,11 @@ do -- SET_BASE -- @return Core.Base#BASE function SET_BASE:GetRandom() - local RandomItem = self.Set[self.Index[math.random(#self.Index)]] + local RandomItem = self.Set[self.Index[math.random( #self.Index )]] self:T3( { RandomItem } ) return RandomItem end - --- Retrieves the amount of objects in the @{Core.Set#SET_BASE} and derived classes. -- @param #SET_BASE self -- @return #number Count @@ -419,7 +409,6 @@ do -- SET_BASE return self.Index and #self.Index or 0 end - --- Copies the Filter criteria from a given Set (for rebuilding a new Set based on an existing Set). -- @param #SET_BASE self -- @param #SET_BASE BaseSet @@ -435,8 +424,6 @@ do -- SET_BASE return self end - - --- Define the SET iterator **"yield interval"** and the **"time interval"**. -- @param #SET_BASE self -- @param #number YieldInterval Sets the frequency when the iterator loop will yield after the number of objects processed. The default frequency is 10 objects processed. @@ -461,7 +448,7 @@ do -- SET_BASE return self end - --- Get the SET iterator **"limit"**. + --- Get the SET iterator **"limit"**. -- @param #SET_BASE self -- @return #number Defines how many objects are evaluated of the set as part of the Some iterators. function SET_BASE:GetSomeIteratorLimit() @@ -469,7 +456,6 @@ do -- SET_BASE return self.SomeIteratorLimit or self:Count() end - --- Filters for the defined collection. -- @param #SET_BASE self -- @return #SET_BASE self @@ -498,9 +484,8 @@ do -- SET_BASE end -- Follow alive players and clients - --self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) - --self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) - + -- self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) + -- self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) return self end @@ -508,7 +493,7 @@ do -- SET_BASE --- Starts the filtering of the Dead events for the collection. -- @param #SET_BASE self -- @return #SET_BASE self - function SET_BASE:FilterDeads() --R2.1 allow deads to be filtered to automatically handle deads in the collection. + function SET_BASE:FilterDeads() -- R2.1 allow deads to be filtered to automatically handle deads in the collection. self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) @@ -518,7 +503,7 @@ do -- SET_BASE --- Starts the filtering of the Crash events for the collection. -- @param #SET_BASE self -- @return #SET_BASE self - function SET_BASE:FilterCrashes() --R2.1 allow crashes to be filtered to automatically handle crashes in the collection. + function SET_BASE:FilterCrashes() -- R2.1 allow crashes to be filtered to automatically handle crashes in the collection. self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) @@ -563,12 +548,10 @@ do -- SET_BASE return NearestObject end - - ----- Private method that registers all alive players in the mission. ---- @param #SET_BASE self ---- @return #SET_BASE self - --function SET_BASE:_RegisterPlayers() + -- function SET_BASE:_RegisterPlayers() -- -- local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } -- for CoalitionId, CoalitionData in pairs( CoalitionsData ) do @@ -585,7 +568,7 @@ do -- SET_BASE -- end -- -- return self - --end + -- end --- Events @@ -600,7 +583,7 @@ do -- SET_BASE self:T3( ObjectName, Object ) if Object and self:IsIncludeObject( Object ) then self:Add( ObjectName, Object ) - --self:_EventOnPlayerEnterUnit( Event ) + -- self:_EventOnPlayerEnterUnit( Event ) end end end @@ -622,7 +605,7 @@ do -- SET_BASE --- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). -- @param #SET_BASE self -- @param Core.Event#EVENTDATA Event - --function SET_BASE:_EventOnPlayerEnterUnit( Event ) + -- function SET_BASE:_EventOnPlayerEnterUnit( Event ) -- self:F3( { Event } ) -- -- if Event.IniDCSUnit then @@ -633,12 +616,12 @@ do -- SET_BASE -- --self:_EventOnPlayerEnterUnit( Event ) -- end -- end - --end + -- end --- Handles the OnPlayerLeaveUnit event to clean the active players table. -- @param #SET_BASE self -- @param Core.Event#EVENTDATA Event - --function SET_BASE:_EventOnPlayerLeaveUnit( Event ) + -- function SET_BASE:_EventOnPlayerLeaveUnit( Event ) -- self:F3( { Event } ) -- -- local ObjectName = Event.IniDCSUnit @@ -659,7 +642,7 @@ do -- SET_BASE -- end -- end -- end - --end + -- end -- Iterators @@ -681,28 +664,28 @@ do -- SET_BASE local Count = 0 for ObjectID, ObjectData in pairs( Set ) do local Object = ObjectData - self:T3( Object ) - if Function then - if Function( unpack( FunctionArguments or {} ), Object ) == true then - IteratorFunction( Object, unpack( arg ) ) - end - else + self:T3( Object ) + if Function then + if Function( unpack( FunctionArguments or {} ), Object ) == true then IteratorFunction( Object, unpack( arg ) ) end - Count = Count + 1 - -- if Count % self.YieldInterval == 0 then - -- coroutine.yield( false ) - -- end + else + IteratorFunction( Object, unpack( arg ) ) + end + Count = Count + 1 + -- if Count % self.YieldInterval == 0 then + -- coroutine.yield( false ) + -- end end return true end - -- local co = coroutine.create( CoRoutine ) + -- local co = coroutine.create( CoRoutine ) local co = CoRoutine local function Schedule() - -- local status, res = coroutine.resume( co ) + -- local status, res = coroutine.resume( co ) local status, res = co() self:T3( { status, res } ) @@ -716,7 +699,7 @@ do -- SET_BASE return false end - --self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) + -- self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) Schedule() return self @@ -738,31 +721,31 @@ do -- SET_BASE local Count = 0 for ObjectID, ObjectData in pairs( Set ) do local Object = ObjectData - self:T3( Object ) - if Function then - if Function( unpack( FunctionArguments ), Object ) == true then - IteratorFunction( Object, unpack( arg ) ) - end - else + self:T3( Object ) + if Function then + if Function( unpack( FunctionArguments ), Object ) == true then IteratorFunction( Object, unpack( arg ) ) end - Count = Count + 1 - if Count >= Limit then - break - end - -- if Count % self.YieldInterval == 0 then - -- coroutine.yield( false ) - -- end + else + IteratorFunction( Object, unpack( arg ) ) + end + Count = Count + 1 + if Count >= Limit then + break + end + -- if Count % self.YieldInterval == 0 then + -- coroutine.yield( false ) + -- end end return true end - -- local co = coroutine.create( CoRoutine ) + -- local co = coroutine.create( CoRoutine ) local co = CoRoutine local function Schedule() - -- local status, res = coroutine.resume( co ) + -- local status, res = coroutine.resume( co ) local status, res = co() self:T3( { status, res } ) @@ -776,7 +759,7 @@ do -- SET_BASE return false end - --self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) + -- self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) Schedule() return self @@ -787,39 +770,38 @@ do -- SET_BASE ---- @param #SET_BASE self ---- @param #function IteratorFunction The function that will be called when there is an alive unit in the SET_BASE. The function needs to accept a UNIT parameter. ---- @return #SET_BASE self - --function SET_BASE:ForEachDCSUnitAlive( IteratorFunction, ... ) + -- function SET_BASE:ForEachDCSUnitAlive( IteratorFunction, ... ) -- self:F3( arg ) -- -- self:ForEach( IteratorFunction, arg, self.DCSUnitsAlive ) -- -- return self - --end + -- end -- ----- Iterate the SET_BASE and call an iterator function for each **alive** player, providing the Unit of the player and optional parameters. ---- @param #SET_BASE self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a UNIT parameter. ---- @return #SET_BASE self - --function SET_BASE:ForEachPlayer( IteratorFunction, ... ) + -- function SET_BASE:ForEachPlayer( IteratorFunction, ... ) -- self:F3( arg ) -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) -- -- return self - --end + -- end -- -- ----- Iterate the SET_BASE and call an iterator function for each client, providing the Client to the function and optional parameters. ---- @param #SET_BASE self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a CLIENT parameter. ---- @return #SET_BASE self - --function SET_BASE:ForEachClient( IteratorFunction, ... ) + -- function SET_BASE:ForEachClient( IteratorFunction, ... ) -- self:F3( arg ) -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- -- return self - --end - + -- end --- Decides whether to include the Object. -- @param #SET_BASE self @@ -835,7 +817,7 @@ do -- SET_BASE -- @param #SET_BASE self -- @param #table Object -- @return #SET_BASE self - function SET_BASE:IsInSet(ObjectName) + function SET_BASE:IsInSet( ObjectName ) self:F3( Object ) return true @@ -857,7 +839,7 @@ do -- SET_BASE --- Flushes the current SET_BASE contents in the log ... (for debugging reasons). -- @param #SET_BASE self - -- @param Core.Base#BASE MasterObject (optional) The master object as a reference. + -- @param Core.Base#BASE MasterObject (Optional) The master object as a reference. -- @return #string A string with the names of the objects. function SET_BASE:Flush( MasterObject ) self:F3() @@ -873,7 +855,6 @@ do -- SET_BASE end - do -- SET_GROUP --- @type SET_GROUP @@ -1006,7 +987,6 @@ do -- SET_GROUP }, } - --- Creates a new SET_GROUP object, building a set of groups belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_GROUP self -- @return #SET_GROUP @@ -1033,7 +1013,7 @@ do -- SET_GROUP -- Clean the Set before returning with only the alive Groups. for GroupName, GroupObject in pairs( self.Set ) do - local GroupObject=GroupObject --Wrapper.Group#GROUP + local GroupObject = GroupObject -- Wrapper.Group#GROUP if GroupObject then if GroupObject:IsAlive() then AliveSet:Add( GroupName, GroupObject ) @@ -1106,7 +1086,7 @@ do -- SET_GROUP -- @return Core.Set#SET_GROUP self function SET_GROUP:AddGroupsByName( AddGroupNames ) - local AddGroupNamesArray = ( type( AddGroupNames ) == "table" ) and AddGroupNames or { AddGroupNames } + local AddGroupNamesArray = (type( AddGroupNames ) == "table") and AddGroupNames or { AddGroupNames } for AddGroupID, AddGroupName in pairs( AddGroupNamesArray ) do self:Add( AddGroupName, GROUP:FindByName( AddGroupName ) ) @@ -1121,7 +1101,7 @@ do -- SET_GROUP -- @return Core.Set#SET_GROUP self function SET_GROUP:RemoveGroupsByName( RemoveGroupNames ) - local RemoveGroupNamesArray = ( type( RemoveGroupNames ) == "table" ) and RemoveGroupNames or { RemoveGroupNames } + local RemoveGroupNamesArray = (type( RemoveGroupNames ) == "table") and RemoveGroupNames or { RemoveGroupNames } for RemoveGroupID, RemoveGroupName in pairs( RemoveGroupNamesArray ) do self:Remove( RemoveGroupName ) @@ -1130,9 +1110,6 @@ do -- SET_GROUP return self end - - - --- Finds a Group based on the Group Name. -- @param #SET_GROUP self -- @param #string GroupName @@ -1150,7 +1127,7 @@ do -- SET_GROUP function SET_GROUP:FindNearestGroupFromPointVec2( PointVec2 ) self:F2( PointVec2 ) - local NearestGroup = nil --Wrapper.Group#GROUP + local NearestGroup = nil -- Wrapper.Group#GROUP local ClosestDistance = nil local Set = self:GetAliveSet() @@ -1171,6 +1148,29 @@ do -- SET_GROUP return NearestGroup end + --- Builds a set of groups in zones. + -- @param #SET_GROUP self + -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE + -- @return #SET_GROUP self + function SET_GROUP:FilterZones( Zones ) + if not self.Filter.Zones then + self.Filter.Zones = {} + end + local zones = {} + if Zones.ClassName and Zones.ClassName == "SET_ZONE" then + zones = Zones.Set + elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName) then + self:E( "***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!" ) + return self + else + zones = Zones + end + for _, Zone in pairs( zones ) do + local zonename = Zone:GetName() + self.Filter.Zones[zonename] = Zone + end + return self + end --- Builds a set of groups in zones. -- @param #SET_GROUP self @@ -1214,7 +1214,6 @@ do -- SET_GROUP return self end - --- Builds a set of groups out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_GROUP self @@ -1273,8 +1272,6 @@ do -- SET_GROUP return self end - - --- Builds a set of groups of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_GROUP self @@ -1293,9 +1290,8 @@ do -- SET_GROUP return self end - --- Builds a set of groups that contain the given string in their group name. - -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string. -- @param #SET_GROUP self -- @param #string Prefixes The string pattern(s) that needs to be contained in the group name. Can also be passed as a `#table` of strings. -- @return #SET_GROUP self @@ -1315,7 +1311,7 @@ do -- SET_GROUP --- Builds a set of groups that are only active. -- Only the groups that are active will be included within the set. -- @param #SET_GROUP self - -- @param #boolean Active (optional) Include only active groups to the set. + -- @param #boolean Active (Optional) Include only active groups to the set. -- Include inactive groups if you provide false. -- @return #SET_GROUP self -- @usage @@ -1333,12 +1329,11 @@ do -- SET_GROUP -- GroupSet = SET_GROUP:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() -- function SET_GROUP:FilterActive( Active ) - Active = Active or not ( Active == false ) + Active = Active or not (Active == false) self.Filter.Active = Active return self end - --- Starts the filtering. -- @param #SET_GROUP self -- @return #SET_GROUP self @@ -1352,8 +1347,6 @@ do -- SET_GROUP self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) end - - return self end @@ -1553,11 +1546,11 @@ do -- SET_GROUP -- else -- MESSAGE:New("Some or all SET's GROUP are outside zone !", 10):ToAll() -- end - function SET_GROUP:AllCompletelyInZone(Zone) - self:F2(Zone) + function SET_GROUP:AllCompletelyInZone( Zone ) + self:F2( Zone ) local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if not GroupData:IsCompletelyInZone(Zone) then + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if not GroupData:IsCompletelyInZone( Zone ) then return false end end @@ -1586,7 +1579,6 @@ do -- SET_GROUP return self end - --- Iterate the SET_GROUP and return true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE} -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -1601,11 +1593,11 @@ do -- SET_GROUP -- else -- MESSAGE:New("No GROUP is completely in zone !", 10):ToAll() -- end - function SET_GROUP:AnyCompletelyInZone(Zone) - self:F2(Zone) + function SET_GROUP:AnyCompletelyInZone( Zone ) + self:F2( Zone ) local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if GroupData:IsCompletelyInZone(Zone) then + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if GroupData:IsCompletelyInZone( Zone ) then return true end end @@ -1626,11 +1618,11 @@ do -- SET_GROUP -- else -- MESSAGE:New("No UNIT of any GROUP is in zone !", 10):ToAll() -- end - function SET_GROUP:AnyInZone(Zone) - self:F2(Zone) + function SET_GROUP:AnyInZone( Zone ) + self:F2( Zone ) local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if GroupData:IsPartlyInZone(Zone) or GroupData:IsCompletelyInZone(Zone) then + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if GroupData:IsPartlyInZone( Zone ) or GroupData:IsCompletelyInZone( Zone ) then return true end end @@ -1652,14 +1644,14 @@ do -- SET_GROUP -- else -- MESSAGE:New("No GROUP are in zone, or one (or more) GROUP is completely in it !", 10):ToAll() -- end - function SET_GROUP:AnyPartlyInZone(Zone) - self:F2(Zone) + function SET_GROUP:AnyPartlyInZone( Zone ) + self:F2( Zone ) local IsPartlyInZone = false local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if GroupData:IsCompletelyInZone(Zone) then + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if GroupData:IsCompletelyInZone( Zone ) then return false - elseif GroupData:IsPartlyInZone(Zone) then + elseif GroupData:IsPartlyInZone( Zone ) then IsPartlyInZone = true -- at least one GROUP is partly in zone end end @@ -1687,11 +1679,11 @@ do -- SET_GROUP -- else -- MESSAGE:New("No UNIT of any GROUP is in zone !", 10):ToAll() -- end - function SET_GROUP:NoneInZone(Zone) - self:F2(Zone) + function SET_GROUP:NoneInZone( Zone ) + self:F2( Zone ) local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if not GroupData:IsNotInZone(Zone) then -- If the GROUP is in Zone in any way + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if not GroupData:IsNotInZone( Zone ) then -- If the GROUP is in Zone in any way return false end end @@ -1710,12 +1702,12 @@ do -- SET_GROUP -- MySetGroup:AddGroupsByName({"Group1", "Group2"}) -- -- MESSAGE:New("There are " .. MySetGroup:CountInZone(MyZone) .. " GROUPs in the Zone !", 10):ToAll() - function SET_GROUP:CountInZone(Zone) - self:F2(Zone) + function SET_GROUP:CountInZone( Zone ) + self:F2( Zone ) local Count = 0 local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - if GroupData:IsCompletelyInZone(Zone) then + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if GroupData:IsCompletelyInZone( Zone ) then Count = Count + 1 end end @@ -1732,12 +1724,12 @@ do -- SET_GROUP -- MySetGroup:AddGroupsByName({"Group1", "Group2"}) -- -- MESSAGE:New("There are " .. MySetGroup:CountUnitInZone(MyZone) .. " UNITs in the Zone !", 10):ToAll() - function SET_GROUP:CountUnitInZone(Zone) - self:F2(Zone) + function SET_GROUP:CountUnitInZone( Zone ) + self:F2( Zone ) local Count = 0 local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP - Count = Count + GroupData:CountInZone(Zone) + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + Count = Count + GroupData:CountInZone( Zone ) end return Count end @@ -1752,50 +1744,49 @@ do -- SET_GROUP local Set = self:GetSet() - for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP if GroupData and GroupData:IsAlive() then CountG = CountG + 1 - --Count Units. - for _,_unit in pairs(GroupData:GetUnits()) do - local unit=_unit --Wrapper.Unit#UNIT + -- Count Units. + for _, _unit in pairs( GroupData:GetUnits() ) do + local unit = _unit -- Wrapper.Unit#UNIT if unit and unit:IsAlive() then - CountU=CountU+1 + CountU = CountU + 1 end end end end - return CountG,CountU + return CountG, CountU end ----- Iterate the SET_GROUP and call an iterator function for each **alive** player, providing the Group of the player and optional parameters. ---- @param #SET_GROUP self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a GROUP parameter. ---- @return #SET_GROUP self - --function SET_GROUP:ForEachPlayer( IteratorFunction, ... ) + -- function SET_GROUP:ForEachPlayer( IteratorFunction, ... ) -- self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) -- -- return self - --end + -- end -- -- ----- Iterate the SET_GROUP and call an iterator function for each client, providing the Client to the function and optional parameters. ---- @param #SET_GROUP self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a CLIENT parameter. ---- @return #SET_GROUP self - --function SET_GROUP:ForEachClient( IteratorFunction, ... ) + -- function SET_GROUP:ForEachClient( IteratorFunction, ... ) -- self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- -- return self - --end - + -- end --- -- @param #SET_GROUP self @@ -1808,7 +1799,7 @@ do -- SET_GROUP if self.Filter.Active ~= nil then local MGroupActive = false self:F( { Active = self.Filter.Active } ) - if self.Filter.Active == false or ( self.Filter.Active == true and MGroup:IsActive() == true ) then + if self.Filter.Active == false or (self.Filter.Active == true and MGroup:IsActive() == true) then MGroupActive = true end MGroupInclude = MGroupInclude and MGroupActive @@ -1851,7 +1842,7 @@ do -- SET_GROUP local MGroupPrefix = false for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do self:T3( { "Prefix:", string.find( MGroup:GetName(), GroupPrefix, 1 ), GroupPrefix } ) - if string.find( MGroup:GetName(), GroupPrefix:gsub ("-", "%%-"), 1 ) then + if string.find( MGroup:GetName(), GroupPrefix:gsub( "-", "%%-" ), 1 ) then MGroupPrefix = true end end @@ -1873,7 +1864,6 @@ do -- SET_GROUP return MGroupInclude end - --- Iterate the SET_GROUP and set for each unit the default cargo bay weight limit. -- Because within a group, the type of carriers can differ, each cargo bay weight limit is set on @{Wrapper.Unit} level. -- @param #SET_GROUP self @@ -1885,7 +1875,7 @@ do -- SET_GROUP local Set = self:GetSet() for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP for UnitName, UnitData in pairs( GroupData:GetUnits() ) do - --local UnitData = UnitData -- Wrapper.Unit#UNIT + -- local UnitData = UnitData -- Wrapper.Unit#UNIT UnitData:SetCargoBayWeightLimit() end end @@ -1893,7 +1883,6 @@ do -- SET_GROUP end - do -- SET_UNIT --- @type SET_UNIT @@ -2030,7 +2019,6 @@ do -- SET_UNIT }, } - --- Get the first unit from the set. -- @function [parent=#SET_UNIT] GetFirst -- @param #SET_UNIT self @@ -2067,14 +2055,13 @@ do -- SET_UNIT 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 } + local AddUnitNamesArray = (type( AddUnitNames ) == "table") and AddUnitNames or { AddUnitNames } self:T( AddUnitNamesArray ) for AddUnitID, AddUnitName in pairs( AddUnitNamesArray ) do @@ -2090,7 +2077,7 @@ do -- SET_UNIT -- @return Core.Set#SET_UNIT self function SET_UNIT:RemoveUnitsByName( RemoveUnitNames ) - local RemoveUnitNamesArray = ( type( RemoveUnitNames ) == "table" ) and RemoveUnitNames or { RemoveUnitNames } + local RemoveUnitNamesArray = (type( RemoveUnitNames ) == "table") and RemoveUnitNames or { RemoveUnitNames } for RemoveUnitID, RemoveUnitName in pairs( RemoveUnitNamesArray ) do self:Remove( RemoveUnitName ) @@ -2099,7 +2086,6 @@ do -- SET_UNIT return self end - --- Finds a Unit based on the Unit Name. -- @param #SET_UNIT self -- @param #string UnitName @@ -2110,8 +2096,6 @@ do -- SET_UNIT return UnitFound end - - --- Builds a set of units of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_UNIT self @@ -2129,7 +2113,6 @@ do -- SET_UNIT return self end - --- Builds a set of units out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_UNIT self @@ -2148,7 +2131,6 @@ do -- SET_UNIT 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 @@ -2167,7 +2149,6 @@ do -- SET_UNIT return self end - --- Builds a set of units of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_UNIT self @@ -2186,7 +2167,6 @@ do -- SET_UNIT return self end - --- Builds a set of UNITs that contain a given string in their unit name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all units that **contain** the string. -- @param #SET_UNIT self @@ -2232,7 +2212,7 @@ do -- SET_UNIT --- Builds a set of units that are only active. -- Only the units that are active will be included within the set. -- @param #SET_UNIT self - -- @param #boolean Active (optional) Include only active units to the set. + -- @param #boolean Active (Optional) Include only active units to the set. -- Include inactive units if you provide false. -- @return #SET_UNIT self -- @usage @@ -2250,7 +2230,7 @@ do -- SET_UNIT -- UnitSet = SET_UNIT:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() -- function SET_UNIT:FilterActive( Active ) - Active = Active or not ( Active == false ) + Active = Active or not (Active == false) self.Filter.Active = Active return self end @@ -2289,7 +2269,7 @@ do -- SET_UNIT local Set = self:GetSet() local CountU = 0 - for UnitID, UnitData in pairs(Set) do -- For each GROUP in SET_GROUP + for UnitID, UnitData in pairs( Set ) do -- For each GROUP in SET_GROUP if UnitData and UnitData:IsAlive() then CountU = CountU + 1 end @@ -2315,8 +2295,6 @@ do -- SET_UNIT 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 @@ -2345,11 +2323,9 @@ do -- SET_UNIT function SET_UNIT:FindInDatabase( Event ) self:F2( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) - return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] end - do -- Is Zone methods --- Check if minimal one element of the SET_UNIT is in the Zone. @@ -2362,7 +2338,7 @@ do -- SET_UNIT local function EvaluateZone( ZoneUnit ) - local ZoneUnitName = ZoneUnit:GetName() + local ZoneUnitName = ZoneUnit:GetName() self:F( { ZoneUnitName = ZoneUnitName } ) if self:FindUnit( ZoneUnitName ) then IsPartiallyInZone = true @@ -2378,7 +2354,6 @@ do -- SET_UNIT return IsPartiallyInZone end - --- 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. @@ -2389,7 +2364,7 @@ do -- SET_UNIT local function EvaluateZone( ZoneUnit ) - local ZoneUnitName = ZoneUnit:GetName() + local ZoneUnitName = ZoneUnit:GetName() if self:FindUnit( ZoneUnitName ) then IsNotInZone = false return false @@ -2418,16 +2393,12 @@ do -- SET_UNIT return self end - --- Get the SET of the SET_UNIT **sorted per Threat Level**. -- -- @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). -- @return #SET_UNIT self - -- @usage - -- - -- function SET_UNIT:GetSetPerThreatLevel( FromThreatLevel, ToThreatLevel ) self:F2( arg ) @@ -2444,12 +2415,10 @@ do -- SET_UNIT self:F( { ThreatLevel = ThreatLevel, ThreatLevelSet = ThreatLevelSet[ThreatLevel].Set } ) end - local OrderedPerThreatLevelSet = {} local ThreatLevelIncrement = FromThreatLevel <= ToThreatLevel and 1 or -1 - for ThreatLevel = FromThreatLevel, ToThreatLevel, ThreatLevelIncrement do self:F( { ThreatLevel = ThreatLevel } ) local ThreatLevelItem = ThreatLevelSet[ThreatLevel] @@ -2482,7 +2451,7 @@ do -- SET_UNIT -- end -- ) -- - function SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) --R2.1 Threat Level implementation + function SET_UNIT:ForEachUnitPerThreatLevel( FromThreatLevel, ToThreatLevel, IteratorFunction, ... ) -- R2.1 Threat Level implementation self:F2( arg ) local ThreatLevelSet = {} @@ -2512,8 +2481,6 @@ do -- SET_UNIT 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. @@ -2581,13 +2548,12 @@ do -- SET_UNIT end for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID + 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 @@ -2598,7 +2564,7 @@ do -- SET_UNIT local UnitTypes = self:GetUnitTypes() for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID + MT[#MT + 1] = UnitType .. " of " .. UnitTypeID end return table.concat( MT, ", " ) @@ -2628,9 +2594,9 @@ do -- SET_UNIT return UnitThreatLevels end - --- Calculate the maxium A2G threat level of the SET_UNIT. + --- Calculate the maximum A2G threat level of the SET_UNIT. -- @param #SET_UNIT self - -- @return #number The maximum threatlevel + -- @return #number The maximum threat level function SET_UNIT:CalculateThreatLevelA2G() local MaxThreatLevelA2G = 0 @@ -2671,27 +2637,27 @@ do -- SET_UNIT 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 + 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 + if Velocity ~= 0 then + MaxVelocity = (MaxVelocity < Velocity) and Velocity or MaxVelocity local Heading = Coordinate:GetHeading() - AvgHeading = AvgHeading and ( AvgHeading + Heading ) or Heading + AvgHeading = AvgHeading and (AvgHeading + Heading) or Heading MovingCount = MovingCount + 1 end end - AvgHeading = AvgHeading and ( AvgHeading / MovingCount ) + AvgHeading = AvgHeading and (AvgHeading / MovingCount) - Coordinate.x = ( x2 - x1 ) / 2 + x1 - Coordinate.y = ( y2 - y1 ) / 2 + y1 - Coordinate.z = ( z2 - z1 ) / 2 + z1 + Coordinate.x = (x2 - x1) / 2 + x1 + Coordinate.y = (y2 - y1) / 2 + y1 + Coordinate.z = (z2 - z1) / 2 + z1 Coordinate:SetHeading( AvgHeading ) Coordinate:SetVelocity( MaxVelocity ) @@ -2715,8 +2681,8 @@ do -- SET_UNIT local Coordinate = Unit:GetCoordinate() local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then - MaxVelocity = ( MaxVelocity < Velocity ) and Velocity or MaxVelocity + if Velocity ~= 0 then + MaxVelocity = (MaxVelocity < Velocity) and Velocity or MaxVelocity end end @@ -2739,12 +2705,12 @@ do -- SET_UNIT local Coordinate = Unit:GetCoordinate() local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then + if Velocity ~= 0 then local Heading = Coordinate:GetHeading() if HeadingSet == nil then HeadingSet = Heading else - local HeadingDiff = ( HeadingSet - Heading + 180 + 360 ) % 360 - 180 + local HeadingDiff = (HeadingSet - Heading + 180 + 360) % 360 - 180 HeadingDiff = math.abs( HeadingDiff ) if HeadingDiff > 5 then HeadingSet = nil @@ -2758,8 +2724,6 @@ do -- SET_UNIT end - - --- Returns if the @{Set} has targets having a radar (of a given type). -- @param #SET_UNIT self -- @param DCS#Unit.RadarType RadarType @@ -2768,7 +2732,7 @@ do -- SET_UNIT self:F2( RadarType ) local RadarCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do + for UnitID, UnitData in pairs( self:GetSet() ) do local UnitSensorTest = UnitData -- Wrapper.Unit#UNIT local HasSensors if RadarType then @@ -2776,7 +2740,7 @@ do -- SET_UNIT else HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR ) end - self:T3(HasSensors) + self:T3( HasSensors ) if HasSensors then RadarCount = RadarCount + 1 end @@ -2792,14 +2756,14 @@ do -- SET_UNIT self:F2() local SEADCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do + 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) + self:T3( HasSEAD ) if HasSEAD then SEADCount = SEADCount + 1 end @@ -2816,7 +2780,7 @@ do -- SET_UNIT self:F2() local GroundUnitCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do + for UnitID, UnitData in pairs( self:GetSet() ) do local UnitTest = UnitData -- Wrapper.Unit#UNIT if UnitTest:IsGround() then GroundUnitCount = GroundUnitCount + 1 @@ -2850,7 +2814,7 @@ do -- SET_UNIT self:F2() local FriendlyUnitCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do + for UnitID, UnitData in pairs( self:GetSet() ) do local UnitTest = UnitData -- Wrapper.Unit#UNIT if UnitTest:IsFriendly( FriendlyCoalition ) then FriendlyUnitCount = FriendlyUnitCount + 1 @@ -2866,27 +2830,26 @@ do -- SET_UNIT ---- @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, ... ) + -- function SET_UNIT:ForEachPlayer( IteratorFunction, ... ) -- self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) -- -- return self - --end + -- end -- -- ----- Iterate the SET_UNIT and call an iterator 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, ... ) + -- function SET_UNIT:ForEachClient( IteratorFunction, ... ) -- self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- -- return self - --end - + -- end --- -- @param #SET_UNIT self @@ -2903,7 +2866,7 @@ do -- SET_UNIT if self.Filter.Active ~= nil then local MUnitActive = false - if self.Filter.Active == false or ( self.Filter.Active == true and MUnit:IsActive() == true ) then + if self.Filter.Active == false or (self.Filter.Active == true and MUnit:IsActive() == true) then MUnitActive = true end MUnitInclude = MUnitInclude and MUnitActive @@ -3003,10 +2966,9 @@ do -- SET_UNIT return MUnitInclude end - --- Retrieve the type names of the @{Wrapper.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. + -- @param #string Delimiter (Optional) The delimiter, which is default a comma. -- @return #string The types of the @{Wrapper.Unit}s delimited. function SET_UNIT:GetTypeNames( Delimiter ) @@ -3037,16 +2999,13 @@ do -- SET_UNIT function SET_UNIT:SetCargoBayWeightLimit() local Set = self:GetSet() for UnitID, UnitData in pairs( Set ) do -- For each UNIT in SET_UNIT - --local UnitData = UnitData -- Wrapper.Unit#UNIT + -- local UnitData = UnitData -- Wrapper.Unit#UNIT UnitData:SetCargoBayWeightLimit() end end - - end - do -- SET_STATIC --- @type SET_STATIC @@ -3165,14 +3124,13 @@ do -- SET_STATIC 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 } + local AddStaticNamesArray = (type( AddStaticNames ) == "table") and AddStaticNames or { AddStaticNames } self:T( AddStaticNamesArray ) for AddStaticID, AddStaticName in pairs( AddStaticNamesArray ) do @@ -3188,7 +3146,7 @@ do -- SET_STATIC -- @return self function SET_STATIC:RemoveStaticsByName( RemoveStaticNames ) - local RemoveStaticNamesArray = ( type( RemoveStaticNames ) == "table" ) and RemoveStaticNames or { RemoveStaticNames } + local RemoveStaticNamesArray = (type( RemoveStaticNames ) == "table") and RemoveStaticNames or { RemoveStaticNames } for RemoveStaticID, RemoveStaticName in pairs( RemoveStaticNamesArray ) do self:Remove( RemoveStaticName ) @@ -3197,7 +3155,6 @@ do -- SET_STATIC return self end - --- Finds a Static based on the Static Name. -- @param #SET_STATIC self -- @param #string StaticName @@ -3208,8 +3165,6 @@ do -- SET_STATIC return StaticFound end - - --- Builds a set of units of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_STATIC self @@ -3271,7 +3226,6 @@ do -- SET_STATIC 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 @@ -3290,7 +3244,6 @@ do -- SET_STATIC return self end - --- Builds a set of units of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_STATIC self @@ -3309,7 +3262,6 @@ do -- SET_STATIC return self end - --- Builds a set of STATICs that contain the given string in their name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all statics that **contain** the string. -- @param #SET_STATIC self @@ -3328,7 +3280,6 @@ do -- SET_STATIC return self end - --- Starts the filtering. -- @param #SET_STATIC self -- @return #SET_STATIC self @@ -3352,7 +3303,7 @@ do -- SET_STATIC local Set = self:GetSet() local CountU = 0 - for UnitID, UnitData in pairs(Set) do + for UnitID, UnitData in pairs( Set ) do if UnitData and UnitData:IsAlive() then CountU = CountU + 1 end @@ -3390,11 +3341,9 @@ do -- SET_STATIC function SET_STATIC:FindInDatabase( Event ) self:F2( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) - return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] end - do -- Is Zone methods --- Check if minimal one element of the SET_STATIC is in the Zone. @@ -3407,7 +3356,7 @@ do -- SET_STATIC local function EvaluateZone( ZoneStatic ) - local ZoneStaticName = ZoneStatic:GetName() + local ZoneStaticName = ZoneStatic:GetName() if self:FindStatic( ZoneStaticName ) then IsPartiallyInZone = true return false @@ -3419,7 +3368,6 @@ do -- SET_STATIC 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. @@ -3430,7 +3378,7 @@ do -- SET_STATIC local function EvaluateZone( ZoneStatic ) - local ZoneStaticName = ZoneStatic:GetName() + local ZoneStaticName = ZoneStatic:GetName() if self:FindStatic( ZoneStaticName ) then IsNotInZone = false return false @@ -3444,7 +3392,6 @@ do -- SET_STATIC 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. @@ -3457,7 +3404,6 @@ do -- SET_STATIC return self end - end @@ -3473,7 +3419,6 @@ do -- SET_STATIC 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. @@ -3541,13 +3486,12 @@ do -- SET_STATIC end for StaticTypeID, StaticType in pairs( StaticTypes ) do - MT[#MT+1] = StaticType .. " of " .. StaticTypeID + 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 @@ -3558,7 +3502,7 @@ do -- SET_STATIC local StaticTypes = self:GetStaticTypes() for StaticTypeID, StaticType in pairs( StaticTypes ) do - MT[#MT+1] = StaticType .. " of " .. StaticTypeID + MT[#MT + 1] = StaticType .. " of " .. StaticTypeID end return table.concat( MT, ", " ) @@ -3586,27 +3530,27 @@ do -- SET_STATIC 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 + 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 + if Velocity ~= 0 then + MaxVelocity = (MaxVelocity < Velocity) and Velocity or MaxVelocity local Heading = Coordinate:GetHeading() - AvgHeading = AvgHeading and ( AvgHeading + Heading ) or Heading + AvgHeading = AvgHeading and (AvgHeading + Heading) or Heading MovingCount = MovingCount + 1 end end - AvgHeading = AvgHeading and ( AvgHeading / MovingCount ) + AvgHeading = AvgHeading and (AvgHeading / MovingCount) - Coordinate.x = ( x2 - x1 ) / 2 + x1 - Coordinate.y = ( y2 - y1 ) / 2 + y1 - Coordinate.z = ( z2 - z1 ) / 2 + z1 + Coordinate.x = (x2 - x1) / 2 + x1 + Coordinate.y = (y2 - y1) / 2 + y1 + Coordinate.z = (z2 - z1) / 2 + z1 Coordinate:SetHeading( AvgHeading ) Coordinate:SetVelocity( MaxVelocity ) @@ -3638,12 +3582,12 @@ do -- SET_STATIC local Coordinate = Static:GetCoordinate() local Velocity = Coordinate:GetVelocity() - if Velocity ~= 0 then + if Velocity ~= 0 then local Heading = Coordinate:GetHeading() if HeadingSet == nil then HeadingSet = Heading else - local HeadingDiff = ( HeadingSet - Heading + 180 + 360 ) % 360 - 180 + local HeadingDiff = (HeadingSet - Heading + 180 + 360) % 360 - 180 HeadingDiff = math.abs( HeadingDiff ) if HeadingDiff > 5 then HeadingSet = nil @@ -3657,24 +3601,24 @@ do -- SET_STATIC end - --- Calculate the maxium A2G threat level of the SET_STATIC. + --- Calculate the maximum A2G threat level of the SET_STATIC. -- @param #SET_STATIC self -- @return #number The maximum threatlevel function SET_STATIC:CalculateThreatLevelA2G() - local MaxThreatLevelA2G = 0 - local MaxThreatText = "" - for StaticName, StaticData in pairs( self:GetSet() ) do - local ThreatStatic = StaticData -- Wrapper.Static#STATIC - local ThreatLevelA2G, ThreatText = ThreatStatic:GetThreatLevel() - if ThreatLevelA2G > MaxThreatLevelA2G then - MaxThreatLevelA2G = ThreatLevelA2G - MaxThreatText = ThreatText + local MaxThreatLevelA2G = 0 + local MaxThreatText = "" + for StaticName, StaticData in pairs( self:GetSet() ) do + local ThreatStatic = StaticData -- Wrapper.Static#STATIC + local ThreatLevelA2G, ThreatText = ThreatStatic:GetThreatLevel() + if ThreatLevelA2G > MaxThreatLevelA2G then + MaxThreatLevelA2G = ThreatLevelA2G + MaxThreatText = ThreatText + end end - end - self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } ) - return MaxThreatLevelA2G, MaxThreatText + self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } ) + return MaxThreatLevelA2G, MaxThreatText end @@ -3756,10 +3700,9 @@ do -- SET_STATIC 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. + -- @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 ) @@ -3783,15 +3726,11 @@ do -- SET_STATIC end - do -- SET_CLIENT - --- @type SET_CLIENT -- @extends Core.Set#SET_BASE - - --- Mission designers can use the @{Core.Set#SET_CLIENT} class to build sets of units belonging to certain: -- -- * Coalitions @@ -3866,7 +3805,6 @@ do -- SET_CLIENT }, } - --- Creates a new SET_CLIENT object, building a set of clients belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_CLIENT self -- @return #SET_CLIENT @@ -3888,7 +3826,7 @@ do -- SET_CLIENT -- @return self function SET_CLIENT:AddClientsByName( AddClientNames ) - local AddClientNamesArray = ( type( AddClientNames ) == "table" ) and AddClientNames or { AddClientNames } + local AddClientNamesArray = (type( AddClientNames ) == "table") and AddClientNames or { AddClientNames } for AddClientID, AddClientName in pairs( AddClientNamesArray ) do self:Add( AddClientName, CLIENT:FindByName( AddClientName ) ) @@ -3903,7 +3841,7 @@ do -- SET_CLIENT -- @return self function SET_CLIENT:RemoveClientsByName( RemoveClientNames ) - local RemoveClientNamesArray = ( type( RemoveClientNames ) == "table" ) and RemoveClientNames or { RemoveClientNames } + local RemoveClientNamesArray = (type( RemoveClientNames ) == "table") and RemoveClientNames or { RemoveClientNames } for RemoveClientID, RemoveClientName in pairs( RemoveClientNamesArray ) do self:Remove( RemoveClientName.ClientName ) @@ -3912,7 +3850,6 @@ do -- SET_CLIENT return self end - --- Finds a Client based on the Client Name. -- @param #SET_CLIENT self -- @param #string ClientName @@ -3923,8 +3860,6 @@ do -- SET_CLIENT return ClientFound end - - --- Builds a set of clients of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_CLIENT self @@ -3943,7 +3878,6 @@ do -- SET_CLIENT return self end - --- Builds a set of clients out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_CLIENT self @@ -3962,7 +3896,6 @@ do -- SET_CLIENT return self end - --- Builds a set of clients of defined client types. -- Possible current types are those types known within DCS world. -- @param #SET_CLIENT self @@ -3981,7 +3914,6 @@ do -- SET_CLIENT return self end - --- Builds a set of clients of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_CLIENT self @@ -4000,7 +3932,6 @@ do -- SET_CLIENT return self end - --- Builds a set of CLIENTs that contain the given string in their unit/pilot name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all clients that **contain** the string. -- @param #SET_CLIENT self @@ -4022,7 +3953,7 @@ do -- SET_CLIENT --- Builds a set of clients that are only active. -- Only the clients that are active will be included within the set. -- @param #SET_CLIENT self - -- @param #boolean Active (optional) Include only active clients to the set. + -- @param #boolean Active (Optional) Include only active clients to the set. -- Include inactive clients if you provide false. -- @return #SET_CLIENT self -- @usage @@ -4040,7 +3971,7 @@ do -- SET_CLIENT -- ClientSet = SET_CLIENT:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() -- function SET_CLIENT:FilterActive( Active ) - Active = Active or not ( Active == false ) + Active = Active or not (Active == false) self.Filter.Active = Active return self end @@ -4163,7 +4094,7 @@ do -- SET_CLIENT return self end - + --- Iterate the SET_CLIENT and count alive units. -- @param #SET_CLIENT self -- @return #number count @@ -4172,7 +4103,7 @@ do -- SET_CLIENT local Set = self:GetSet() local CountU = 0 - for UnitID, UnitData in pairs(Set) do -- For each GROUP in SET_GROUP + for UnitID, UnitData in pairs( Set ) do -- For each GROUP in SET_GROUP if UnitData and UnitData:IsAlive() then CountU = CountU + 1 end @@ -4216,7 +4147,7 @@ do -- SET_CLIENT if self.Filter.Active ~= nil then local MClientActive = false - if self.Filter.Active == false or ( self.Filter.Active == true and MClient:IsActive() == true ) then + if self.Filter.Active == false or (self.Filter.Active == true and MClient:IsActive() == true) then MClientActive = true end MClientInclude = MClientInclude and MClientActive @@ -4263,7 +4194,7 @@ do -- SET_CLIENT if self.Filter.Countries then local MClientCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do - local ClientCountryID = _DATABASE:GetCountryFromClientTemplate(MClientName) + 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 @@ -4304,14 +4235,11 @@ do -- SET_CLIENT end - do -- SET_PLAYER --- @type SET_PLAYER -- @extends Core.Set#SET_BASE - - --- Mission designers can use the @{Core.Set#SET_PLAYER} class to build sets of units belonging to alive players: -- -- ## SET_PLAYER constructor @@ -4376,7 +4304,6 @@ do -- SET_PLAYER }, } - --- Creates a new SET_PLAYER object, building a set of clients belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_PLAYER self -- @return #SET_PLAYER @@ -4396,7 +4323,7 @@ do -- SET_PLAYER -- @return self function SET_PLAYER:AddClientsByName( AddClientNames ) - local AddClientNamesArray = ( type( AddClientNames ) == "table" ) and AddClientNames or { AddClientNames } + local AddClientNamesArray = (type( AddClientNames ) == "table") and AddClientNames or { AddClientNames } for AddClientID, AddClientName in pairs( AddClientNamesArray ) do self:Add( AddClientName, CLIENT:FindByName( AddClientName ) ) @@ -4411,7 +4338,7 @@ do -- SET_PLAYER -- @return self function SET_PLAYER:RemoveClientsByName( RemoveClientNames ) - local RemoveClientNamesArray = ( type( RemoveClientNames ) == "table" ) and RemoveClientNames or { RemoveClientNames } + local RemoveClientNamesArray = (type( RemoveClientNames ) == "table") and RemoveClientNames or { RemoveClientNames } for RemoveClientID, RemoveClientName in pairs( RemoveClientNamesArray ) do self:Remove( RemoveClientName.ClientName ) @@ -4420,7 +4347,6 @@ do -- SET_PLAYER return self end - --- Finds a Client based on the Player Name. -- @param #SET_PLAYER self -- @param #string PlayerName @@ -4431,8 +4357,6 @@ do -- SET_PLAYER return ClientFound end - - --- Builds a set of clients of coalitions joined by specific players. -- Possible current coalitions are red, blue and neutral. -- @param #SET_PLAYER self @@ -4494,7 +4418,6 @@ do -- SET_PLAYER return self end - --- Builds a set of clients of defined client types joined by players. -- Possible current types are those types known within DCS world. -- @param #SET_PLAYER self @@ -4513,7 +4436,6 @@ do -- SET_PLAYER return self end - --- Builds a set of clients of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_PLAYER self @@ -4532,7 +4454,6 @@ do -- SET_PLAYER return self end - --- Builds a set of PLAYERs that contain the given string in their unit/pilot name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all player clients that **contain** the string. -- @param #SET_PLAYER self @@ -4551,9 +4472,6 @@ do -- SET_PLAYER return self end - - - --- Starts the filtering. -- @param #SET_PLAYER self -- @return #SET_PLAYER self @@ -4702,7 +4620,7 @@ do -- SET_PLAYER if self.Filter.Countries then local MClientCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do - local ClientCountryID = _DATABASE:GetCountryFromClientTemplate(MClientName) + 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 @@ -4743,7 +4661,6 @@ do -- SET_PLAYER end - do -- SET_AIRBASE --- @type SET_AIRBASE @@ -4805,7 +4722,6 @@ do -- SET_AIRBASE }, } - --- Creates a new SET_AIRBASE object, building a set of airbases belonging to a coalitions and categories. -- @param #SET_AIRBASE self -- @return #SET_AIRBASE self @@ -4836,7 +4752,7 @@ do -- SET_AIRBASE -- @return self function SET_AIRBASE:AddAirbasesByName( AddAirbaseNames ) - local AddAirbaseNamesArray = ( type( AddAirbaseNames ) == "table" ) and AddAirbaseNames or { AddAirbaseNames } + local AddAirbaseNamesArray = (type( AddAirbaseNames ) == "table") and AddAirbaseNames or { AddAirbaseNames } for AddAirbaseID, AddAirbaseName in pairs( AddAirbaseNamesArray ) do self:Add( AddAirbaseName, AIRBASE:FindByName( AddAirbaseName ) ) @@ -4851,7 +4767,7 @@ do -- SET_AIRBASE -- @return self function SET_AIRBASE:RemoveAirbasesByName( RemoveAirbaseNames ) - local RemoveAirbaseNamesArray = ( type( RemoveAirbaseNames ) == "table" ) and RemoveAirbaseNames or { RemoveAirbaseNames } + local RemoveAirbaseNamesArray = (type( RemoveAirbaseNames ) == "table") and RemoveAirbaseNames or { RemoveAirbaseNames } for RemoveAirbaseID, RemoveAirbaseName in pairs( RemoveAirbaseNamesArray ) do self:Remove( RemoveAirbaseName ) @@ -4860,7 +4776,6 @@ do -- SET_AIRBASE return self end - --- Finds a Airbase based on the Airbase Name. -- @param #SET_AIRBASE self -- @param #string AirbaseName @@ -4871,7 +4786,6 @@ do -- SET_AIRBASE return AirbaseFound end - --- Finds an Airbase in range of a coordinate. -- @param #SET_AIRBASE self -- @param Core.Point#COORDINATE Coordinate @@ -4886,7 +4800,7 @@ do -- SET_AIRBASE local AirbaseCoordinate = AirbaseObject:GetCoordinate() local Distance = Coordinate:Get2DDistance( AirbaseCoordinate ) - self:F({Distance=Distance}) + self:F( { Distance = Distance } ) if Distance <= Range then AirbaseFound = AirbaseObject @@ -4898,7 +4812,6 @@ do -- SET_AIRBASE return AirbaseFound end - --- Finds a random Airbase in the set. -- @param #SET_AIRBASE self -- @return Wrapper.Airbase#AIRBASE The found Airbase. @@ -4910,8 +4823,6 @@ do -- SET_AIRBASE return RandomAirbase end - - --- Builds a set of airbases of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_AIRBASE self @@ -4930,7 +4841,6 @@ do -- SET_AIRBASE return self end - --- Builds a set of airbases out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_AIRBASE self @@ -4957,8 +4867,8 @@ do -- SET_AIRBASE if _DATABASE then -- We use the BaseCaptured event, which is generated by DCS when a base got captured. - self:HandleEvent(EVENTS.BaseCaptured) - self:HandleEvent(EVENTS.Dead) + self:HandleEvent( EVENTS.BaseCaptured ) + self:HandleEvent( EVENTS.Dead ) -- We initialize the first set. for ObjectName, Object in pairs( self.Database ) do @@ -4976,7 +4886,7 @@ do -- SET_AIRBASE --- Base capturing event. -- @param #SET_AIRBASE self -- @param Core.Event#EVENT EventData - function SET_AIRBASE:OnEventBaseCaptured(EventData) + function SET_AIRBASE:OnEventBaseCaptured( EventData ) -- When a base got captured, we reevaluate the set. for ObjectName, Object in pairs( self.Database ) do @@ -4994,17 +4904,16 @@ do -- SET_AIRBASE --- Dead event. -- @param #SET_AIRBASE self -- @param Core.Event#EVENT EventData - function SET_AIRBASE:OnEventDead(EventData) + function SET_AIRBASE:OnEventDead( EventData ) - local airbaseName, airbase=self:FindInDatabase(EventData) + local airbaseName, airbase = self:FindInDatabase( EventData ) if airbase and (airbase:IsShip() or airbase:IsHelipad()) then - self:RemoveAirbasesByName(airbaseName) + self:RemoveAirbasesByName( airbaseName ) end 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_AIRBASE self @@ -5050,8 +4959,6 @@ do -- SET_AIRBASE return NearestAirbase end - - --- -- @param #SET_AIRBASE self -- @param Wrapper.Airbase#AIRBASE MAirbase @@ -5097,7 +5004,6 @@ do -- SET_AIRBASE end - do -- SET_CARGO --- @type SET_CARGO @@ -5143,7 +5049,6 @@ do -- SET_CARGO -- * @{#SET_CARGO.ForEachCargo}: Calls a function for each cargo it finds within the SET_CARGO. -- -- @field #SET_CARGO SET_CARGO - -- SET_CARGO = { ClassName = "SET_CARGO", Cargos = {}, @@ -5162,40 +5067,37 @@ do -- SET_CARGO }, } - --- Creates a new SET_CARGO object, building a set of cargos belonging to a coalitions and categories. -- @param #SET_CARGO self -- @return #SET_CARGO -- @usage -- -- Define a new SET_CARGO Object. The DatabaseSet will contain a reference to all Cargos. -- DatabaseSet = SET_CARGO:New() - function SET_CARGO:New() --R2.1 + function SET_CARGO:New() -- R2.1 -- Inherits from BASE local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.CARGOS ) ) -- #SET_CARGO return self end - --- (R2.1) Add CARGO to SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param Cargo.Cargo#CARGO Cargo A single cargo. -- @return Core.Set#SET_CARGO self - function SET_CARGO:AddCargo( Cargo ) --R2.4 + function SET_CARGO:AddCargo( Cargo ) -- R2.4 self:Add( Cargo:GetName(), Cargo ) return self end - --- (R2.1) Add CARGOs to SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param #string AddCargoNames A single name or an array of CARGO names. -- @return Core.Set#SET_CARGO self - function SET_CARGO:AddCargosByName( AddCargoNames ) --R2.1 + function SET_CARGO:AddCargosByName( AddCargoNames ) -- R2.1 - local AddCargoNamesArray = ( type( AddCargoNames ) == "table" ) and AddCargoNames or { AddCargoNames } + local AddCargoNamesArray = (type( AddCargoNames ) == "table") and AddCargoNames or { AddCargoNames } for AddCargoID, AddCargoName in pairs( AddCargoNamesArray ) do self:Add( AddCargoName, CARGO:FindByName( AddCargoName ) ) @@ -5208,9 +5110,9 @@ do -- SET_CARGO -- @param Core.Set#SET_CARGO self -- @param Wrapper.Cargo#CARGO RemoveCargoNames A single name or an array of CARGO names. -- @return Core.Set#SET_CARGO self - function SET_CARGO:RemoveCargosByName( RemoveCargoNames ) --R2.1 + function SET_CARGO:RemoveCargosByName( RemoveCargoNames ) -- R2.1 - local RemoveCargoNamesArray = ( type( RemoveCargoNames ) == "table" ) and RemoveCargoNames or { RemoveCargoNames } + local RemoveCargoNamesArray = (type( RemoveCargoNames ) == "table") and RemoveCargoNames or { RemoveCargoNames } for RemoveCargoID, RemoveCargoName in pairs( RemoveCargoNamesArray ) do self:Remove( RemoveCargoName.CargoName ) @@ -5219,25 +5121,22 @@ do -- SET_CARGO return self end - --- (R2.1) Finds a Cargo based on the Cargo Name. -- @param #SET_CARGO self -- @param #string CargoName -- @return Wrapper.Cargo#CARGO The found Cargo. - function SET_CARGO:FindCargo( CargoName ) --R2.1 + function SET_CARGO:FindCargo( CargoName ) -- R2.1 local CargoFound = self.Set[CargoName] return CargoFound end - - --- (R2.1) Builds a set of cargos of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_CARGO self -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". -- @return #SET_CARGO self - function SET_CARGO:FilterCoalitions( Coalitions ) --R2.1 + function SET_CARGO:FilterCoalitions( Coalitions ) -- R2.1 if not self.Filter.Coalitions then self.Filter.Coalitions = {} end @@ -5255,7 +5154,7 @@ do -- SET_CARGO -- @param #SET_CARGO self -- @param #string Types Can take those type strings known within DCS world. -- @return #SET_CARGO self - function SET_CARGO:FilterTypes( Types ) --R2.1 + function SET_CARGO:FilterTypes( Types ) -- R2.1 if not self.Filter.Types then self.Filter.Types = {} end @@ -5268,13 +5167,12 @@ do -- SET_CARGO return self end - --- (R2.1) Builds a set of cargos of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_CARGO self -- @param #string Countries Can take those country strings known within DCS world. -- @return #SET_CARGO self - function SET_CARGO:FilterCountries( Countries ) --R2.1 + function SET_CARGO:FilterCountries( Countries ) -- R2.1 if not self.Filter.Countries then self.Filter.Countries = {} end @@ -5287,13 +5185,12 @@ do -- SET_CARGO return self end - --- Builds a set of CARGOs that contain a given string in their name. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all cargos that **contain** the string. -- @param #SET_CARGO self -- @param #string Prefixes The string pattern(s) that need to be in the cargo name. Can also be passed as a `#table` of strings. -- @return #SET_CARGO self - function SET_CARGO:FilterPrefixes( Prefixes ) --R2.1 + function SET_CARGO:FilterPrefixes( Prefixes ) -- R2.1 if not self.Filter.CargoPrefixes then self.Filter.CargoPrefixes = {} end @@ -5306,12 +5203,10 @@ do -- SET_CARGO return self end - - --- (R2.1) Starts the filtering. -- @param #SET_CARGO self -- @return #SET_CARGO self - function SET_CARGO:FilterStart() --R2.1 + function SET_CARGO:FilterStart() -- R2.1 if _DATABASE then self:_FilterStart() @@ -5333,14 +5228,13 @@ do -- SET_CARGO return self end - --- (R2.1) 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_CARGO self -- @param Core.Event#EVENTDATA Event -- @return #string The name of the CARGO -- @return #table The CARGO - function SET_CARGO:AddInDatabase( Event ) --R2.1 + function SET_CARGO:AddInDatabase( Event ) -- R2.1 self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] @@ -5352,7 +5246,7 @@ do -- SET_CARGO -- @param Core.Event#EVENTDATA Event -- @return #string The name of the CARGO -- @return #table The CARGO - function SET_CARGO:FindInDatabase( Event ) --R2.1 + function SET_CARGO:FindInDatabase( Event ) -- R2.1 self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] @@ -5362,7 +5256,7 @@ do -- SET_CARGO -- @param #SET_CARGO self -- @param #function IteratorFunction The function that will be called when there is an alive CARGO in the SET_CARGO. The function needs to accept a CARGO parameter. -- @return #SET_CARGO self - function SET_CARGO:ForEachCargo( IteratorFunction, ... ) --R2.1 + function SET_CARGO:ForEachCargo( IteratorFunction, ... ) -- R2.1 self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet() ) @@ -5374,7 +5268,7 @@ do -- SET_CARGO -- @param #SET_CARGO self -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Cargo.Cargo#CARGO}. -- @return Wrapper.Cargo#CARGO The closest @{Cargo.Cargo#CARGO}. - function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) --R2.1 + function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) -- R2.1 self:F2( PointVec2 ) local NearestCargo = self:FindNearestObjectFromPointVec2( PointVec2 ) @@ -5409,7 +5303,6 @@ do -- SET_CARGO return FirstCargo end - --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is UnLoaded. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5418,7 +5311,6 @@ do -- SET_CARGO return FirstCargo end - --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is UnLoaded and not Deployed. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5427,7 +5319,6 @@ do -- SET_CARGO return FirstCargo end - --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is Loaded. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5436,7 +5327,6 @@ do -- SET_CARGO return FirstCargo end - --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is Deployed. -- @param #SET_CARGO self -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. @@ -5445,14 +5335,11 @@ do -- SET_CARGO return FirstCargo end - - - --- (R2.1) -- @param #SET_CARGO self -- @param AI.AI_Cargo#AI_CARGO MCargo -- @return #SET_CARGO self - function SET_CARGO:IsIncludeObject( MCargo ) --R2.1 + function SET_CARGO:IsIncludeObject( MCargo ) -- R2.1 self:F2( MCargo ) local MCargoInclude = true @@ -5505,13 +5392,13 @@ do -- SET_CARGO --- (R2.1) Handles the OnEventNewCargo event for the Set. -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA EventData - function SET_CARGO:OnEventNewCargo( EventData ) --R2.1 + function SET_CARGO:OnEventNewCargo( EventData ) -- R2.1 self:F( { "New Cargo", EventData } ) if EventData.Cargo then if EventData.Cargo and self:IsIncludeObject( EventData.Cargo ) then - self:Add( EventData.Cargo.Name , EventData.Cargo ) + self:Add( EventData.Cargo.Name, EventData.Cargo ) end end end @@ -5519,20 +5406,20 @@ do -- SET_CARGO --- (R2.1) Handles the OnDead or OnCrash event for alive units set. -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA EventData - function SET_CARGO:OnEventDeleteCargo( EventData ) --R2.1 + function SET_CARGO:OnEventDeleteCargo( EventData ) -- R2.1 self:F3( { EventData } ) if EventData.Cargo then local Cargo = _DATABASE:FindCargo( EventData.Cargo.Name ) if Cargo and Cargo.Name then - -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. - -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. - -- And this is a problem because it will remove all entries from the SET_CARGOs. - -- To prevent this from happening, the Cargo object has a flag NoDestroy. - -- When true, the SET_CARGO won't Remove the Cargo object from the set. - -- This flag is switched off after the event handlers have been called in the EVENT class. - self:F( { CargoNoDestroy=Cargo.NoDestroy } ) + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. + -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. + -- And this is a problem because it will remove all entries from the SET_CARGOs. + -- To prevent this from happening, the Cargo object has a flag NoDestroy. + -- When true, the SET_CARGO won't Remove the Cargo object from the set. + -- This flag is switched off after the event handlers have been called in the EVENT class. + self:F( { CargoNoDestroy = Cargo.NoDestroy } ) if Cargo.NoDestroy then else self:Remove( Cargo.Name ) @@ -5543,7 +5430,6 @@ do -- SET_CARGO end - do -- SET_ZONE --- @type SET_ZONE @@ -5589,11 +5475,10 @@ do -- SET_ZONE Filter = { Prefixes = nil, }, - FilterMeta = { + FilterMeta = { }, } - --- Creates a new SET_ZONE object, building a set of zones. -- @param #SET_ZONE self -- @return #SET_ZONE self @@ -5613,7 +5498,7 @@ do -- SET_ZONE -- @return self function SET_ZONE:AddZonesByName( AddZoneNames ) - local AddZoneNamesArray = ( type( AddZoneNames ) == "table" ) and AddZoneNames or { AddZoneNames } + local AddZoneNamesArray = (type( AddZoneNames ) == "table") and AddZoneNames or { AddZoneNames } for AddAirbaseID, AddZoneName in pairs( AddZoneNamesArray ) do self:Add( AddZoneName, ZONE:FindByName( AddZoneName ) ) @@ -5633,14 +5518,13 @@ do -- SET_ZONE return self end - --- Remove ZONEs from SET_ZONE. -- @param Core.Set#SET_ZONE self -- @param Core.Zone#ZONE_BASE RemoveZoneNames A single name or an array of ZONE_BASE names. -- @return self function SET_ZONE:RemoveZonesByName( RemoveZoneNames ) - local RemoveZoneNamesArray = ( type( RemoveZoneNames ) == "table" ) and RemoveZoneNames or { RemoveZoneNames } + local RemoveZoneNamesArray = (type( RemoveZoneNames ) == "table") and RemoveZoneNames or { RemoveZoneNames } for RemoveZoneID, RemoveZoneName in pairs( RemoveZoneNamesArray ) do self:Remove( RemoveZoneName ) @@ -5649,7 +5533,6 @@ do -- SET_ZONE return self end - --- Finds a Zone based on the Zone Name. -- @param #SET_ZONE self -- @param #string ZoneName @@ -5660,14 +5543,13 @@ do -- SET_ZONE return ZoneFound end - --- Get a random zone from the set. -- @param #SET_ZONE self -- @param #number margin Number of tries to find a zone -- @return Core.Zone#ZONE_BASE The random Zone. -- @return #nil if no zone in the collection. - function SET_ZONE:GetRandomZone(margin) - + function SET_ZONE:GetRandomZone( margin ) + local margin = margin or 100 if self:Count() ~= 0 then @@ -5690,7 +5572,6 @@ do -- SET_ZONE return nil end - --- Set a zone probability. -- @param #SET_ZONE self -- @param #string ZoneName The name of the zone. @@ -5699,9 +5580,6 @@ do -- SET_ZONE Zone:SetZoneProbability( ZoneProbability ) end - - - --- Builds a set of ZONEs that contain the given string in their name. -- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string. -- @param #SET_ZONE self @@ -5720,7 +5598,6 @@ do -- SET_ZONE return self end - --- Starts the filtering. -- @param #SET_ZONE self -- @return #SET_ZONE self @@ -5844,13 +5721,13 @@ do -- SET_ZONE --- Handles the OnEventNewZone event for the Set. -- @param #SET_ZONE self -- @param Core.Event#EVENTDATA EventData - function SET_ZONE:OnEventNewZone( EventData ) --R2.1 + function SET_ZONE:OnEventNewZone( EventData ) -- R2.1 self:F( { "New Zone", EventData } ) if EventData.Zone then if EventData.Zone and self:IsIncludeObject( EventData.Zone ) then - self:Add( EventData.Zone.ZoneName , EventData.Zone ) + self:Add( EventData.Zone.ZoneName, EventData.Zone ) end end end @@ -5858,20 +5735,20 @@ do -- SET_ZONE --- Handles the OnDead or OnCrash event for alive units set. -- @param #SET_ZONE self -- @param Core.Event#EVENTDATA EventData - function SET_ZONE:OnEventDeleteZone( EventData ) --R2.1 + function SET_ZONE:OnEventDeleteZone( EventData ) -- R2.1 self:F3( { EventData } ) if EventData.Zone then local Zone = _DATABASE:FindZone( EventData.Zone.ZoneName ) if Zone and Zone.ZoneName then - -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. - -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. - -- And this is a problem because it will remove all entries from the SET_ZONEs. - -- To prevent this from happening, the Zone object has a flag NoDestroy. - -- When true, the SET_ZONE won't Remove the Zone object from the set. - -- This flag is switched off after the event handlers have been called in the EVENT class. - self:F( { ZoneNoDestroy=Zone.NoDestroy } ) + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. + -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. + -- And this is a problem because it will remove all entries from the SET_ZONEs. + -- To prevent this from happening, the Zone object has a flag NoDestroy. + -- When true, the SET_ZONE won't Remove the Zone object from the set. + -- This flag is switched off after the event handlers have been called in the EVENT class. + self:F( { ZoneNoDestroy = Zone.NoDestroy } ) if Zone.NoDestroy then else self:Remove( Zone.ZoneName ) @@ -5881,7 +5758,7 @@ do -- SET_ZONE end --- Validate if a coordinate is in one of the zones in the set. - -- Returns the ZONE object where the coordiante is located. + -- Returns the ZONE object where the coordinate is located. -- If zones overlap, the first zone that validates the test is returned. -- @param #SET_ZONE self -- @param Core.Point#COORDINATE Coordinate The coordinate to be searched. @@ -5946,11 +5823,10 @@ do -- SET_ZONE_GOAL Filter = { Prefixes = nil, }, - FilterMeta = { + FilterMeta = { }, } - --- Creates a new SET_ZONE_GOAL object, building a set of zones. -- @param #SET_ZONE_GOAL self -- @return #SET_ZONE_GOAL self @@ -5975,14 +5851,13 @@ do -- SET_ZONE_GOAL return self end - --- Remove ZONEs from SET_ZONE_GOAL. -- @param Core.Set#SET_ZONE_GOAL self -- @param Core.Zone#ZONE_BASE RemoveZoneNames A single name or an array of ZONE_BASE names. -- @return self function SET_ZONE_GOAL:RemoveZonesByName( RemoveZoneNames ) - local RemoveZoneNamesArray = ( type( RemoveZoneNames ) == "table" ) and RemoveZoneNames or { RemoveZoneNames } + local RemoveZoneNamesArray = (type( RemoveZoneNames ) == "table") and RemoveZoneNames or { RemoveZoneNames } for RemoveZoneID, RemoveZoneName in pairs( RemoveZoneNamesArray ) do self:Remove( RemoveZoneName ) @@ -5991,7 +5866,6 @@ do -- SET_ZONE_GOAL return self end - --- Finds a Zone based on the Zone Name. -- @param #SET_ZONE_GOAL self -- @param #string ZoneName @@ -6002,7 +5876,6 @@ do -- SET_ZONE_GOAL return ZoneFound end - --- Get a random zone from the set. -- @param #SET_ZONE_GOAL self -- @return Core.Zone#ZONE_BASE The random Zone. @@ -6028,7 +5901,6 @@ do -- SET_ZONE_GOAL return nil end - --- Set a zone probability. -- @param #SET_ZONE_GOAL self -- @param #string ZoneName The name of the zone. @@ -6037,9 +5909,6 @@ do -- SET_ZONE_GOAL Zone:SetZoneProbability( ZoneProbability ) end - - - --- Builds a set of ZONE_GOALs that contain the given string in their name. -- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string. -- @param #SET_ZONE_GOAL self @@ -6058,7 +5927,6 @@ do -- SET_ZONE_GOAL return self end - --- Starts the filtering. -- @param #SET_ZONE_GOAL self -- @return #SET_ZONE_GOAL self @@ -6129,7 +5997,6 @@ do -- SET_ZONE_GOAL return self end - --- -- @param #SET_ZONE_GOAL self -- @param Core.Zone#ZONE_BASE MZone @@ -6171,7 +6038,7 @@ do -- SET_ZONE_GOAL if EventData.ZoneGoal then if EventData.ZoneGoal and self:IsIncludeObject( EventData.ZoneGoal ) then self:T( { "Adding Zone Capture Coalition", EventData.ZoneGoal.ZoneName, EventData.ZoneGoal } ) - self:Add( EventData.ZoneGoal.ZoneName , EventData.ZoneGoal ) + self:Add( EventData.ZoneGoal.ZoneName, EventData.ZoneGoal ) end end end @@ -6179,20 +6046,20 @@ do -- SET_ZONE_GOAL --- Handles the OnDead or OnCrash event for alive units set. -- @param #SET_ZONE_GOAL self -- @param Core.Event#EVENTDATA EventData - function SET_ZONE_GOAL:OnEventDeleteZoneGoal( EventData ) --R2.1 + function SET_ZONE_GOAL:OnEventDeleteZoneGoal( EventData ) -- R2.1 self:F3( { EventData } ) if EventData.ZoneGoal then local Zone = _DATABASE:FindZone( EventData.ZoneGoal.ZoneName ) if Zone and Zone.ZoneName then - -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. - -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. - -- And this is a problem because it will remove all entries from the SET_ZONE_GOALs. - -- To prevent this from happening, the Zone object has a flag NoDestroy. - -- When true, the SET_ZONE_GOAL won't Remove the Zone object from the set. - -- This flag is switched off after the event handlers have been called in the EVENT class. - self:F( { ZoneNoDestroy=Zone.NoDestroy } ) + -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. + -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. + -- And this is a problem because it will remove all entries from the SET_ZONE_GOALs. + -- To prevent this from happening, the Zone object has a flag NoDestroy. + -- When true, the SET_ZONE_GOAL won't Remove the Zone object from the set. + -- This flag is switched off after the event handlers have been called in the EVENT class. + self:F( { ZoneNoDestroy = Zone.NoDestroy } ) if Zone.NoDestroy then else self:Remove( Zone.ZoneName ) @@ -6588,7 +6455,7 @@ do -- SET_OPSGROUP -- @param #SET_OPSGROUP self -- @return #SET_OPSGROUP self function SET_OPSGROUP:FilterCategoryHelicopter() - self:FilterCategories("helicopter") + self:FilterCategories( "helicopter" ) return self end diff --git a/Moose Development/Moose/Core/Settings.lua b/Moose Development/Moose/Core/Settings.lua index 209bb3beb..72cf73a0c 100644 --- a/Moose Development/Moose/Core/Settings.lua +++ b/Moose Development/Moose/Core/Settings.lua @@ -29,15 +29,14 @@ -- @module Core.Settings -- @image Core_Settings.JPG - --- @type SETTINGS -- @extends Core.Base#BASE ---- Takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework. +--- Takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework. -- -- === -- --- The SETTINGS class takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework. +-- The SETTINGS class takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework. -- SETTINGS can work on 2 levels: -- -- - **Default settings**: A running mission has **Default settings**. @@ -59,7 +58,7 @@ -- -- A menu is created automatically per Command Center that allows to modify the **Default** settings. -- So, when joining a CC unit, a menu will be available that allows to change the settings parameters **FOR ALL THE PLAYERS**! --- Note that the **Default settings** will only be used when a player has not choosen its own settings. +-- Note that the **Default settings** will only be used when a player has not chosen its own settings. -- -- ## 2.2) Player settings menu -- @@ -69,7 +68,7 @@ -- -- ## 2.3) Show or Hide the Player Setting menus -- --- Of course, it may be requried not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**. +-- Of course, it may be required not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**. -- Use @{#SETTINGS.SetPlayerMenuOff}() to hide the player menus, and use @{#SETTINGS.SetPlayerMenuOn}() show the player menus. -- Note that when this method is used, any player already in a slot will not have its menus visibility changed. -- The option will only have effect when a player enters a new slot or changes a slot. @@ -94,8 +93,8 @@ -- -- - A2G BR: [Bearing Range](https://en.wikipedia.org/wiki/Bearing_(navigation)). -- - A2G MGRS: The [Military Grid Reference System](https://en.wikipedia.org/wiki/Military_Grid_Reference_System). The accuracy can also be adapted. --- - A2G LL DMS: Lattitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted. --- - A2G LL DDM: Lattitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted. +-- - A2G LL DMS: Latitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted. +-- - A2G LL DDM: Latitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted. -- -- ### 3.1.2) A2G coordinates setting **menu** -- @@ -183,7 +182,7 @@ -- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot. -- -- Each Message Type has specific timings that will be applied when the message is displayed. --- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be choosen. +-- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be chosen. -- So the player can choose its own amount of seconds how long a message should be displayed of a certain type. -- Note that **Update** messages can be chosen not to be displayed at all! -- @@ -196,7 +195,7 @@ -- -- ## 3.5) **Era** of the battle -- --- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greather threat in WWII than on modern warfare. +-- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greater threat in WWII than on modern warfare. -- Therefore, there are 4 era that are defined within the settings: -- -- - **WWII** era: Use for warfare with equipment during the world war II time. @@ -213,8 +212,8 @@ SETTINGS = { ClassName = "SETTINGS", ShowPlayerMenu = true, - MenuShort = false, - MenuStatic = false, + MenuShort = false, + MenuStatic = false, } SETTINGS.__Enum = {} @@ -231,7 +230,6 @@ SETTINGS.__Enum.Era = { Modern = 4, } - do -- SETTINGS --- SETTINGS constructor. @@ -269,14 +267,14 @@ do -- SETTINGS -- Short text are better suited for, e.g., VR. -- @param #SETTINGS self -- @param #boolean onoff If *true* use short menu texts. If *false* long ones (default). - function SETTINGS:SetMenutextShort(onoff) + function SETTINGS:SetMenutextShort( onoff ) _SETTINGS.MenuShort = onoff end --- Set menu to be static. -- @param #SETTINGS self -- @param #boolean onoff If *true* menu is static. If *false* menu will be updated after changes (default). - function SETTINGS:SetMenuStatic(onoff) + function SETTINGS:SetMenuStatic( onoff ) _SETTINGS.MenuStatic = onoff end @@ -304,7 +302,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if metric. function SETTINGS:IsMetric() - return ( self.Metric ~= nil and self.Metric == true ) or ( self.Metric == nil and _SETTINGS:IsMetric() ) + return (self.Metric ~= nil and self.Metric == true) or (self.Metric == nil and _SETTINGS:IsMetric()) end --- Sets the SETTINGS imperial. @@ -317,7 +315,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if imperial. function SETTINGS:IsImperial() - return ( self.Metric ~= nil and self.Metric == false ) or ( self.Metric == nil and _SETTINGS:IsMetric() ) + return (self.Metric ~= nil and self.Metric == false) or (self.Metric == nil and _SETTINGS:IsMetric()) end --- Sets the SETTINGS LL accuracy. @@ -359,13 +357,12 @@ do -- SETTINGS self.MessageTypeTimings[MessageType] = MessageTime end - --- Gets the SETTINGS Message Display Timing of a MessageType -- @param #SETTINGS self -- @param Core.Message#MESSAGE MessageType The type of the message. -- @return #number function SETTINGS:GetMessageTime( MessageType ) - return ( self.MessageTypeTimings and self.MessageTypeTimings[MessageType] ) or _SETTINGS:GetMessageTime( MessageType ) + return (self.MessageTypeTimings and self.MessageTypeTimings[MessageType]) or _SETTINGS:GetMessageTime( MessageType ) end --- Sets A2G LL DMS @@ -386,14 +383,14 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if LL DMS function SETTINGS:IsA2G_LL_DMS() - return ( self.A2GSystem and self.A2GSystem == "LL DMS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS() ) + return (self.A2GSystem and self.A2GSystem == "LL DMS") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS()) end --- Is LL DDM -- @param #SETTINGS self -- @return #boolean true if LL DDM function SETTINGS:IsA2G_LL_DDM() - return ( self.A2GSystem and self.A2GSystem == "LL DDM" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM() ) + return (self.A2GSystem and self.A2GSystem == "LL DDM") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM()) end --- Sets A2G MGRS @@ -407,7 +404,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if MGRS function SETTINGS:IsA2G_MGRS() - return ( self.A2GSystem and self.A2GSystem == "MGRS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_MGRS() ) + return (self.A2GSystem and self.A2GSystem == "MGRS") or (not self.A2GSystem and _SETTINGS:IsA2G_MGRS()) end --- Sets A2G BRA @@ -421,7 +418,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if BRA function SETTINGS:IsA2G_BR() - return ( self.A2GSystem and self.A2GSystem == "BR" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_BR() ) + return (self.A2GSystem and self.A2GSystem == "BR") or (not self.A2GSystem and _SETTINGS:IsA2G_BR()) end --- Sets A2A BRA @@ -435,7 +432,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if BRA function SETTINGS:IsA2A_BRAA() - return ( 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 --- Sets A2A BULLS @@ -449,7 +446,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if BULLS function SETTINGS:IsA2A_BULLS() - return ( self.A2ASystem and self.A2ASystem == "BULLS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BULLS() ) + return (self.A2ASystem and self.A2ASystem == "BULLS") or (not self.A2ASystem and _SETTINGS:IsA2A_BULLS()) end --- Sets A2A LL DMS @@ -470,14 +467,14 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if LL DMS function SETTINGS:IsA2A_LL_DMS() - return ( self.A2ASystem and self.A2ASystem == "LL DMS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS() ) + return (self.A2ASystem and self.A2ASystem == "LL DMS") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS()) end --- Is LL DDM -- @param #SETTINGS self -- @return #boolean true if LL DDM function SETTINGS:IsA2A_LL_DDM() - return ( self.A2ASystem and self.A2ASystem == "LL DDM" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM() ) + return (self.A2ASystem and self.A2ASystem == "LL DDM") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM()) end --- Sets A2A MGRS @@ -491,7 +488,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if MGRS function SETTINGS:IsA2A_MGRS() - return ( self.A2ASystem and self.A2ASystem == "MGRS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_MGRS() ) + return (self.A2ASystem and self.A2ASystem == "MGRS") or (not self.A2ASystem and _SETTINGS:IsA2A_MGRS()) end --- @param #SETTINGS self @@ -510,37 +507,37 @@ do -- SETTINGS -- A2G Coordinate System ------- - local text="A2G Coordinate System" + local text = "A2G Coordinate System" if _SETTINGS.MenuShort then - text="A2G Coordinates" + text = "A2G Coordinates" end local A2GCoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) -- Set LL DMS if not self:IsA2G_LL_DMS() then - local text="Lat/Lon Degree Min Sec (LL DMS)" + local text = "Lat/Lon Degree Min Sec (LL DMS)" if _SETTINGS.MenuShort then - text="LL DMS" + text = "LL DMS" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime ) end -- Set LL DDM if not self:IsA2G_LL_DDM() then - local text="Lat/Lon Degree Dec Min (LL DDM)" + local text = "Lat/Lon Degree Dec Min (LL DDM)" if _SETTINGS.MenuShort then - text="LL DDM" + text = "LL DDM" end MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime ) end -- Set LL DMS accuracy. if self:IsA2G_LL_DDM() then - local text1="LL DDM Accuracy 1" - local text2="LL DDM Accuracy 2" - local text3="LL DDM Accuracy 3" + local text1 = "LL DDM Accuracy 1" + local text2 = "LL DDM Accuracy 2" + local text3 = "LL DDM Accuracy 3" if _SETTINGS.MenuShort then - text1="LL DDM" + text1 = "LL DDM" end 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 ) @@ -549,18 +546,18 @@ do -- SETTINGS -- Set BR. if not self:IsA2G_BR() then - local text="Bearing, Range (BR)" + local text = "Bearing, Range (BR)" if _SETTINGS.MenuShort then - text="BR" + text = "BR" end - MENU_GROUP_COMMAND:New( MenuGroup, text , A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime ) + MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime ) end -- Set MGRS. if not self:IsA2G_MGRS() then - local text="Military Grid (MGRS)" + local text = "Military Grid (MGRS)" if _SETTINGS.MenuShort then - text="MGRS" + text = "MGRS" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime ) end @@ -578,24 +575,24 @@ do -- SETTINGS -- A2A Coordinate System ------- - local text="A2A Coordinate System" + local text = "A2A Coordinate System" if _SETTINGS.MenuShort then - text="A2A Coordinates" + text = "A2A Coordinates" end local A2ACoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) if not self:IsA2A_LL_DMS() then - local text="Lat/Lon Degree Min Sec (LL DMS)" + local text = "Lat/Lon Degree Min Sec (LL DMS)" if _SETTINGS.MenuShort then - text="LL DMS" + text = "LL DMS" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime ) end if not self:IsA2A_LL_DDM() then - local text="Lat/Lon Degree Dec Min (LL DDM)" + local text = "Lat/Lon Degree Dec Min (LL DDM)" if _SETTINGS.MenuShort then - text="LL DDM" + text = "LL DDM" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime ) end @@ -608,25 +605,25 @@ do -- SETTINGS end if not self:IsA2A_BULLS() then - local text="Bullseye (BULLS)" + local text = "Bullseye (BULLS)" if _SETTINGS.MenuShort then - text="Bulls" + text = "Bulls" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BULLS" ):SetTime( MenuTime ) end if not self:IsA2A_BRAA() then - local text="Bearing Range Altitude Aspect (BRAA)" + local text = "Bearing Range Altitude Aspect (BRAA)" if _SETTINGS.MenuShort then - text="BRAA" + text = "BRAA" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BRAA" ):SetTime( MenuTime ) end if not self:IsA2A_MGRS() then - local text="Military Grid (MGRS)" + local text = "Military Grid (MGRS)" if _SETTINGS.MenuShort then - text="MGRS" + text = "MGRS" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime ) end @@ -639,31 +636,31 @@ do -- SETTINGS MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 5", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime ) end - local text="Measures and Weights System" + local text = "Measures and Weights System" if _SETTINGS.MenuShort then - text="Unit System" + text = "Unit System" end local MetricsMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) if self:IsMetric() then - local text="Imperial (Miles,Feet)" + local text = "Imperial (Miles,Feet)" if _SETTINGS.MenuShort then - text="Imperial" + text = "Imperial" end MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, false ):SetTime( MenuTime ) end if self:IsImperial() then - local text="Metric (Kilometers,Meters)" + local text = "Metric (Kilometers,Meters)" if _SETTINGS.MenuShort then - text="Metric" + text = "Metric" end MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, true ):SetTime( MenuTime ) end - local text="Messages and Reports" + local text = "Messages and Reports" if _SETTINGS.MenuShort then - text="Messages & Reports" + text = "Messages & Reports" end local MessagesMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) @@ -704,7 +701,6 @@ do -- SETTINGS 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 @@ -748,11 +744,11 @@ do -- SETTINGS self.PlayerMenu = PlayerMenu - self:I(string.format("Setting menu for player %s", tostring(PlayerName))) + self:I( string.format( "Setting menu for player %s", tostring( PlayerName ) ) ) local submenu = MENU_GROUP:New( PlayerGroup, "LL Accuracy", PlayerMenu ) MENU_GROUP_COMMAND:New( PlayerGroup, "LL 0 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 0 ) - MENU_GROUP_COMMAND:New( PlayerGroup, "LL 1 Decimal", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 ) + MENU_GROUP_COMMAND:New( PlayerGroup, "LL 1 Decimal", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 ) MENU_GROUP_COMMAND:New( PlayerGroup, "LL 2 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 ) MENU_GROUP_COMMAND:New( PlayerGroup, "LL 3 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 ) MENU_GROUP_COMMAND:New( PlayerGroup, "LL 4 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 4 ) @@ -769,40 +765,40 @@ do -- SETTINGS -- A2G Coordinate System ------ - local text="A2G Coordinate System" + local text = "A2G Coordinate System" if _SETTINGS.MenuShort then - text="A2G Coordinates" + text = "A2G Coordinates" end local A2GCoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) if not self:IsA2G_LL_DMS() or _SETTINGS.MenuStatic then - local text="Lat/Lon Degree Min Sec (LL DMS)" + local text = "Lat/Lon Degree Min Sec (LL DMS)" if _SETTINGS.MenuShort then - text="A2G LL DMS" + text = "A2G LL DMS" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" ) end if not self:IsA2G_LL_DDM() or _SETTINGS.MenuStatic then - local text="Lat/Lon Degree Dec Min (LL DDM)" + local text = "Lat/Lon Degree Dec Min (LL DDM)" if _SETTINGS.MenuShort then - text="A2G LL DDM" + text = "A2G LL DDM" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" ) end if not self:IsA2G_BR() or _SETTINGS.MenuStatic then - local text="Bearing, Range (BR)" + local text = "Bearing, Range (BR)" if _SETTINGS.MenuShort then - text="A2G BR" + text = "A2G BR" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BR" ) end if not self:IsA2G_MGRS() or _SETTINGS.MenuStatic then - local text="Military Grid (MGRS)" + local text = "Military Grid (MGRS)" if _SETTINGS.MenuShort then - text="A2G MGRS" + text = "A2G MGRS" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" ) end @@ -811,49 +807,48 @@ do -- SETTINGS -- A2A Coordinates Menu ------ - local text="A2A Coordinate System" + local text = "A2A Coordinate System" if _SETTINGS.MenuShort then - text="A2A Coordinates" + text = "A2A Coordinates" end local A2ACoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) - if not self:IsA2A_LL_DMS() or _SETTINGS.MenuStatic then - local text="Lat/Lon Degree Min Sec (LL DMS)" + local text = "Lat/Lon Degree Min Sec (LL DMS)" if _SETTINGS.MenuShort then - text="A2A LL DMS" + text = "A2A LL DMS" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" ) end if not self:IsA2A_LL_DDM() or _SETTINGS.MenuStatic then - local text="Lat/Lon Degree Dec Min (LL DDM)" + local text = "Lat/Lon Degree Dec Min (LL DDM)" if _SETTINGS.MenuShort then - text="A2A LL DDM" + text = "A2A LL DDM" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" ) end if not self:IsA2A_BULLS() or _SETTINGS.MenuStatic then - local text="Bullseye (BULLS)" + local text = "Bullseye (BULLS)" if _SETTINGS.MenuShort then - text="A2A BULLS" + text = "A2A BULLS" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BULLS" ) end if not self:IsA2A_BRAA() or _SETTINGS.MenuStatic then - local text="Bearing Range Altitude Aspect (BRAA)" + local text = "Bearing Range Altitude Aspect (BRAA)" if _SETTINGS.MenuShort then - text="A2A BRAA" + text = "A2A BRAA" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRAA" ) end if not self:IsA2A_MGRS() or _SETTINGS.MenuStatic then - local text="Military Grid (MGRS)" + local text = "Military Grid (MGRS)" if _SETTINGS.MenuShort then - text="A2A MGRS" + text = "A2A MGRS" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" ) end @@ -862,24 +857,24 @@ do -- SETTINGS -- Unit system --- - local text="Measures and Weights System" + local text = "Measures and Weights System" if _SETTINGS.MenuShort then - text="Unit System" + text = "Unit System" end local MetricsMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) if self:IsMetric() or _SETTINGS.MenuStatic then - local text="Imperial (Miles,Feet)" + local text = "Imperial (Miles,Feet)" if _SETTINGS.MenuShort then - text="Imperial" + text = "Imperial" end MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, false ) end if self:IsImperial() or _SETTINGS.MenuStatic then - local text="Metric (Kilometers,Meters)" + local text = "Metric (Kilometers,Meters)" if _SETTINGS.MenuShort then - text="Metric" + text = "Metric" end MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true ) end @@ -888,9 +883,9 @@ do -- SETTINGS -- Messages and Reports --- - local text="Messages and Reports" + local text = "Messages and Reports" if _SETTINGS.MenuShort then - text="Messages & Reports" + text = "Messages & Reports" end local MessagesMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) @@ -950,39 +945,38 @@ do -- SETTINGS return self end - --- @param #SETTINGS self 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() + MESSAGE:New( string.format( "Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end --- @param #SETTINGS self 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() + MESSAGE:New( string.format( "Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end --- @param #SETTINGS self 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() + MESSAGE:New( string.format( "Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end --- @param #SETTINGS self 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() + MESSAGE:New( string.format( "Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end --- @param #SETTINGS self 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() + 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 @@ -995,12 +989,12 @@ do -- SETTINGS do --- @param #SETTINGS self function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem ) - BASE:E( {self, PlayerUnit:GetName(), 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 ) - if _SETTINGS.MenuStatic==false then - self:RemovePlayerMenu(PlayerUnit) - self:SetPlayerMenu(PlayerUnit) + if _SETTINGS.MenuStatic == false then + self:RemovePlayerMenu( PlayerUnit ) + self:SetPlayerMenu( PlayerUnit ) end end @@ -1008,9 +1002,9 @@ do -- SETTINGS 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 ) - if _SETTINGS.MenuStatic==false then - self:RemovePlayerMenu(PlayerUnit) - self:SetPlayerMenu(PlayerUnit) + if _SETTINGS.MenuStatic == false then + self:RemovePlayerMenu( PlayerUnit ) + self:SetPlayerMenu( PlayerUnit ) end end @@ -1018,9 +1012,9 @@ do -- SETTINGS function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy ) self.LL_Accuracy = LL_Accuracy MESSAGE:New( string.format( "Settings: LL format accuracy set to %d decimal places for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup ) - if _SETTINGS.MenuStatic==false then - self:RemovePlayerMenu(PlayerUnit) - self:SetPlayerMenu(PlayerUnit) + if _SETTINGS.MenuStatic == false then + self:RemovePlayerMenu( PlayerUnit ) + self:SetPlayerMenu( PlayerUnit ) end end @@ -1028,9 +1022,9 @@ do -- SETTINGS function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy ) self.MGRS_Accuracy = MGRS_Accuracy MESSAGE:New( string.format( "Settings: MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup ) - if _SETTINGS.MenuStatic==false then - self:RemovePlayerMenu(PlayerUnit) - self:SetPlayerMenu(PlayerUnit) + if _SETTINGS.MenuStatic == false then + self:RemovePlayerMenu( PlayerUnit ) + self:SetPlayerMenu( PlayerUnit ) end end @@ -1038,9 +1032,9 @@ do -- SETTINGS 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 ) - if _SETTINGS.MenuStatic==false then - self:RemovePlayerMenu(PlayerUnit) - self:SetPlayerMenu(PlayerUnit) + if _SETTINGS.MenuStatic == false then + self:RemovePlayerMenu( PlayerUnit ) + self:SetPlayerMenu( PlayerUnit ) end end @@ -1070,7 +1064,6 @@ do -- SETTINGS end - --- Configures the era of the mission to be Cold war. -- @param #SETTINGS self -- @return #SETTINGS self @@ -1080,7 +1073,6 @@ do -- SETTINGS end - --- Configures the era of the mission to be Modern war. -- @param #SETTINGS self -- @return #SETTINGS self @@ -1090,7 +1082,4 @@ do -- SETTINGS end - - - end diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 175e972cb..b22a27939 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1,16 +1,16 @@ --- **Core** - Spawn dynamically new groups of units in running missions. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Spawn new groups in running missions. -- * Schedule spawning of new groups. -- * Put limits on the amount of groups that can be spawned, and the amount of units that can be alive at the same time. -- * Randomize the spawning location between different zones. -- * Randomize the initial positions within the zones. -- * Spawn in array formation. --- * Spawn uncontrolled (for planes or helos only). +-- * Spawn uncontrolled (for planes or helicopters only). -- * Clean up inactive helicopters that "crashed". -- * Place a hook to capture a spawn event, and tailor with customer code. -- * Spawn late activated. @@ -27,26 +27,25 @@ -- * Spawn and keep the unit names. -- * Spawn with a different coalition and country. -- * Enquiry methods to check on spawn status. --- +-- -- === -- -- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SPA%20-%20Spawning) -- -- === --- +-- -- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1jirWIo4t4YxqN-HxjqRkL) --- +-- -- === --- +-- -- ### Author: **FlightControl** -- ### Contributions: A lot of people within this community! --- +-- -- === --- +-- -- @module Core.Spawn -- @image Core_Spawn.JPG - --- SPAWN Class -- @type SPAWN -- @field ClassName @@ -59,81 +58,80 @@ -- @field #SPAWN.SpawnZoneTable SpawnZoneTable -- @extends Core.Base#BASE - ---- Allows to spawn dynamically new @{Core.Group}s. --- +--- Allows to spawn dynamically new @{Core.Group}s. +-- -- Each SPAWN object needs to be have related **template groups** setup in the Mission Editor (ME), --- which is a normal group with the **Late Activation** flag set. --- This template group will never be activated in your mission. --- SPAWN uses that **template group** to reference to all the characteristics --- (air, ground, livery, unit composition, formation, skill level etc) of each new group to be spawned. --- +-- which is a normal group with the **Late Activation** flag set. +-- This template group will never be activated in your mission. +-- SPAWN uses that **template group** to reference to all the characteristics +-- (air, ground, livery, unit composition, formation, skill level etc) of each new group to be spawned. +-- -- Therefore, when creating a SPAWN object, the @{#SPAWN.New} and @{#SPAWN.NewWithAlias} require --- **the name of the template group** to be given as a string to those constructor methods. --- --- Initialization settings can be applied on the SPAWN object, --- which modify the behaviour or the way groups are spawned. +-- **the name of the template group** to be given as a string to those constructor methods. +-- +-- Initialization settings can be applied on the SPAWN object, +-- which modify the behavior or the way groups are spawned. -- These initialization methods have the prefix **Init**. -- There are also spawn methods with the prefix **Spawn** and will spawn new groups in various ways. --- --- ### IMPORTANT! The methods with prefix **Init** must be used before any methods with prefix **Spawn** method are used, or unexpected results may appear!!! --- --- Because SPAWN can spawn multiple groups of a template group, --- SPAWN has an **internal index** that keeps track --- which was the latest group that was spawned. --- --- **Limits** can be set on how many groups can be spawn in each SPAWN object, +-- +-- ### IMPORTANT! The methods with prefix **Init** must be used before any methods with prefix **Spawn** method are used, or unexpected results may appear!!! +-- +-- Because SPAWN can spawn multiple groups of a template group, +-- SPAWN has an **internal index** that keeps track +-- which was the latest group that was spawned. +-- +-- **Limits** can be set on how many groups can be spawn in each SPAWN object, -- using the method @{#SPAWN.InitLimit}. SPAWN has 2 kind of limits: --- --- * The maximum amount of @{Wrapper.Unit}s that can be **alive** at the same time... +-- +-- * The maximum amount of @{Wrapper.Unit}s that can be **alive** at the same time... -- * The maximum amount of @{Wrapper.Group}s that can be **spawned**... This is more of a **resource**-type of limit. --- --- When new groups get spawned using the **Spawn** methods, +-- +-- When new groups get spawned using the **Spawn** methods, -- it will be evaluated whether any limits have been reached. --- When no spawn limit is reached, a new group will be created by the spawning methods, --- and the internal index will be increased with 1. --- --- These limits ensure that your mission does not accidentally get flooded with spawned groups. --- Additionally, it also guarantees that independent of the group composition, +-- When no spawn limit is reached, a new group will be created by the spawning methods, +-- and the internal index will be increased with 1. +-- +-- These limits ensure that your mission does not accidentally get flooded with spawned groups. +-- Additionally, it also guarantees that independent of the group composition, -- at any time, the most optimal amount of groups are alive in your mission. -- For example, if your template group has a group composition of 10 units, and you specify a limit of 100 units alive at the same time, -- with unlimited resources = :InitLimit( 100, 0 ) and 10 groups are alive, but two groups have only one unit alive in the group, -- then a sequent Spawn(Scheduled) will allow a new group to be spawned!!! --- --- ### IMPORTANT!! If a limit has been reached, it is possible that a **Spawn** method returns **nil**, meaning, no @{Wrapper.Group} had been spawned!!! --- --- Spawned groups get **the same name** as the name of the template group. --- Spawned units in those groups keep _by default_ **the same name** as the name of the template group. --- However, because multiple groups and units are created from the template group, +-- +-- ### IMPORTANT!! If a limit has been reached, it is possible that a **Spawn** method returns **nil**, meaning, no @{Wrapper.Group} had been spawned!!! +-- +-- Spawned groups get **the same name** as the name of the template group. +-- Spawned units in those groups keep _by default_ **the same name** as the name of the template group. +-- However, because multiple groups and units are created from the template group, -- a suffix is added to each spawned group and unit. --- +-- -- Newly spawned groups will get the following naming structure at run-time: --- --- 1. Spawned groups will have the name _GroupName_#_nnn_, where _GroupName_ is the name of the **template group**, +-- +-- 1. Spawned groups will have the name _GroupName_#_nnn_, where _GroupName_ is the name of the **template group**, -- and _nnn_ is a **counter from 0 to 999**. --- 2. Spawned units will have the name _GroupName_#_nnn_-_uu_, +-- 2. Spawned units will have the name _GroupName_#_nnn_-_uu_, -- where _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group. --- --- That being said, there is a way to keep the same unit names! +-- +-- That being said, there is a way to keep the same unit names! -- The method @{#SPAWN.InitKeepUnitNames}() will keep the same unit names as defined within the template group, thus: --- --- 3. Spawned units will have the name _UnitName_#_nnn_-_uu_, --- where _UnitName_ is the **unit name as defined in the template group*, +-- +-- 3. Spawned units will have the name _UnitName_#_nnn_-_uu_, +-- where _UnitName_ is the **unit name as defined in the template group*, -- and _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group. --- +-- -- Some **additional notes that need to be considered!!**: --- --- * templates are actually groups defined within the mission editor, with the flag "Late Activation" set. +-- +-- * templates are actually groups defined within the mission editor, with the flag "Late Activation" set. -- As such, these groups are never used within the mission, but are used by the @{#SPAWN} module. -- * It is important to defined BEFORE you spawn new groups, -- a proper initialization of the SPAWN instance is done with the options you want to use. --- * When designing a mission, NEVER name groups using a "#" within the name of the group Spawn template(s), +-- * When designing a mission, NEVER name groups using a "#" within the name of the group Spawn template(s), -- or the SPAWN module logic won't work anymore. --- +-- -- ## SPAWN construction methods --- +-- -- Create a new SPAWN object with the @{#SPAWN.New}() or the @{#SPAWN.NewWithAlias}() methods: --- +-- -- * @{#SPAWN.New}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition). -- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition), and gives each spawned @{Wrapper.Group} an different name. -- @@ -142,63 +140,62 @@ -- So in principle, the group list will contain all parameters and configurations after initialization, and when groups get actually spawned, this spawning can be done quickly and efficient. -- -- ## SPAWN **Init**ialization methods --- --- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix: --- +-- +-- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix: +-- -- ### Unit Names --- +-- -- * @{#SPAWN.InitKeepUnitNames}(): Keeps the unit names as defined within the mission editor, but note that anything after a # mark is ignored, and any spaces before and after the resulting name are removed. IMPORTANT! This method MUST be the first used after :New !!! --- +-- -- ### Route randomization --- +-- -- * @{#SPAWN.InitRandomizeRoute}(): Randomize the routes of spawned groups, and for air groups also optionally the height. --- --- ### Group composition randomization --- +-- +-- ### Group composition randomization +-- -- * @{#SPAWN.InitRandomizeTemplate}(): Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined. --- +-- -- ### Uncontrolled --- +-- -- * @{#SPAWN.InitUnControlled}(): Spawn plane groups uncontrolled. --- +-- -- ### Array formation --- --- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a batallion in an array. --- +-- +-- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a battalion in an array. +-- -- ### Position randomization --- +-- -- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Wrapper.Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens. -- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Wrapper.Unit}s in the @{Wrapper.Group} that is spawned within a **radius band**, given an Outer and Inner radius. -- * @{#SPAWN.InitRandomizeZones}(): Randomizes the spawning between a predefined list of @{Zone}s that are declared using this function. Each zone can be given a probability factor. --- +-- -- ### Enable / Disable AI when spawning a new @{Wrapper.Group} --- +-- -- * @{#SPAWN.InitAIOn}(): Turns the AI On when spawning the new @{Wrapper.Group} object. -- * @{#SPAWN.InitAIOff}(): Turns the AI Off when spawning the new @{Wrapper.Group} object. -- * @{#SPAWN.InitAIOnOff}(): Turns the AI On or Off when spawning the new @{Wrapper.Group} object. --- --- ### Limit scheduled spawning --- +-- +-- ### Limit scheduled spawning +-- -- * @{#SPAWN.InitLimit}(): Limits the amount of groups that can be alive at the same time and that can be dynamically spawned. --- +-- -- ### Delay initial scheduled spawn --- --- * @{#SPAWN.InitDelayOnOff}(): Turns the inital delay On/Off when scheduled spawning the first @{Wrapper.Group} object. --- * @{#SPAWN.InitDelayOn}(): Turns the inital delay On when scheduled spawning the first @{Wrapper.Group} object. --- * @{#SPAWN.InitDelayOff}(): Turns the inital delay Off when scheduled spawning the first @{Wrapper.Group} object. --- +-- +-- * @{#SPAWN.InitDelayOnOff}(): Turns the initial delay On/Off when scheduled spawning the first @{Wrapper.Group} object. +-- * @{#SPAWN.InitDelayOn}(): Turns the initial delay On when scheduled spawning the first @{Wrapper.Group} object. +-- * @{#SPAWN.InitDelayOff}(): Turns the initial delay Off when scheduled spawning the first @{Wrapper.Group} object. +-- -- ### Repeat spawned @{Wrapper.Group}s upon landing --- +-- -- * @{#SPAWN.InitRepeat}() or @{#SPAWN.InitRepeatOnLanding}(): This method is used to re-spawn automatically the same group after it has landed. -- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp. --- --- +-- -- ## SPAWN **Spawn** methods --- +-- -- Groups can be spawned at different times and methods: --- +-- -- ### **Single** spawning methods --- +-- -- * @{#SPAWN.Spawn}(): Spawn one new group based on the last spawned index. -- * @{#SPAWN.ReSpawn}(): Re-spawn a group based on a given index. -- * @{#SPAWN.SpawnFromVec3}(): Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air). @@ -207,70 +204,66 @@ -- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Wrapper.Unit}. -- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}. -- * @{#SPAWN.SpawnAtAirbase}(): Spawn a new group at an @{Wrapper.Airbase}, which can be an airdrome, ship or helipad. --- --- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{Wrapper.Group#GROUP.New} object, that contains a reference to the DCSGroup object. +-- +-- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{Wrapper.Group#GROUP.New} object, that contains a reference to the DCSGroup object. -- You can use the @{GROUP} object to do further actions with the DCSGroup. --- +-- -- ### **Scheduled** spawning methods --- --- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals. ---- * @{#SPAWN.SpawnScheduleStart}(): Start or continue to spawn groups at scheduled time intervals. --- * @{#SPAWN.SpawnScheduleStop}(): Stop the spawning of groups at scheduled time intervals. --- --- +-- +-- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals. +--- * @{#SPAWN.SpawnScheduleStart}(): Start or continue to spawn groups at scheduled time intervals. +-- * @{#SPAWN.SpawnScheduleStop}(): Stop the spawning of groups at scheduled time intervals. +-- -- ## Retrieve alive GROUPs spawned by the SPAWN object --- +-- -- The SPAWN class administers which GROUPS it has reserved (in stock) or has created during mission execution. -- Every time a SPAWN object spawns a new GROUP object, a reference to the GROUP object is added to an internal table of GROUPS. -- SPAWN provides methods to iterate through that internal GROUP object reference table: --- +-- -- * @{#SPAWN.GetFirstAliveGroup}(): Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found. -- * @{#SPAWN.GetNextAliveGroup}(): Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found. -- * @{#SPAWN.GetLastAliveGroup}(): Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found. --- +-- -- You can use the methods @{#SPAWN.GetFirstAliveGroup}() and sequently @{#SPAWN.GetNextAliveGroup}() to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example. -- The method @{#SPAWN.GetGroupFromIndex}() will return the GROUP object reference from the given Index, dead or alive... --- +-- -- ## Spawned cleaning of inactive groups --- --- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damged stop their activities, while remaining alive. --- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't, +-- +-- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damaged stop their activities, while remaining alive. +-- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't, -- and it may occur that no new groups are or can be spawned as limits are reached. -- To prevent this, a @{#SPAWN.InitCleanUp}() initialization method has been defined that will silently monitor the status of each spawned group. --- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time. --- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"... --- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically. --- This models AI that has succesfully returned to their airbase, to restart their combat activities. +-- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time. +-- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"... +-- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically. +-- This models AI that has successfully returned to their airbase, to restart their combat activities. -- Check the @{#SPAWN.InitCleanUp}() for further info. --- +-- -- ## Catch the @{Wrapper.Group} Spawn Event in a callback function! --- +-- -- When using the @{#SPAWN.SpawnScheduled)() method, new @{Wrapper.Group}s are created following the spawn time interval parameters. -- When a new @{Wrapper.Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event. --- The SPAWN class supports this functionality through the method @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ), --- which takes a function as a parameter that you can define locally. +-- The SPAWN class supports this functionality through the method @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ), +-- which takes a function as a parameter that you can define locally. -- Whenever a new @{Wrapper.Group} is spawned, the given function is called, and the @{Wrapper.Group} that was just spawned, is given as a parameter. --- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Wrapper.Group} object. +-- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Wrapper.Group} object. -- A coding example is provided at the description of the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method. --- +-- -- ## Delay the initial spawning --- --- When using the @{#SPAWN.SpawnScheduled)() method, the default behaviour of this method will be that it will spawn the initial (first) @{Wrapper.Group} +-- +-- When using the @{#SPAWN.SpawnScheduled)() method, the default behavior of this method will be that it will spawn the initial (first) @{Wrapper.Group} -- immediately when :SpawnScheduled() is initiated. The methods @{#SPAWN.InitDelayOnOff}() and @{#SPAWN.InitDelayOn}() can be used to -- activate a delay before the first @{Wrapper.Group} is spawned. For completeness, a method @{#SPAWN.InitDelayOff}() is also available, that --- can be used to switch off the initial delay. Because there is no delay by default, this method would only be used when a +-- can be used to switch off the initial delay. Because there is no delay by default, this method would only be used when a -- @{#SPAWN.SpawnScheduleStop}() ; @{#SPAWN.SpawnScheduleStart}() sequence would have been used. --- --- +-- -- @field #SPAWN SPAWN --- SPAWN = { ClassName = "SPAWN", SpawnTemplatePrefix = nil, SpawnAliasPrefix = nil, } - --- Enumerator for spawns at airbases -- @type SPAWN.Takeoff -- @extends Wrapper.Group#GROUP.Takeoff @@ -286,7 +279,6 @@ SPAWN.Takeoff = { --- @type SPAWN.SpawnZoneTable -- @list SpawnZone - --- Creates the main object to spawn a @{Wrapper.Group} defined in the DCS ME. -- @param #SPAWN self -- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix. @@ -296,47 +288,47 @@ SPAWN.Takeoff = { -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ) -- @usage local Plane = SPAWN:New( "Plane" ) -- Creates a new local variable that can initiate new planes with the name "Plane#ddd" using the template "Plane" as defined within the ME. function SPAWN:New( SpawnTemplatePrefix ) - local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN - self:F( { SpawnTemplatePrefix } ) - - local TemplateGroup = GROUP:FindByName( SpawnTemplatePrefix ) - if TemplateGroup then - self.SpawnTemplatePrefix = SpawnTemplatePrefix - self.SpawnIndex = 0 - self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. - self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. - self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! - self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. - self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. - self.SpawnInitLimit = false -- By default, no InitLimit - self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. - self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. - self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. - self.AIOnOff = true -- The AI is on by default when spawning a group. + local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN + self:F( { SpawnTemplatePrefix } ) + + local TemplateGroup = GROUP:FindByName( SpawnTemplatePrefix ) + if TemplateGroup then + self.SpawnTemplatePrefix = SpawnTemplatePrefix + self.SpawnIndex = 0 + self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. + self.AliveUnits = 0 -- Contains the counter how many units are currently alive + self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. + self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! + self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. + self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. + self.SpawnInitLimit = false -- By default, no InitLimit + self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. + self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. + self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. + self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. + self.AIOnOff = true -- The AI is on by default when spawning a group. self.SpawnUnControlled = false - self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. - self.DelayOnOff = false -- No intial delay when spawning the first group. - self.SpawnGrouping = nil -- No grouping. - self.SpawnInitLivery = nil -- No special livery. - self.SpawnInitSkill = nil -- No special skill. - self.SpawnInitFreq = nil -- No special frequency. - self.SpawnInitModu = nil -- No special modulation. - self.SpawnInitRadio = nil -- No radio comms setting. + self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. + self.DelayOnOff = false -- No intial delay when spawning the first group. + self.SpawnGrouping = nil -- No grouping. + self.SpawnInitLivery = nil -- No special livery. + self.SpawnInitSkill = nil -- No special skill. + self.SpawnInitFreq = nil -- No special frequency. + self.SpawnInitModu = nil -- No special modulation. + self.SpawnInitRadio = nil -- No radio comms setting. self.SpawnInitModex = nil self.SpawnInitAirbase = nil - self.TweakedTemplate = false -- Check if the user is using self made template. + self.TweakedTemplate = false -- Check if the user is using self made template. - self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. - else - error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) - end + self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. + else + error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) + end self:SetEventPriority( 5 ) self.SpawnHookScheduler = SCHEDULER:New( nil ) - return self + return self end --- Creates a new SPAWN instance to create new groups based on the defined template and using a new alias for each new group. @@ -349,50 +341,49 @@ end -- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' ) -- @usage local PlaneWithAlias = SPAWN:NewWithAlias( "Plane", "Bomber" ) -- Creates a new local variable that can instantiate new planes with the name "Bomber#ddd" using the template "Plane" as defined within the ME. function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } ) - - local TemplateGroup = GROUP:FindByName( SpawnTemplatePrefix ) - if TemplateGroup then - self.SpawnTemplatePrefix = SpawnTemplatePrefix - self.SpawnAliasPrefix = SpawnAliasPrefix - self.SpawnIndex = 0 - self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. - self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. - self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! - self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. - self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. - self.SpawnInitLimit = false -- By default, no InitLimit - self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. - self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. - self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. - self.AIOnOff = true -- The AI is on by default when spawning a group. + local self = BASE:Inherit( self, BASE:New() ) + self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } ) + + local TemplateGroup = GROUP:FindByName( SpawnTemplatePrefix ) + if TemplateGroup then + self.SpawnTemplatePrefix = SpawnTemplatePrefix + self.SpawnAliasPrefix = SpawnAliasPrefix + self.SpawnIndex = 0 + self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. + self.AliveUnits = 0 -- Contains the counter how many units are currently alive + self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. + self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! + self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. + self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. + self.SpawnInitLimit = false -- By default, no InitLimit + self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. + self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. + self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. + self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. + self.AIOnOff = true -- The AI is on by default when spawning a group. self.SpawnUnControlled = false - self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. - self.DelayOnOff = false -- No intial delay when spawning the first group. - self.SpawnGrouping = nil -- No grouping. - self.SpawnInitLivery = nil -- No special livery. - self.SpawnInitSkill = nil -- No special skill. - self.SpawnInitFreq = nil -- No special frequency. - self.SpawnInitModu = nil -- No special modulation. - self.SpawnInitRadio = nil -- No radio comms setting. + self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. + self.DelayOnOff = false -- No initial delay when spawning the first group. + self.SpawnGrouping = nil -- No grouping. + self.SpawnInitLivery = nil -- No special livery. + self.SpawnInitSkill = nil -- No special skill. + self.SpawnInitFreq = nil -- No special frequency. + self.SpawnInitModu = nil -- No special modulation. + self.SpawnInitRadio = nil -- No radio communication setting. self.SpawnInitModex = nil self.SpawnInitAirbase = nil - self.TweakedTemplate = false -- Check if the user is using self made template. + self.TweakedTemplate = false -- Check if the user is using self made template. + + self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. + else + error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) + end - self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. - else - error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) - end - self:SetEventPriority( 5 ) self.SpawnHookScheduler = SCHEDULER:New( nil ) - - return self -end + return self +end --- Creates a new SPAWN instance to create new groups based on the provided template. -- @param #SPAWN self @@ -403,86 +394,89 @@ end -- @usage -- -- Create a new SPAWN object based on a Group Template defined from scratch. -- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' ) --- @usage --- -- Create a new CSAR_Spawn object based on a normal Group Template to spawn a soldier. --- local CSAR_Spawn = SPAWN:NewWithFromTemplate( Template, "CSAR", "Pilot" ) +-- @usage +-- +-- -- Create a new CSAR_Spawn object based on a normal Group Template to spawn a soldier. +-- local CSAR_Spawn = SPAWN:NewWithFromTemplate( Template, "CSAR", "Pilot" ) +-- function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix ) local self = BASE:Inherit( self, BASE:New() ) self:F( { SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix } ) if SpawnAliasPrefix == nil or SpawnAliasPrefix == "" then - BASE:I("ERROR: in function NewFromTemplate, required paramter SpawnAliasPrefix is not set") + BASE:I( "ERROR: in function NewFromTemplate, required paramter SpawnAliasPrefix is not set" ) return nil end if SpawnTemplate then - self.SpawnTemplate = SpawnTemplate -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! + self.SpawnTemplate = SpawnTemplate -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! self.SpawnTemplatePrefix = SpawnTemplatePrefix self.SpawnAliasPrefix = SpawnAliasPrefix self.SpawnIndex = 0 - self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. - self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. - self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. - self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. - self.SpawnInitLimit = false -- By default, no InitLimit. - self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. - self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. - self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. - self.AIOnOff = true -- The AI is on by default when spawning a group. + self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. + self.AliveUnits = 0 -- Contains the counter how many units are currently alive + self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. + self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. + self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. + self.SpawnInitLimit = false -- By default, no InitLimit. + self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. + self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. + self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. + self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. + self.AIOnOff = true -- The AI is on by default when spawning a group. self.SpawnUnControlled = false - self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. - self.DelayOnOff = false -- No intial delay when spawning the first group. - self.Grouping = nil -- No grouping. - self.SpawnInitLivery = nil -- No special livery. - self.SpawnInitSkill = nil -- No special skill. - self.SpawnInitFreq = nil -- No special frequency. - self.SpawnInitModu = nil -- No special modulation. - self.SpawnInitRadio = nil -- No radio comms setting. + self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. + self.DelayOnOff = false -- No initial delay when spawning the first group. + self.Grouping = nil -- No grouping. + self.SpawnInitLivery = nil -- No special livery. + self.SpawnInitSkill = nil -- No special skill. + self.SpawnInitFreq = nil -- No special frequency. + self.SpawnInitModu = nil -- No special modulation. + self.SpawnInitRadio = nil -- No radio communication setting. self.SpawnInitModex = nil self.SpawnInitAirbase = nil - self.TweakedTemplate = true -- Check if the user is using self made template. - - self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. + self.TweakedTemplate = true -- Check if the user is using self made template. + + self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. else error( "There is no template provided for SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) end - + self:SetEventPriority( 5 ) self.SpawnHookScheduler = SCHEDULER:New( nil ) - + return self end - --- Stops any more repeat spawns from happening once the UNIT count of Alive units, spawned by the same SPAWN object, exceeds the first parameter. Also can stop spawns from happening once a total GROUP still alive is met. -- Exceptionally powerful when combined with SpawnSchedule for Respawning. -- Note that this method is exceptionally important to balance the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units. -- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this method should be used... -- When a @{#SPAWN.New} is executed and the limit of the amount of units alive is reached, then no new spawn will happen of the group, until some of these units of the spawn object will be destroyed. -- @param #SPAWN self --- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime. --- @param #number SpawnMaxGroups The maximum amount of groups that can be spawned. When the limit is reached, then no more actual spawns will happen of the group. --- This parameter is useful to define a maximum amount of airplanes, ground troops, helicopters, ships etc within a supply area. +-- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime. +-- @param #number SpawnMaxGroups The maximum amount of groups that can be spawned. When the limit is reached, then no more actual spawns will happen of the group. +-- This parameter is useful to define a maximum amount of airplanes, ground troops, helicopters, ships etc within a supply area. -- This parameter accepts the value 0, which defines that there are no maximum group limits, but there are limits on the maximum of units that can be alive at the same time. -- @return #SPAWN self -- @usage --- -- NATO helicopters engaging in the battle field. --- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE. --- -- There will be maximum 24 groups spawned during the whole mission lifetime. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitLimit( 2, 24 ) +-- +-- -- NATO helicopters engaging in the battle field. +-- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE. +-- -- There will be maximum 24 groups spawned during the whole mission lifetime. +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitLimit( 2, 24 ) +-- function SPAWN:InitLimit( SpawnMaxUnitsAlive, SpawnMaxGroups ) - self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } ) + self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } ) self.SpawnInitLimit = true - self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = SpawnMaxGroups -- The maximum amount of groups that can be spawned. - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_InitializeSpawnGroups( SpawnGroupID ) - end + self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. + self.SpawnMaxGroups = SpawnMaxGroups -- The maximum amount of groups that can be spawned. - return self + for SpawnGroupID = 1, self.SpawnMaxGroups do + self:_InitializeSpawnGroups( SpawnGroupID ) + end + + return self end --- Keeps the unit names as defined within the mission editor, @@ -493,23 +487,22 @@ end -- @param #boolean KeepUnitNames (optional) If true, the unit names are kept, false or not provided to make new unit names. -- @return #SPAWN self function SPAWN:InitKeepUnitNames( KeepUnitNames ) - self:F( ) + self:F() self.SpawnInitKeepUnitNames = KeepUnitNames or true - + return self end - --- Flags that the spawned groups must be spawned late activated. -- @param #SPAWN self -- @param #boolean LateActivated (optional) If true, the spawned groups are late activated. -- @return #SPAWN self function SPAWN:InitLateActivated( LateActivated ) - self:F( ) + self:F() self.LateActivated = LateActivated or true - + return self end @@ -517,47 +510,45 @@ end -- @param #SPAWN self -- @param #string AirbaseName Name of the airbase. -- @param #number Takeoff (Optional) Takeoff type. Can be SPAWN.Takeoff.Hot (default), SPAWN.Takeoff.Cold or SPAWN.Takeoff.Runway. --- @param #number TerminalTyple (Optional) The terminal type. +-- @param #number TerminalType (Optional) The terminal type. -- @return #SPAWN self function SPAWN:InitAirbase( AirbaseName, Takeoff, TerminalType ) - self:F( ) + self:F() + + self.SpawnInitAirbase = AIRBASE:FindByName( AirbaseName ) + + self.SpawnInitTakeoff = Takeoff or SPAWN.Takeoff.Hot + + self.SpawnInitTerminalType = TerminalType - self.SpawnInitAirbase=AIRBASE:FindByName(AirbaseName) - - self.SpawnInitTakeoff=Takeoff or SPAWN.Takeoff.Hot - - self.SpawnInitTerminalType=TerminalType - return self end - ---- Defines the Heading for the new spawned units. +--- Defines the Heading for the new spawned units. -- The heading can be given as one fixed degree, or can be randomized between minimum and maximum degrees. -- @param #SPAWN self -- @param #number HeadingMin The minimum or fixed heading in degrees. -- @param #number HeadingMax (optional) The maximum heading in degrees. This there is no maximum heading, then the heading will be fixed for all units using minimum heading. -- @return #SPAWN self -- @usage --- --- Spawn = SPAWN:New( ... ) --- --- -- Spawn the units pointing to 100 degrees. --- Spawn:InitHeading( 100 ) --- --- -- Spawn the units pointing between 100 and 150 degrees. --- Spawn:InitHeading( 100, 150 ) --- +-- +-- Spawn = SPAWN:New( ... ) +-- +-- -- Spawn the units pointing to 100 degrees. +-- Spawn:InitHeading( 100 ) +-- +-- -- Spawn the units pointing between 100 and 150 degrees. +-- Spawn:InitHeading( 100, 150 ) +-- function SPAWN:InitHeading( HeadingMin, HeadingMax ) - self:F( ) + self:F() self.SpawnInitHeadingMin = HeadingMin self.SpawnInitHeadingMax = HeadingMax - + return self end - --- Defines the heading of the overall formation of the new spawned group. -- The heading can be given as one fixed degree, or can be randomized between minimum and maximum degrees. -- The Group's formation as laid out in its template will be rotated around the first unit in the group @@ -569,116 +560,114 @@ end -- @param #number unitVar (optional) Individual units within the group will have their heading randomized by +/- unitVar degrees. Default is zero. -- @return #SPAWN self -- @usage --- +-- -- mySpawner = SPAWN:New( ... ) --- --- -- Spawn the Group with the formation rotated +100 degrees around unit #1, compared to the mission template. --- mySpawner:InitGroupHeading( 100 ) --- --- Spawn the Group with the formation rotated units between +100 and +150 degrees around unit #1, compared to the mission template, and with individual units varying by +/- 10 degrees from their templated facing. --- mySpawner:InitGroupHeading( 100, 150, 10 ) --- --- Spawn the Group with the formation rotated -60 degrees around unit #1, compared to the mission template, but with all units facing due north regardless of how they were laid out in the template. --- mySpawner:InitGroupHeading(-60):InitHeading(0) --- or --- mySpawner:InitHeading(0):InitGroupHeading(-60) --- +-- +-- -- Spawn the Group with the formation rotated +100 degrees around unit #1, compared to the mission template. +-- mySpawner:InitGroupHeading( 100 ) +-- +-- -- Spawn the Group with the formation rotated units between +100 and +150 degrees around unit #1, compared to the mission template, and with individual units varying by +/- 10 degrees from their templated facing. +-- mySpawner:InitGroupHeading( 100, 150, 10 ) +-- +-- -- Spawn the Group with the formation rotated -60 degrees around unit #1, compared to the mission template, but with all units facing due north regardless of how they were laid out in the template. +-- mySpawner:InitGroupHeading(-60):InitHeading(0) +-- -- or +-- mySpawner:InitHeading(0):InitGroupHeading(-60) +-- function SPAWN:InitGroupHeading( HeadingMin, HeadingMax, unitVar ) - self:F({HeadingMin=HeadingMin, HeadingMax=HeadingMax, unitVar=unitVar}) + self:F( { HeadingMin = HeadingMin, HeadingMax = HeadingMax, unitVar = unitVar } ) self.SpawnInitGroupHeadingMin = HeadingMin self.SpawnInitGroupHeadingMax = HeadingMax - self.SpawnInitGroupUnitVar = unitVar + self.SpawnInitGroupUnitVar = unitVar return self end - --- Sets the coalition of the spawned group. Note that it might be necessary to also set the country explicitly! --- @param #SPAWN self +-- @param #SPAWN self -- @param DCS#coalition.side Coalition Coalition of the group as number of enumerator: --- --- * @{DCS#coaliton.side.NEUTRAL} --- * @{DCS#coaliton.side.RED} +-- +-- * @{DCS#coalition.side.NEUTRAL} +-- * @{DCS#coalition.side.RED} -- * @{DCS#coalition.side.BLUE} --- +-- -- @return #SPAWN self function SPAWN:InitCoalition( Coalition ) - self:F({coalition=Coalition}) + self:F( { coalition = Coalition } ) self.SpawnInitCoalition = Coalition - + return self end ---- Sets the country of the spawn group. Note that the country determins the coalition of the group depending on which country is defined to be on which side for each specific mission! --- @param #SPAWN self +--- Sets the country of the spawn group. Note that the country determines the coalition of the group depending on which country is defined to be on which side for each specific mission! +-- @param #SPAWN self -- @param #number Country Country id as number or enumerator: --- +-- -- * @{DCS#country.id.RUSSIA} -- * @{DCS#county.id.USA} --- +-- -- @return #SPAWN self function SPAWN:InitCountry( Country ) - self:F( ) + self:F() self.SpawnInitCountry = Country - + return self end - --- Sets category ID of the group. --- @param #SPAWN self +-- @param #SPAWN self -- @param #number Category Category id. -- @return #SPAWN self function SPAWN:InitCategory( Category ) - self:F( ) + self:F() self.SpawnInitCategory = Category - + return self end --- Sets livery of the group. --- @param #SPAWN self --- @param #string Livery Livery name. Note that this is not necessarily the same name as displayed in the mission edior. +-- @param #SPAWN self +-- @param #string Livery Livery name. Note that this is not necessarily the same name as displayed in the mission editor. -- @return #SPAWN self function SPAWN:InitLivery( Livery ) - self:F({livery=Livery} ) + self:F( { livery = Livery } ) self.SpawnInitLivery = Livery - + return self end --- Sets skill of the group. --- @param #SPAWN self +-- @param #SPAWN self -- @param #string Skill Skill, possible values "Average", "Good", "High", "Excellent" or "Random". -- @return #SPAWN self function SPAWN:InitSkill( Skill ) - self:F({skill=Skill}) - if Skill:lower()=="average" then - self.SpawnInitSkill="Average" - elseif Skill:lower()=="good" then - self.SpawnInitSkill="Good" - elseif Skill:lower()=="excellent" then - self.SpawnInitSkill="Excellent" - elseif Skill:lower()=="random" then - self.SpawnInitSkill="Random" + self:F( { skill = Skill } ) + if Skill:lower() == "average" then + self.SpawnInitSkill = "Average" + elseif Skill:lower() == "good" then + self.SpawnInitSkill = "Good" + elseif Skill:lower() == "excellent" then + self.SpawnInitSkill = "Excellent" + elseif Skill:lower() == "random" then + self.SpawnInitSkill = "Random" else - self.SpawnInitSkill="High" + self.SpawnInitSkill = "High" end - + return self end ---- Sets the radio comms on or off. Same as checking/unchecking the COMM box in the mission editor. --- @param #SPAWN self --- @param #number switch If true (or nil), enables the radio comms. If false, disables the radio for the spawned group. +--- Sets the radio communication on or off. Same as checking/unchecking the COMM box in the mission editor. +-- @param #SPAWN self +-- @param #number switch If true (or nil), enables the radio communication. If false, disables the radio for the spawned group. -- @return #SPAWN self -function SPAWN:InitRadioCommsOnOff(switch) - self:F({switch=switch} ) - self.SpawnInitRadio=switch or true +function SPAWN:InitRadioCommsOnOff( switch ) + self:F( { switch = switch } ) + self.SpawnInitRadio = switch or true return self end @@ -686,11 +675,11 @@ end -- @param #SPAWN self -- @param #number frequency The frequency in MHz. -- @return #SPAWN self -function SPAWN:InitRadioFrequency(frequency) - self:F({frequency=frequency} ) +function SPAWN:InitRadioFrequency( frequency ) + self:F( { frequency = frequency } ) + + self.SpawnInitFreq = frequency - self.SpawnInitFreq=frequency - return self end @@ -698,64 +687,65 @@ end -- @param #SPAWN self -- @param #string modulation Either "FM" or "AM". If no value is given, modulation is set to AM. -- @return #SPAWN self -function SPAWN:InitRadioModulation(modulation) - self:F({modulation=modulation}) - if modulation and modulation:lower()=="fm" then - self.SpawnInitModu=radio.modulation.FM +function SPAWN:InitRadioModulation( modulation ) + self:F( { modulation = modulation } ) + if modulation and modulation:lower() == "fm" then + self.SpawnInitModu = radio.modulation.FM else - self.SpawnInitModu=radio.modulation.AM + self.SpawnInitModu = radio.modulation.AM end return self end --- Sets the modex of the first unit of the group. If more units are in the group, the number is increased by one with every unit. --- @param #SPAWN self +-- @param #SPAWN self -- @param #number modex Modex of the first unit. -- @return #SPAWN self -function SPAWN:InitModex(modex) +function SPAWN:InitModex( modex ) if modex then - self.SpawnInitModex=tonumber(modex) + self.SpawnInitModex = tonumber( modex ) end - + return self end - ---- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behaviour of groups. +--- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behavior of groups. -- @param #SPAWN self --- @param #number SpawnStartPoint is the waypoint where the randomization begins. +-- @param #number SpawnStartPoint is the waypoint where the randomization begins. -- Note that the StartPoint = 0 equaling the point where the group is spawned. --- @param #number SpawnEndPoint is the waypoint where the randomization ends counting backwards. +-- @param #number SpawnEndPoint is the waypoint where the randomization ends counting backwards. -- This parameter is useful to avoid randomization to end at a waypoint earlier than the last waypoint on the route. -- @param #number SpawnRadius is the radius in meters in which the randomization of the new waypoints, with the original waypoint of the original template located in the middle ... -- @param #number SpawnHeight (optional) Specifies the **additional** height in meters that can be added to the base height specified at each waypoint in the ME. -- @return #SPAWN -- @usage --- -- NATO helicopters engaging in the battle field. --- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). --- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. --- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +-- +-- -- NATO helicopters engaging in the battle field. +-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). +-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. +-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +-- function SPAWN:InitRandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) - self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } ) + self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } ) - self.SpawnRandomizeRoute = true - self.SpawnRandomizeRouteStartPoint = SpawnStartPoint - self.SpawnRandomizeRouteEndPoint = SpawnEndPoint - self.SpawnRandomizeRouteRadius = SpawnRadius - self.SpawnRandomizeRouteHeight = SpawnHeight + self.SpawnRandomizeRoute = true + self.SpawnRandomizeRouteStartPoint = SpawnStartPoint + self.SpawnRandomizeRouteEndPoint = SpawnEndPoint + self.SpawnRandomizeRouteRadius = SpawnRadius + self.SpawnRandomizeRouteHeight = SpawnHeight - for GroupID = 1, self.SpawnMaxGroups do - self:_RandomizeRoute( GroupID ) - end - - return self + for GroupID = 1, self.SpawnMaxGroups do + self:_RandomizeRoute( GroupID ) + end + + return self end --- Randomizes the position of @{Wrapper.Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens. -- @param #SPAWN self --- @param #boolean RandomizePosition If true, SPAWN will perform the randomization of the @{Wrapper.Group}s position between a given outer and inner radius. +-- @param #boolean RandomizePosition If true, SPAWN will perform the randomization of the @{Wrapper.Group}s position between a given outer and inner radius. -- @param DCS#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. -- @param DCS#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. -- @return #SPAWN @@ -769,23 +759,24 @@ function SPAWN:InitRandomizePosition( RandomizePosition, OuterRadius, InnerRadiu for GroupID = 1, self.SpawnMaxGroups do self:_RandomizeRoute( GroupID ) end - + return self end - --- Randomizes the UNITs that are spawned within a radius band given an Outer and Inner radius. -- @param #SPAWN self --- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius. +-- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius. -- @param DCS#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. -- @param DCS#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. -- @return #SPAWN -- @usage --- -- NATO helicopters engaging in the battle field. --- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). --- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. --- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +-- +-- -- NATO helicopters engaging in the battle field. +-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). +-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. +-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +-- function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius ) self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } ) @@ -796,65 +787,67 @@ function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius ) for GroupID = 1, self.SpawnMaxGroups do self:_RandomizeRoute( GroupID ) end - + return self end --- This method is rather complicated to understand. But I'll try to explain. --- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, +-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, -- but they will all follow the same Template route and have the same prefix name. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self --- @param #string SpawnTemplatePrefixTable A table with the names of the groups defined within the mission editor, from which one will be choosen when a new group will be spawned. +-- @param #string SpawnTemplatePrefixTable A table with the names of the groups defined within the mission editor, from which one will be chosen when a new group will be spawned. -- @return #SPAWN -- @usage --- -- NATO Tank Platoons invading Gori. --- -- Choose between 13 different 'US Tank Platoon' configurations for each new SPAWN the Group to be spawned for the --- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SpawnTemplatePrefixes. --- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and --- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission. --- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5', --- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10', --- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' } --- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +-- +-- -- NATO Tank Platoons invading Gori. +-- -- Choose between 13 different 'US Tank Platoon' configurations for each new SPAWN the Group to be spawned for the +-- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SpawnTemplatePrefixes. +-- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and +-- -- with a limit set of maximum 12 Units alive simultaneously and 150 Groups to be spawned during the whole mission. +-- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5', +-- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10', +-- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' } +-- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable ) - self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } ) + self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } ) - self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable - self.SpawnRandomizeTemplate = true + self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable + self.SpawnRandomizeTemplate = true - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_RandomizeTemplate( SpawnGroupID ) - end - - return self + for SpawnGroupID = 1, self.SpawnMaxGroups do + self:_RandomizeTemplate( SpawnGroupID ) + end + + return self end - --- Randomize templates to be used as the unit representatives for the Spawned group, defined using a SET_GROUP object. --- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, +-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, -- but they will all follow the same Template route and have the same prefix name. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self --- @param Core.Set#SET_GROUP SpawnTemplateSet A SET_GROUP object set, that contains the groups that are possible unit representatives of the group to be spawned. +-- @param Core.Set#SET_GROUP SpawnTemplateSet A SET_GROUP object set, that contains the groups that are possible unit representatives of the group to be spawned. -- @return #SPAWN -- @usage --- -- NATO Tank Platoons invading Gori. --- --- -- Choose between different 'US Tank Platoon Template' configurations to be spawned for the --- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SPAWN objects. --- --- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and --- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission. --- --- Spawn_US_PlatoonSet = SET_GROUP:New():FilterPrefixes( "US Tank Platoon Templates" ):FilterOnce() --- --- --- Now use the Spawn_US_PlatoonSet to define the templates using InitRandomizeTemplateSet. --- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) +-- +-- -- NATO Tank Platoons invading Gori. +-- +-- -- Choose between different 'US Tank Platoon Template' configurations to be spawned for the +-- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SPAWN objects. +-- +-- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and +-- -- with a limit set of maximum 12 Units alive simultaneously and 150 Groups to be spawned during the whole mission. +-- +-- Spawn_US_PlatoonSet = SET_GROUP:New():FilterPrefixes( "US Tank Platoon Templates" ):FilterOnce() +-- +-- -- Now use the Spawn_US_PlatoonSet to define the templates using InitRandomizeTemplateSet. +-- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) +-- function SPAWN:InitRandomizeTemplateSet( SpawnTemplateSet ) -- R2.3 self:F( { self.SpawnTemplatePrefix } ) @@ -864,44 +857,44 @@ function SPAWN:InitRandomizeTemplateSet( SpawnTemplateSet ) -- R2.3 for SpawnGroupID = 1, self.SpawnMaxGroups do self:_RandomizeTemplate( SpawnGroupID ) end - + return self end - --- Randomize templates to be used as the unit representatives for the Spawned group, defined by specifying the prefix names. --- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, +-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, -- but they will all follow the same Template route and have the same prefix name. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self -- @param #string SpawnTemplatePrefixes A string or a list of string that contains the prefixes of the groups that are possible unit representatives of the group to be spawned. -- @return #SPAWN -- @usage --- -- NATO Tank Platoons invading Gori. --- --- -- Choose between different 'US Tank Platoon Templates' configurations to be spawned for the --- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SPAWN objects. --- --- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and --- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission. --- --- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) -function SPAWN:InitRandomizeTemplatePrefixes( SpawnTemplatePrefixes ) --R2.3 +-- +-- -- NATO Tank Platoons invading Gori. +-- +-- -- Choose between different 'US Tank Platoon Templates' configurations to be spawned for the +-- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SPAWN objects. +-- +-- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and +-- -- with a limit set of maximum 12 Units alive simultaneously and 150 Groups to be spawned during the whole mission. +-- +-- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) +-- +function SPAWN:InitRandomizeTemplatePrefixes( SpawnTemplatePrefixes ) -- R2.3 self:F( { self.SpawnTemplatePrefix } ) local SpawnTemplateSet = SET_GROUP:New():FilterPrefixes( SpawnTemplatePrefixes ):FilterOnce() self:InitRandomizeTemplateSet( SpawnTemplateSet ) - + return self end - --- When spawning a new group, make the grouping of the units according the InitGrouping setting. -- @param #SPAWN self --- @param #number Grouping Indicates the maximum amount of units in the group. +-- @param #number Grouping Indicates the maximum amount of units in the group. -- @return #SPAWN function SPAWN:InitGrouping( Grouping ) -- R2.2 self:F( { self.SpawnTemplatePrefix, Grouping } ) @@ -911,22 +904,21 @@ function SPAWN:InitGrouping( Grouping ) -- R2.2 return self end - - --- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types. -- @param #SPAWN self --- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects. +-- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects. -- @return #SPAWN -- @usage --- -- Create a zone table of the 2 zones. --- ZoneTable = { ZONE:New( "Zone1" ), ZONE:New( "Zone2" ) } --- --- Spawn_Vehicle_1 = SPAWN:New( "Spawn Vehicle 1" ) --- :InitLimit( 10, 10 ) --- :InitRandomizeRoute( 1, 1, 200 ) --- :InitRandomizeZones( ZoneTable ) --- :SpawnScheduled( 5, .5 ) --- +-- +-- -- Create a zone table of the 2 zones. +-- ZoneTable = { ZONE:New( "Zone1" ), ZONE:New( "Zone2" ) } +-- +-- Spawn_Vehicle_1 = SPAWN:New( "Spawn Vehicle 1" ) +-- :InitLimit( 10, 10 ) +-- :InitRandomizeRoute( 1, 1, 200 ) +-- :InitRandomizeZones( ZoneTable ) +-- :SpawnScheduled( 5, .5 ) +-- function SPAWN:InitRandomizeZones( SpawnZoneTable ) self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } ) @@ -936,111 +928,106 @@ function SPAWN:InitRandomizeZones( SpawnZoneTable ) for SpawnGroupID = 1, self.SpawnMaxGroups do self:_RandomizeZones( SpawnGroupID ) end - + return self end - - - - ---- For planes and helicopters, when these groups go home and land on their home airbases and farps, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment. --- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed. +--- For planes and helicopters, when these groups go home and land on their home airbases and FARPs, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment. +-- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed. -- This will enable a spawned group to be re-spawned after it lands, until it is destroyed... --- Note: When the group is respawned, it will re-spawn from the original airbase where it took off. +-- Note: When the group is respawned, it will re-spawn from the original airbase where it took off. -- So ensure that the routes for groups that respawn, always return to the original airbase, or players may get confused ... -- @param #SPAWN self -- @return #SPAWN self -- @usage --- -- RU Su-34 - AI Ship Attack --- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. --- SpawnRU_SU34 = SPAWN --- :New( 'Su-34' ) --- :Schedule( 2, 3, 1800, 0.4 ) --- :SpawnUncontrolled() --- :InitRandomizeRoute( 1, 1, 3000 ) --- :InitRepeatOnEngineShutDown() --- +-- +-- -- RU Su-34 - AI Ship Attack +-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. +-- SpawnRU_SU34 = SPAWN:New( 'Su-34' ) +-- :Schedule( 2, 3, 1800, 0.4 ) +-- :SpawnUncontrolled() +-- :InitRandomizeRoute( 1, 1, 3000 ) +-- :InitRepeatOnEngineShutDown() +-- function SPAWN:InitRepeat() - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) - self.Repeat = true - self.RepeatOnEngineShutDown = false - self.RepeatOnLanding = true + self.Repeat = true + self.RepeatOnEngineShutDown = false + self.RepeatOnLanding = true - return self + return self end --- Respawn group after landing. -- @param #SPAWN self -- @return #SPAWN self -- @usage --- -- RU Su-34 - AI Ship Attack --- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. --- SpawnRU_SU34 = SPAWN --- :New( 'Su-34' ) --- :InitRandomizeRoute( 1, 1, 3000 ) --- :InitRepeatOnLanding() --- :Spawn() +-- +-- -- RU Su-34 - AI Ship Attack +-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. +-- SpawnRU_SU34 = SPAWN:New( 'Su-34' ) +-- :InitRandomizeRoute( 1, 1, 3000 ) +-- :InitRepeatOnLanding() +-- :Spawn() +-- function SPAWN:InitRepeatOnLanding() - self:F( { self.SpawnTemplatePrefix } ) + self:F( { self.SpawnTemplatePrefix } ) - self:InitRepeat() - self.RepeatOnEngineShutDown = false - self.RepeatOnLanding = true - - return self + self:InitRepeat() + self.RepeatOnEngineShutDown = false + self.RepeatOnLanding = true + + return self end - --- Respawn after landing when its engines have shut down. -- @param #SPAWN self -- @return #SPAWN self -- @usage --- -- RU Su-34 - AI Ship Attack --- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. --- SpawnRU_SU34 = SPAWN --- :New( 'Su-34' ) --- :SpawnUncontrolled() --- :InitRandomizeRoute( 1, 1, 3000 ) --- :InitRepeatOnEngineShutDown() --- :Spawn() +-- +-- -- RU Su-34 - AI Ship Attack +-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. +-- SpawnRU_SU34 = SPAWN:New( 'Su-34' ) +-- :SpawnUncontrolled() +-- :InitRandomizeRoute( 1, 1, 3000 ) +-- :InitRepeatOnEngineShutDown() +-- :Spawn() function SPAWN:InitRepeatOnEngineShutDown() - self:F( { self.SpawnTemplatePrefix } ) + self:F( { self.SpawnTemplatePrefix } ) - self:InitRepeat() - self.RepeatOnEngineShutDown = true - self.RepeatOnLanding = false - - return self + self:InitRepeat() + self.RepeatOnEngineShutDown = true + self.RepeatOnLanding = false + + return self end - --- Delete groups that have not moved for X seconds - AIR ONLY!!! -- DO NOT USE ON GROUPS THAT DO NOT MOVE OR YOUR SERVER WILL BURN IN HELL (Pikes - April 2020) -- When groups are still alive and have become inactive due to damage and are unable to contribute anything, then this group will be removed at defined intervals in seconds. -- @param #SPAWN self -- @param #string SpawnCleanUpInterval The interval to check for inactive groups within seconds. -- @return #SPAWN self --- @usage --- Spawn_Helicopter:InitCleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive. +-- @usage +-- +-- Spawn_Helicopter:InitCleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive. +-- function SPAWN:InitCleanUp( SpawnCleanUpInterval ) - self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } ) + self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } ) - self.SpawnCleanUpInterval = SpawnCleanUpInterval - self.SpawnCleanUpTimeStamps = {} + self.SpawnCleanUpInterval = SpawnCleanUpInterval + self.SpawnCleanUpTimeStamps = {} local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() self:T( { "CleanUp Scheduler:", SpawnGroup } ) - - --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval ) - self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 ) - return self + + -- self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval ) + self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 ) + return self end - - ---- Makes the groups visible before start (like a batallion). +--- Makes the groups visible before start (like a battalion). -- The method will take the position of the group as the first position in the array. -- CAUTION: this directive will NOT work with OnSpawnGroup function. -- @param #SPAWN self @@ -1050,45 +1037,45 @@ end -- @param #number SpawnDeltaY The space between each Group on the Y-axis. -- @return #SPAWN self -- @usage --- -- Define an array of Groups. --- Spawn_BE_Ground = SPAWN --- :New( 'BE Ground' ) --- :InitLimit( 2, 24 ) --- :InitArray( 90, 10, 100, 50 ) --- +-- +-- -- Define an array of Groups. +-- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ) +-- :InitLimit( 2, 24 ) +-- :InitArray( 90, 10, 100, 50 ) +-- function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) - self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } ) + self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } ) - self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start. - - local SpawnX = 0 - local SpawnY = 0 - local SpawnXIndex = 0 - local SpawnYIndex = 0 - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:T( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } ) + self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start. - self.SpawnGroups[SpawnGroupID].Visible = true - self.SpawnGroups[SpawnGroupID].Spawned = false - - SpawnXIndex = SpawnXIndex + 1 - if SpawnWidth and SpawnWidth ~= 0 then - if SpawnXIndex >= SpawnWidth then - SpawnXIndex = 0 - SpawnYIndex = SpawnYIndex + 1 - end - end + local SpawnX = 0 + local SpawnY = 0 + local SpawnXIndex = 0 + local SpawnYIndex = 0 - 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 + for SpawnGroupID = 1, self.SpawnMaxGroups do + self:T( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } ) + + self.SpawnGroups[SpawnGroupID].Visible = true + self.SpawnGroups[SpawnGroupID].Spawned = false + + SpawnXIndex = SpawnXIndex + 1 + if SpawnWidth and SpawnWidth ~= 0 then + if SpawnXIndex >= 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 ) @@ -1101,40 +1088,41 @@ function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) 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 + self.SpawnGroups[SpawnGroupID].Group = _DATABASE:Spawn( self.SpawnGroups[SpawnGroupID].SpawnTemplate ) + + SpawnX = SpawnXIndex * SpawnDeltaX + SpawnY = SpawnYIndex * SpawnDeltaY + end + + return self end do -- AI methods + --- Turns the AI On or Off for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @param #boolean AIOnOff A value of true sets the AI On, a value of false sets the AI Off. -- @return #SPAWN The SPAWN object function SPAWN:InitAIOnOff( AIOnOff ) - + self.AIOnOff = AIOnOff return self end - + --- Turns the AI On for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @return #SPAWN The SPAWN object function SPAWN:InitAIOn() - + return self:InitAIOnOff( true ) end - + --- Turns the AI Off for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @return #SPAWN The SPAWN object function SPAWN:InitAIOff() - + return self:InitAIOnOff( false ) end @@ -1147,24 +1135,24 @@ do -- Delay methods -- @param #boolean DelayOnOff A value of true sets the Delay On, a value of false sets the Delay Off. -- @return #SPAWN The SPAWN object function SPAWN:InitDelayOnOff( DelayOnOff ) - + self.DelayOnOff = DelayOnOff return self end - + --- Turns the Delay On for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @return #SPAWN The SPAWN object function SPAWN:InitDelayOn() - + return self:InitDelayOnOff( true ) end - + --- Turns the Delay Off for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @return #SPAWN The SPAWN object function SPAWN:InitDelayOff() - + return self:InitDelayOnOff( false ) end @@ -1175,14 +1163,14 @@ end -- Delay methods -- @param #SPAWN self -- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:Spawn() - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } ) if self.SpawnInitAirbase then - return self:SpawnAtAirbase(self.SpawnInitAirbase, self.SpawnInitTakeoff, nil, self.SpawnInitTerminalType) + return self:SpawnAtAirbase( self.SpawnInitAirbase, self.SpawnInitTakeoff, nil, self.SpawnInitTerminalType ) else - return self:SpawnWithIndex( self.SpawnIndex + 1 ) - end - + return self:SpawnWithIndex( self.SpawnIndex + 1 ) + end + end --- Will re-spawn a group based on a given index. @@ -1191,38 +1179,37 @@ end -- @param #string SpawnIndex The index of the group to be spawned. -- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:ReSpawn( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) - - if not SpawnIndex then - SpawnIndex = 1 - end + self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) --- TODO: This logic makes DCS crash and i don't know why (yet). -- ED (Pikes -- not in the least bit scary to see this, right?) - 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 + if not SpawnIndex then + SpawnIndex = 1 end - local SpawnGroup = self:SpawnWithIndex( SpawnIndex ) - if SpawnGroup and WayPoints then - -- If there were WayPoints set, then Re-Execute those WayPoints! - SpawnGroup:WayPointInitialize( WayPoints ) - SpawnGroup:WayPointExecute( 1, 5 ) - end - - if SpawnGroup.ReSpawnFunction then - SpawnGroup:ReSpawnFunction() - end - - SpawnGroup:ResetEvents() - - return SpawnGroup -end + -- TODO: This logic makes DCS crash and i don't know why (yet). -- ED (Pikes -- not in the least bit scary to see this, right?) + 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 + -- If there were WayPoints set, then Re-Execute those WayPoints! + SpawnGroup:WayPointInitialize( WayPoints ) + SpawnGroup:WayPointExecute( 1, 5 ) + end + + if SpawnGroup.ReSpawnFunction then + SpawnGroup:ReSpawnFunction() + end + + SpawnGroup:ResetEvents() + + return SpawnGroup +end --- Set the spawn index to a specified index number. -- This method can be used to "reset" the spawn counter to a specific index number. @@ -1234,23 +1221,22 @@ function SPAWN:SetSpawnIndex( SpawnIndex ) self.SpawnIndex = SpawnIndex or 0 end - --- Will spawn a group with a specified index number. -- Uses @{DATABASE} global object defined in MOOSE. -- @param #SPAWN self -- @param #string SpawnIndex The index of the group to be spawned. -- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) - 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 + self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) - local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate - self:T( SpawnTemplate.name ) + 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 @@ -1265,46 +1251,46 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) 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 ) + 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 RandomizeUnits, then Randomize the formation at the start point. 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 ) + self:T( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end - - -- Get correct heading in Radians. - local function _Heading(courseDeg) - local h - if courseDeg<=180 then - h=math.rad(courseDeg) - else - h=-math.rad(360-courseDeg) - end - return h - end - local Rad180 = math.rad(180) - local function _HeadingRad(courseRad) - if courseRad<=Rad180 then + -- Get correct heading in Radians. + local function _Heading( courseDeg ) + local h + if courseDeg <= 180 then + h = math.rad( courseDeg ) + else + h = -math.rad( 360 - courseDeg ) + end + return h + end + + local Rad180 = math.rad( 180 ) + local function _HeadingRad( courseRad ) + if courseRad <= Rad180 then return courseRad else - return -((2*Rad180)-courseRad) + return -((2 * Rad180) - courseRad) end - end + end -- Generate a random value somewhere between two floating point values. - local function _RandomInRange ( min, max ) + local function _RandomInRange( min, max ) if min and max then - return min + ( math.random()*(max-min) ) + return min + (math.random() * (max - min)) else return min end @@ -1318,39 +1304,39 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) local pivotX = SpawnTemplate.units[1].x -- unit #1 is the pivot point local pivotY = SpawnTemplate.units[1].y - local headingRad = math.rad(_RandomInRange(self.SpawnInitGroupHeadingMin or 0,self.SpawnInitGroupHeadingMax)) - local cosHeading = math.cos(headingRad) - local sinHeading = math.sin(headingRad) - - local unitVarRad = math.rad(self.SpawnInitGroupUnitVar or 0) + local headingRad = math.rad( _RandomInRange( self.SpawnInitGroupHeadingMin or 0, self.SpawnInitGroupHeadingMax ) ) + local cosHeading = math.cos( headingRad ) + local sinHeading = math.sin( headingRad ) + + local unitVarRad = math.rad( self.SpawnInitGroupUnitVar or 0 ) for UnitID = 1, #SpawnTemplate.units do - + if UnitID > 1 then -- don't rotate position of unit #1 local unitXOff = SpawnTemplate.units[UnitID].x - pivotX -- rotate position offset around unit #1 local unitYOff = SpawnTemplate.units[UnitID].y - pivotY - SpawnTemplate.units[UnitID].x = pivotX + (unitXOff*cosHeading) - (unitYOff*sinHeading) - SpawnTemplate.units[UnitID].y = pivotY + (unitYOff*cosHeading) + (unitXOff*sinHeading) + SpawnTemplate.units[UnitID].x = pivotX + (unitXOff * cosHeading) - (unitYOff * sinHeading) + SpawnTemplate.units[UnitID].y = pivotY + (unitYOff * cosHeading) + (unitXOff * sinHeading) end - + -- adjust heading of all units, including unit #1 local unitHeading = SpawnTemplate.units[UnitID].heading + headingRad -- add group rotation to units default rotation - SpawnTemplate.units[UnitID].heading = _HeadingRad(_RandomInRange(unitHeading-unitVarRad, unitHeading+unitVarRad)) + SpawnTemplate.units[UnitID].heading = _HeadingRad( _RandomInRange( unitHeading - unitVarRad, unitHeading + unitVarRad ) ) SpawnTemplate.units[UnitID].psi = -SpawnTemplate.units[UnitID].heading - + end end - + -- If Heading is given, point all the units towards the given Heading. Overrides any heading set in InitGroupHeading above. if self.SpawnInitHeadingMin then for UnitID = 1, #SpawnTemplate.units do - SpawnTemplate.units[UnitID].heading = _Heading(_RandomInRange(self.SpawnInitHeadingMin, self.SpawnInitHeadingMax)) + SpawnTemplate.units[UnitID].heading = _Heading( _RandomInRange( self.SpawnInitHeadingMin, self.SpawnInitHeadingMax ) ) SpawnTemplate.units[UnitID].psi = -SpawnTemplate.units[UnitID].heading end end - + -- Set livery. if self.SpawnInitLivery then for UnitID = 1, #SpawnTemplate.units do @@ -1368,39 +1354,38 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) -- Set tail number. if self.SpawnInitModex then for UnitID = 1, #SpawnTemplate.units do - SpawnTemplate.units[UnitID].onboard_num = string.format("%03d", self.SpawnInitModex+(UnitID-1)) + SpawnTemplate.units[UnitID].onboard_num = string.format( "%03d", self.SpawnInitModex + (UnitID - 1) ) end end - + -- Set radio comms on/off. if self.SpawnInitRadio then - SpawnTemplate.communication=self.SpawnInitRadio - end - + SpawnTemplate.communication = self.SpawnInitRadio + end + -- Set radio frequency. if self.SpawnInitFreq then - SpawnTemplate.frequency=self.SpawnInitFreq + SpawnTemplate.frequency = self.SpawnInitFreq end - + -- Set radio modulation. if self.SpawnInitModu then - SpawnTemplate.modulation=self.SpawnInitModu - end - - -- Set country, coaliton and categroy. - SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID - SpawnTemplate.CountryID = self.SpawnInitCountry or SpawnTemplate.CountryID - SpawnTemplate.CoalitionID = self.SpawnInitCoalition or SpawnTemplate.CoalitionID - - --- 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 + SpawnTemplate.modulation = self.SpawnInitModu + end + + -- Set country, coalition and category. + SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID + SpawnTemplate.CountryID = self.SpawnInitCountry or SpawnTemplate.CountryID + SpawnTemplate.CoalitionID = self.SpawnInitCoalition or SpawnTemplate.CoalitionID + + -- 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 - - if not NoBirth then + + if not NoBirth then self:HandleEvent( EVENTS.Birth, self._OnBirth ) end self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash ) @@ -1414,37 +1399,36 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) self:HandleEvent( EVENTS.EngineShutdown, self._OnEngineShutDown ) end - self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( SpawnTemplate ) - - local SpawnGroup = self.SpawnGroups[self.SpawnIndex].Group -- Wrapper.Group#GROUP - - --TODO: Need to check if this function doesn't need to be scheduled, as the group may not be immediately there! + self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( SpawnTemplate ) + + local SpawnGroup = self.SpawnGroups[self.SpawnIndex].Group -- Wrapper.Group#GROUP + + -- TODO: Need to check if this function doesn't need to be scheduled, as the group may not be immediately there! if SpawnGroup then - - SpawnGroup:SetAIOnOff( self.AIOnOff ) - end + + SpawnGroup:SetAIOnOff( self.AIOnOff ) + end self:T3( SpawnTemplate.name ) - - -- If there is a SpawnFunction hook defined, call it. - if self.SpawnFunctionHook then - -- delay calling this for .1 seconds so that it hopefully comes after the BIRTH event of the group. - self.SpawnHookScheduler:Schedule( nil, self.SpawnFunctionHook, { self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments)}, 0.1 ) - end - -- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats. - --if self.Repeat then - -- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" ) - --end - end - - - self.SpawnGroups[self.SpawnIndex].Spawned = true - return self.SpawnGroups[self.SpawnIndex].Group - else - --self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } ) - end - return nil + -- If there is a SpawnFunction hook defined, call it. + if self.SpawnFunctionHook then + -- delay calling this for .1 seconds so that it hopefully comes after the BIRTH event of the group. + self.SpawnHookScheduler:Schedule( nil, self.SpawnFunctionHook, { self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) }, 0.1 ) + end + -- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats. + -- if self.Repeat then + -- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" ) + -- end + end + + self.SpawnGroups[self.SpawnIndex].Spawned = true + return self.SpawnGroups[self.SpawnIndex].Group + else + -- self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } ) + end + + return nil end --- Spawns new groups at varying time intervals. @@ -1452,29 +1436,29 @@ end -- @param #SPAWN self -- @param #number SpawnTime The time interval defined in seconds between each new spawn of new groups. -- @param #number SpawnTimeVariation The variation to be applied on the defined time interval between each new spawn. --- The variation is a number between 0 and 1, representing the %-tage of variation to be applied on the time interval. +-- The variation is a number between 0 and 1, representing the % of variation to be applied on the time interval. -- @return #SPAWN self -- @usage -- -- NATO helicopters engaging in the battle field. -- -- The time interval is set to SPAWN new helicopters between each 600 seconds, with a time variation of 50%. --- -- The time variation in this case will be between 450 seconds and 750 seconds. --- -- This is calculated as follows: --- -- Low limit: 600 * ( 1 - 0.5 / 2 ) = 450 +-- -- The time variation in this case will be between 450 seconds and 750 seconds. +-- -- This is calculated as follows: +-- -- Low limit: 600 * ( 1 - 0.5 / 2 ) = 450 -- -- High limit: 600 * ( 1 + 0.5 / 2 ) = 750 --- -- Between these two values, a random amount of seconds will be choosen for each new spawn of the helicopters. +-- -- Between these two values, a random amount of seconds will be chosen for each new spawn of the helicopters. -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):SpawnScheduled( 600, 0.5 ) function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation ) - self:F( { 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 + 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 + end - return self + return self end --- Will re-start the spawning scheduler. @@ -1493,12 +1477,11 @@ end -- @return #SPAWN function SPAWN:SpawnScheduleStop() self:F( { self.SpawnTemplatePrefix } ) - + self.SpawnScheduler:Stop() return self end - --- Allows to place a CallFunction hook when a new group spawns. -- The provided method will be called when a new group is spawned, including its given parameters. -- The first parameter of the SpawnFunction is the @{Wrapper.Group#GROUP} that was spawned. @@ -1507,17 +1490,16 @@ end -- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns. -- @return #SPAWN -- @usage --- -- Declare SpawnObject and call a function when a new Group is spawned. --- local SpawnObject = SPAWN --- :New( "SpawnObject" ) --- :InitLimit( 2, 10 ) --- :OnSpawnGroup( --- function( SpawnGroup ) --- SpawnGroup:E( "I am spawned" ) --- end --- ) --- :SpawnScheduled( 300, 0.3 ) --- +-- +-- -- Declare SpawnObject and call a function when a new Group is spawned. +-- local SpawnObject = SPAWN:New( "SpawnObject" ) +-- :InitLimit( 2, 10 ) +-- :OnSpawnGroup( function( SpawnGroup ) +-- SpawnGroup:E( "I am spawned" ) +-- end +-- ) +-- :SpawnScheduled( 300, 0.3 ) +-- function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... ) self:F( "OnSpawnGroup" ) @@ -1525,109 +1507,110 @@ function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... ) self.SpawnFunctionArguments = {} if arg then self.SpawnFunctionArguments = arg - end + end return self end ---- Will spawn a group at an @{Wrapper.Airbase}. +--- Will spawn a group at an @{Wrapper.Airbase}. -- This method is mostly advisable to be used if you want to simulate spawning units at an airbase. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. --- +-- -- The @{Wrapper.Airbase#AIRBASE} object must refer to a valid airbase known in the sim. -- You can use the following enumerations to search for the pre-defined airbases on the current known maps of DCS: --- --- * @{Wrapper.Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map. --- * @{Wrapper.Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map. --- * @{Wrapper.Airbase#AIRBASE.Normandy}: The airbases on the Normandy map. --- --- Use the method @{Wrapper.Airbase#AIRBASE.FindByName}() to retrieve the airbase object. +-- +-- * @{Wrapper.Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map. +-- * @{Wrapper.Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map. +-- * @{Wrapper.Airbase#AIRBASE.Normandy}: The airbases on the Normandy map. +-- +-- Use the method @{Wrapper.Airbase#AIRBASE.FindByName}() to retrieve the airbase object. -- The known AIRBASE objects are automatically imported at mission start by MOOSE. -- Therefore, there isn't any New() constructor defined for AIRBASE objects. --- --- Ships and Farps are added within the mission, and are therefore not known. +-- +-- Ships and FARPs are added within the mission, and are therefore not known. -- For these AIRBASE objects, there isn't an @{Wrapper.Airbase#AIRBASE} enumeration defined. -- You need to provide the **exact name** of the airbase as the parameter to the @{Wrapper.Airbase#AIRBASE.FindByName}() method! --- +-- -- @param #SPAWN self -- @param Wrapper.Airbase#AIRBASE SpawnAirbase The @{Wrapper.Airbase} where to spawn the group. -- @param #SPAWN.Takeoff Takeoff (optional) The location and takeoff method. Default is Hot. -- @param #number TakeoffAltitude (optional) The altitude above the ground. -- @param Wrapper.Airbase#AIRBASE.TerminalType TerminalType (optional) The terminal type the aircraft should be spawned at. See @{Wrapper.Airbase#AIRBASE.TerminalType}. -- @param #boolean EmergencyAirSpawn (optional) If true (default), groups are spawned in air if there is no parking spot at the airbase. If false, nothing is spawned if no parking spot is available. --- @param #table Parkingdata (optional) Table holding the coordinates and terminal ids for all units of the group. Spawning will be forced to happen at exactily these spots! +-- @param #table Parkingdata (optional) Table holding the coordinates and terminal ids for all units of the group. Spawning will be forced to happen at exactly these spots! -- @return Wrapper.Group#GROUP The group that was spawned or nil when nothing was spawned. -- @usage +-- -- Spawn_Plane = SPAWN:New( "Plane" ) -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold ) -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Hot ) -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Runway ) --- +-- -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( "Carrier" ), SPAWN.Takeoff.Cold ) --- +-- -- Spawn_Heli = SPAWN:New( "Heli") --- +-- -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Cold" ), SPAWN.Takeoff.Cold ) -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Hot" ), SPAWN.Takeoff.Hot ) -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Runway" ), SPAWN.Takeoff.Runway ) -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Air" ), SPAWN.Takeoff.Air ) --- +-- -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "Carrier" ), SPAWN.Takeoff.Cold ) --- +-- -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold, nil, AIRBASE.TerminalType.OpenBig ) --- +-- function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType, EmergencyAirSpawn, Parkingdata ) -- R2.2, R2.4 self:F( { self.SpawnTemplatePrefix, SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType } ) -- Get position of airbase. local PointVec3 = SpawnAirbase:GetCoordinate() - self:T2(PointVec3) + self:T2( PointVec3 ) -- Set take off type. Default is hot. Takeoff = Takeoff or SPAWN.Takeoff.Hot - + -- By default, groups are spawned in air if no parking spot is available. - if EmergencyAirSpawn==nil then - EmergencyAirSpawn=true + if EmergencyAirSpawn == nil then + EmergencyAirSpawn = true end - + self:F( { SpawnIndex = self.SpawnIndex } ) - + if self:_GetSpawnIndex( self.SpawnIndex + 1 ) then - + -- Get group template. local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate - + self:F( { SpawnTemplate = SpawnTemplate } ) - + if SpawnTemplate then - + -- Check if the aircraft with the specified SpawnIndex is already spawned. -- If yes, ensure that the aircraft is spawned at the same aircraft spot. - + local GroupAlive = self:GetGroupFromIndex( self.SpawnIndex ) - + self:F( { GroupAlive = GroupAlive } ) -- Debug output self:T( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) - + -- Template group, unit and its attributes. - local TemplateGroup = GROUP:FindByName(self.SpawnTemplatePrefix) - local TemplateUnit=TemplateGroup:GetUnit(1) - + local TemplateGroup = GROUP:FindByName( self.SpawnTemplatePrefix ) + local TemplateUnit = TemplateGroup:GetUnit( 1 ) + -- General category of spawned group. - local group=TemplateGroup - local istransport=group:HasAttribute("Transports") and group:HasAttribute("Planes") - local isawacs=group:HasAttribute("AWACS") - local isfighter=group:HasAttribute("Fighters") or group:HasAttribute("Interceptors") or group:HasAttribute("Multirole fighters") or (group:HasAttribute("Bombers") and not group:HasAttribute("Strategic bombers")) - local isbomber=group:HasAttribute("Strategic bombers") - local istanker=group:HasAttribute("Tankers") - local ishelo=TemplateUnit:HasAttribute("Helicopters") - + local group = TemplateGroup + local istransport = group:HasAttribute( "Transports" ) and group:HasAttribute( "Planes" ) + local isawacs = group:HasAttribute( "AWACS" ) + local isfighter = group:HasAttribute( "Fighters" ) or group:HasAttribute( "Interceptors" ) or group:HasAttribute( "Multirole fighters" ) or (group:HasAttribute( "Bombers" ) and not group:HasAttribute( "Strategic bombers" )) + local isbomber = group:HasAttribute( "Strategic bombers" ) + local istanker = group:HasAttribute( "Tankers" ) + local ishelo = TemplateUnit:HasAttribute( "Helicopters" ) + -- Number of units in the group. With grouping this can actually differ from the template group size! - local nunits=#SpawnTemplate.units + local nunits = #SpawnTemplate.units -- First waypoint of the group. local SpawnPoint = SpawnTemplate.route.points[1] @@ -1641,7 +1624,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT local AirbaseID = SpawnAirbase:GetID() local AirbaseCategory = SpawnAirbase:GetAirbaseCategory() self:F( { AirbaseCategory = AirbaseCategory } ) - + -- Set airdromeId. if AirbaseCategory == Airbase.Category.SHIP then SpawnPoint.linkUnit = AirbaseID @@ -1654,71 +1637,70 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT end -- Set waypoint type/action. - SpawnPoint.alt = 0 - SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type + SpawnPoint.alt = 0 + SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action - + -- Check if we spawn on ground. - local spawnonground=not (Takeoff==SPAWN.Takeoff.Air) - self:T({spawnonground=spawnonground, TOtype=Takeoff, TOair=Takeoff==SPAWN.Takeoff.Air}) - + local spawnonground = not (Takeoff == SPAWN.Takeoff.Air) + self:T( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) + -- Check where we actually spawn if we spawn on ground. - local spawnonship=false - local spawnonfarp=false - local spawnonrunway=false - local spawnonairport=false - if spawnonground then + local spawnonship = false + local spawnonfarp = false + local spawnonrunway = false + local spawnonairport = false + if spawnonground then if AirbaseCategory == Airbase.Category.SHIP then - spawnonship=true + spawnonship = true elseif AirbaseCategory == Airbase.Category.HELIPAD then - spawnonfarp=true + spawnonfarp = true elseif AirbaseCategory == Airbase.Category.AIRDROME then - spawnonairport=true + spawnonairport = true end - spawnonrunway=Takeoff==SPAWN.Takeoff.Runway + spawnonrunway = Takeoff == SPAWN.Takeoff.Runway end - + -- Array with parking spots coordinates. - local parkingspots={} - local parkingindex={} + local parkingspots = {} + local parkingindex = {} local spots - + -- Spawn happens on ground, i.e. at an airbase, a FARP or a ship. if spawnonground and not SpawnTemplate.parked then - - + -- Number of free parking spots. - local nfree=0 - + local nfree = 0 + -- Set terminal type. - local termtype=TerminalType - if spawnonrunway then + local termtype = TerminalType + if spawnonrunway then if spawnonship then -- Looks like there are no runway spawn spots on the stennis! if ishelo then - termtype=AIRBASE.TerminalType.HelicopterUsable + termtype = AIRBASE.TerminalType.HelicopterUsable else - termtype=AIRBASE.TerminalType.OpenMedOrBig + termtype = AIRBASE.TerminalType.OpenMedOrBig end else - termtype=AIRBASE.TerminalType.Runway - end + termtype = AIRBASE.TerminalType.Runway + end end - + -- Scan options. Might make that input somehow. - local scanradius=50 - local scanunits=true - local scanstatics=true - local scanscenery=false - local verysafe=false - + local scanradius = 50 + local scanunits = true + local scanstatics = true + local scanscenery = false + local verysafe = false + -- Number of free parking spots at the airbase. if spawnonship or spawnonfarp or spawnonrunway then -- These places work procedural and have some kind of build in queue ==> Less effort. - self:T(string.format("Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) - nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype, true) - spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype, true) - --[[ + self:T( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true ) + spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true ) + --[[ elseif Parkingdata~=nil then -- Parking data explicitly set by user as input parameter. nfree=#Parkingdata @@ -1726,146 +1708,146 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT ]] else if ishelo then - if termtype==nil then + if termtype == nil then -- Helo is spawned. Try exclusive helo spots first. - self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly)) - spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata) - nfree=#spots - if nfree=1 then - + + -- On free spot required in these cases. + if nfree >= 1 then + -- All units get the same spot. DCS takes care of the rest. - for i=1,nunits do - table.insert(parkingspots, spots[1].Coordinate) - table.insert(parkingindex, spots[1].TerminalID) + for i = 1, nunits do + table.insert( parkingspots, spots[1].Coordinate ) + table.insert( parkingindex, spots[1].TerminalID ) end -- This is actually used... - PointVec3=spots[1].Coordinate - + PointVec3 = spots[1].Coordinate + else -- If there is absolutely no spot ==> air start! - _notenough=true + _notenough = true end - + elseif spawnonairport then - - if nfree>=nunits then - - for i=1,nunits do - table.insert(parkingspots, spots[i].Coordinate) - table.insert(parkingindex, spots[i].TerminalID) + + if nfree >= nunits then + + for i = 1, nunits do + table.insert( parkingspots, spots[i].Coordinate ) + table.insert( parkingindex, spots[i].TerminalID ) end - + else -- Not enough spots for the whole group ==> air start! - _notenough=true - end + _notenough = true + end end - + -- Not enough spots ==> Prepare airstart. if _notenough then - - if EmergencyAirSpawn and not self.SpawnUnControlled then - self:E(string.format("WARNING: Group %s has no parking spots at %s ==> air start!", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) - + + if EmergencyAirSpawn and not self.SpawnUnControlled then + self:E( string.format( "WARNING: Group %s has no parking spots at %s ==> air start!", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + -- Not enough parking spots at the airport ==> Spawn in air. - spawnonground=false - spawnonship=false - spawnonfarp=false - spawnonrunway=false - + spawnonground = false + spawnonship = false + spawnonfarp = false + spawnonrunway = false + -- Set waypoint type/action to turning point. - SpawnPoint.type = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -- type = Turning Point + SpawnPoint.type = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -- type = Turning Point SpawnPoint.action = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2] -- action = Turning Point - + -- Adjust altitude to be 500-1000 m above the airbase. - PointVec3.x=PointVec3.x+math.random(-500,500) - PointVec3.z=PointVec3.z+math.random(-500,500) + PointVec3.x = PointVec3.x + math.random( -500, 500 ) + PointVec3.z = PointVec3.z + math.random( -500, 500 ) if ishelo then - PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) + PointVec3.y = PointVec3:GetLandHeight() + math.random( 100, 1000 ) else -- Randomize position so that multiple AC wont be spawned on top even in air. - PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500) + PointVec3.y = PointVec3:GetLandHeight() + math.random( 500, 2500 ) end - - Takeoff=GROUP.Takeoff.Air + + Takeoff = GROUP.Takeoff.Air else - self:E(string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) + self:E( string.format( "WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) return nil end end - + else - + -- Air start requested initially ==> Set altitude. if TakeoffAltitude then - PointVec3.y=TakeoffAltitude + PointVec3.y = TakeoffAltitude else if ishelo then - PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) + PointVec3.y = PointVec3:GetLandHeight() + math.random( 100, 1000 ) else -- Randomize position so that multiple AC wont be spawned on top even in air. - PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500) + PointVec3.y = PointVec3:GetLandHeight() + math.random( 500, 2500 ) end end - + end if not SpawnTemplate.parked then @@ -1874,163 +1856,162 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT SpawnTemplate.parked = true for UnitID = 1, nunits do - self:T2('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) - + self:T2( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + -- Template of the current unit. local UnitTemplate = SpawnTemplate.units[UnitID] - + -- Tranlate position and preserve the relative position/formation of all aircraft. local SX = UnitTemplate.x - local SY = UnitTemplate.y + 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) - + local TX = PointVec3.x + (SX - BX) + local TY = PointVec3.z + (SY - BY) + if spawnonground then - + -- Ships and FARPS seem to have a build in queue. if spawnonship or spawnonfarp or spawnonrunway then - - self:T(string.format("Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) - + + self:T( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + -- Spawn on ship. We take only the position of the ship. - SpawnTemplate.units[UnitID].x = PointVec3.x --TX - SpawnTemplate.units[UnitID].y = PointVec3.z --TY + SpawnTemplate.units[UnitID].x = PointVec3.x -- TX + SpawnTemplate.units[UnitID].y = PointVec3.z -- TY SpawnTemplate.units[UnitID].alt = PointVec3.y - + else - - self:T(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) - + + self:T( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) + -- Get coordinates of parking spot. - SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x - SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z + SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x + SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y - - --parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) + + -- parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) end - + else - - self:T(string.format("Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) - + + self:T( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. - SpawnTemplate.units[UnitID].x = TX - SpawnTemplate.units[UnitID].y = TY + SpawnTemplate.units[UnitID].x = TX + SpawnTemplate.units[UnitID].y = TY SpawnTemplate.units[UnitID].alt = PointVec3.y - + end - + -- Parking spot id. UnitTemplate.parking = nil UnitTemplate.parking_id = nil if parkingindex[UnitID] then UnitTemplate.parking = parkingindex[UnitID] end - + -- Debug output. - self:T(string.format("Group %s unit number %d: Parking = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking))) - self:T(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking_id))) - self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) + self:T( string.format( "Group %s unit number %d: Parking = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking ) ) ) + self:T( string.format( "Group %s unit number %d: Parking ID = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking_id ) ) ) + self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end - + -- Set gereral spawnpoint position. - SpawnPoint.x = PointVec3.x - SpawnPoint.y = PointVec3.z + SpawnPoint.x = PointVec3.x + SpawnPoint.y = PointVec3.z SpawnPoint.alt = PointVec3.y - + SpawnTemplate.x = PointVec3.x SpawnTemplate.y = PointVec3.z - + SpawnTemplate.uncontrolled = self.SpawnUnControlled - + -- Spawn group. local GroupSpawned = self:SpawnWithIndex( self.SpawnIndex ) - + -- When spawned in the air, we need to generate a Takeoff Event. if Takeoff == GROUP.Takeoff.Air then for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do - SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() } , 5 ) + SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() }, 5 ) end end - + -- Check if we accidentally spawned on the runway. Needs to be schedules, because group is not immidiately alive. - if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then - SCHEDULER:New(nil, AIRBASE.CheckOnRunWay, {SpawnAirbase, GroupSpawned, 75, true} , 1.0) + if Takeoff ~= SPAWN.Takeoff.Runway and Takeoff ~= SPAWN.Takeoff.Air and spawnonairport then + SCHEDULER:New( nil, AIRBASE.CheckOnRunWay, { SpawnAirbase, GroupSpawned, 75, true }, 1.0 ) end - - return GroupSpawned + + return GroupSpawned end end - + return nil end ---- Spawn a group on an @{Wrapper.Airbase} at a specific parking spot. +--- Spawn a group on an @{Wrapper.Airbase} at a specific parking spot. -- @param #SPAWN self -- @param Wrapper.Airbase#AIRBASE Airbase The @{Wrapper.Airbase} where to spawn the group. -- @param #table Spots Table of parking spot IDs. Note that these in general are different from the numbering in the mission editor! -- @param #SPAWN.Takeoff Takeoff (Optional) Takeoff type, i.e. either SPAWN.Takeoff.Cold or SPAWN.Takeoff.Hot. Default is Hot. -- @return Wrapper.Group#GROUP The group that was spawned or nil when nothing was spawned. -function SPAWN:SpawnAtParkingSpot(Airbase, Spots, Takeoff) -- R2.5 - self:F({Airbase=Airbase, Spots=Spots, Takeoff=Takeoff}) +function SPAWN:SpawnAtParkingSpot( Airbase, Spots, Takeoff ) -- R2.5 + self:F( { Airbase = Airbase, Spots = Spots, Takeoff = Takeoff } ) -- Ensure that Spots parameter is a table. - if type(Spots)~="table" then - Spots={Spots} + if type( Spots ) ~= "table" then + Spots = { Spots } end -- Get template group. - local group=GROUP:FindByName(self.SpawnTemplatePrefix) - + local group = GROUP:FindByName( self.SpawnTemplatePrefix ) + -- Get number of units in group. - local nunits=self.SpawnGrouping or #group:GetUnits() + local nunits = self.SpawnGrouping or #group:GetUnits() -- Quick check. if nunits then - + -- Check that number of provided parking spots is large enough. - if #Spots=nunits then - return self:SpawnAtAirbase(Airbase, Takeoff, nil, nil, nil, Parkingdata) + + if #Parkingdata >= nunits then + return self:SpawnAtAirbase( Airbase, Takeoff, nil, nil, nil, Parkingdata ) else - self:E("ERROR: Could not find enough free parking spots!") + self:E( "ERROR: Could not find enough free parking spots!" ) end - - + else - self:E("ERROR: Could not get number of units in group!") + self:E( "ERROR: Could not get number of units in group!" ) end return nil end ---- Will park a group at an @{Wrapper.Airbase}. --- +--- Will park a group at an @{Wrapper.Airbase}. +-- -- @param #SPAWN self -- @param Wrapper.Airbase#AIRBASE SpawnAirbase The @{Wrapper.Airbase} where to spawn the group. -- @param Wrapper.Airbase#AIRBASE.TerminalType TerminalType (optional) The terminal type the aircraft should be spawned at. See @{Wrapper.Airbase#AIRBASE.TerminalType}. @@ -2042,34 +2023,34 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Get position of airbase. local PointVec3 = SpawnAirbase:GetCoordinate() - self:T2(PointVec3) + self:T2( PointVec3 ) -- Set take off type. Default is hot. local Takeoff = SPAWN.Takeoff.Cold - + -- Get group template. local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate if SpawnTemplate then - + -- Check if the aircraft with the specified SpawnIndex is already spawned. -- If yes, ensure that the aircraft is spawned at the same aircraft spot. - + local GroupAlive = self:GetGroupFromIndex( SpawnIndex ) -- Debug output self:T( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) - + -- Template group, unit and its attributes. - local TemplateGroup = GROUP:FindByName(self.SpawnTemplatePrefix) - local TemplateUnit=TemplateGroup:GetUnit(1) - local ishelo=TemplateUnit:HasAttribute("Helicopters") - local isbomber=TemplateUnit:HasAttribute("Bombers") - local istransport=TemplateUnit:HasAttribute("Transports") - local isfighter=TemplateUnit:HasAttribute("Battleplanes") - + local TemplateGroup = GROUP:FindByName( self.SpawnTemplatePrefix ) + local TemplateUnit = TemplateGroup:GetUnit( 1 ) + local ishelo = TemplateUnit:HasAttribute( "Helicopters" ) + local isbomber = TemplateUnit:HasAttribute( "Bombers" ) + local istransport = TemplateUnit:HasAttribute( "Transports" ) + local isfighter = TemplateUnit:HasAttribute( "Battleplanes" ) + -- Number of units in the group. With grouping this can actually differ from the template group size! - local nunits=#SpawnTemplate.units + local nunits = #SpawnTemplate.units -- First waypoint of the group. local SpawnPoint = SpawnTemplate.route.points[1] @@ -2083,7 +2064,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex local AirbaseID = SpawnAirbase:GetID() local AirbaseCategory = SpawnAirbase:GetAirbaseCategory() self:F( { AirbaseCategory = AirbaseCategory } ) - + -- Set airdromeId. if AirbaseCategory == Airbase.Category.SHIP then SpawnPoint.linkUnit = AirbaseID @@ -2096,59 +2077,58 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex end -- Set waypoint type/action. - SpawnPoint.alt = 0 - SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type + SpawnPoint.alt = 0 + SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action - + -- Check if we spawn on ground. - local spawnonground=not (Takeoff==SPAWN.Takeoff.Air) - self:T({spawnonground=spawnonground, TOtype=Takeoff, TOair=Takeoff==SPAWN.Takeoff.Air}) - + local spawnonground = not (Takeoff == SPAWN.Takeoff.Air) + self:T( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) + -- Check where we actually spawn if we spawn on ground. - local spawnonship=false - local spawnonfarp=false - local spawnonrunway=false - local spawnonairport=false - if spawnonground then + local spawnonship = false + local spawnonfarp = false + local spawnonrunway = false + local spawnonairport = false + if spawnonground then if AirbaseCategory == Airbase.Category.SHIP then - spawnonship=true + spawnonship = true elseif AirbaseCategory == Airbase.Category.HELIPAD then - spawnonfarp=true + spawnonfarp = true elseif AirbaseCategory == Airbase.Category.AIRDROME then - spawnonairport=true + spawnonairport = true end - spawnonrunway=Takeoff==SPAWN.Takeoff.Runway + spawnonrunway = Takeoff == SPAWN.Takeoff.Runway end - + -- Array with parking spots coordinates. - local parkingspots={} - local parkingindex={} + local parkingspots = {} + local parkingindex = {} local spots - + -- Spawn happens on ground, i.e. at an airbase, a FARP or a ship. if spawnonground and not SpawnTemplate.parked then - - + -- Number of free parking spots. - local nfree=0 - + local nfree = 0 + -- Set terminal type. - local termtype=TerminalType + local termtype = TerminalType -- Scan options. Might make that input somehow. - local scanradius=50 - local scanunits=true - local scanstatics=true - local scanscenery=false - local verysafe=false - + local scanradius = 50 + local scanunits = true + local scanstatics = true + local scanscenery = false + local verysafe = false + -- Number of free parking spots at the airbase. if spawnonship or spawnonfarp or spawnonrunway then -- These places work procedural and have some kind of build in queue ==> Less effort. - self:T(string.format("Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) - nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype, true) - spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype, true) - --[[ + self:T( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true ) + spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true ) + --[[ elseif Parkingdata~=nil then -- Parking data explicitly set by user as input parameter. nfree=#Parkingdata @@ -2156,114 +2136,114 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ]] else if ishelo then - if termtype==nil then + if termtype == nil then -- Helo is spawned. Try exclusive helo spots first. - self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly)) - spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata) - nfree=#spots - if nfree=1 then - + if nfree >= 1 then + -- All units get the same spot. DCS takes care of the rest. - for i=1,nunits do - table.insert(parkingspots, spots[1].Coordinate) - table.insert(parkingindex, spots[1].TerminalID) + for i = 1, nunits do + table.insert( parkingspots, spots[1].Coordinate ) + table.insert( parkingindex, spots[1].TerminalID ) end -- This is actually used... - PointVec3=spots[1].Coordinate - + PointVec3 = spots[1].Coordinate + else -- If there is absolutely no spot ==> air start! - _notenough=true + _notenough = true end - + elseif spawnonairport then - - if nfree>=nunits then - - for i=1,nunits do - table.insert(parkingspots, spots[i].Coordinate) - table.insert(parkingindex, spots[i].TerminalID) + + if nfree >= nunits then + + for i = 1, nunits do + table.insert( parkingspots, spots[i].Coordinate ) + table.insert( parkingindex, spots[i].TerminalID ) end - + else -- Not enough spots for the whole group ==> air start! - _notenough=true - end + _notenough = true + end end - + -- Not enough spots ==> Prepare airstart. if _notenough then - - if not self.SpawnUnControlled then + + if not self.SpawnUnControlled then else - self:E(string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) + self:E( string.format( "WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) return nil end end - + else - + end if not SpawnTemplate.parked then @@ -2272,118 +2252,118 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex SpawnTemplate.parked = true for UnitID = 1, nunits do - self:F('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) - + self:F( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + -- Template of the current unit. local UnitTemplate = SpawnTemplate.units[UnitID] - + -- Tranlate position and preserve the relative position/formation of all aircraft. local SX = UnitTemplate.x - local SY = UnitTemplate.y + 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) - + local TX = PointVec3.x + (SX - BX) + local TY = PointVec3.z + (SY - BY) + if spawnonground then - + -- Ships and FARPS seem to have a build in queue. if spawnonship or spawnonfarp or spawnonrunway then - - self:T(string.format("Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) + + self:T( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) -- Spawn on ship. We take only the position of the ship. - SpawnTemplate.units[UnitID].x = PointVec3.x --TX - SpawnTemplate.units[UnitID].y = PointVec3.z --TY + SpawnTemplate.units[UnitID].x = PointVec3.x -- TX + SpawnTemplate.units[UnitID].y = PointVec3.z -- TY SpawnTemplate.units[UnitID].alt = PointVec3.y - + else - self:T(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) - + self:T( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) + -- Get coordinates of parking spot. - SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x - SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z + SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x + SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y - - --parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) + + -- parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) end - + else - - self:T(string.format("Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) - + + self:T( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. - SpawnTemplate.units[UnitID].x = TX - SpawnTemplate.units[UnitID].y = TY + SpawnTemplate.units[UnitID].x = TX + SpawnTemplate.units[UnitID].y = TY SpawnTemplate.units[UnitID].alt = PointVec3.y - + end - + -- Parking spot id. UnitTemplate.parking = nil UnitTemplate.parking_id = nil if parkingindex[UnitID] then UnitTemplate.parking = parkingindex[UnitID] end - + -- Debug output. - self:T2(string.format("Group %s unit number %d: Parking = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking))) - self:T2(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking_id))) - self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) + self:T2( string.format( "Group %s unit number %d: Parking = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking ) ) ) + self:T2( string.format( "Group %s unit number %d: Parking ID = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking_id ) ) ) + self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end - + -- Set general spawnpoint position. - SpawnPoint.x = PointVec3.x - SpawnPoint.y = PointVec3.z + SpawnPoint.x = PointVec3.x + SpawnPoint.y = PointVec3.z SpawnPoint.alt = PointVec3.y - + SpawnTemplate.x = PointVec3.x SpawnTemplate.y = PointVec3.z - + SpawnTemplate.uncontrolled = true - + -- Spawn group. local GroupSpawned = self:SpawnWithIndex( SpawnIndex, true ) - + -- When spawned in the air, we need to generate a Takeoff Event. if Takeoff == GROUP.Takeoff.Air then for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do - SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() } , 5 ) + SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() }, 5 ) end end - + -- Check if we accidentally spawned on the runway. Needs to be schedules, because group is not immidiately alive. - if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then - SCHEDULER:New(nil, AIRBASE.CheckOnRunWay, {SpawnAirbase, GroupSpawned, 75, true} , 1.0) + if Takeoff ~= SPAWN.Takeoff.Runway and Takeoff ~= SPAWN.Takeoff.Air and spawnonairport then + SCHEDULER:New( nil, AIRBASE.CheckOnRunWay, { SpawnAirbase, GroupSpawned, 75, true }, 1.0 ) end - + end end ---- Will park a group at an @{Wrapper.Airbase}. +--- Will park a group at an @{Wrapper.Airbase}. -- This method is mostly advisable to be used if you want to simulate parking units at an airbase and be visible. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. --- +-- -- All groups that are in the spawn collection and that are alive, and not in the air, are parked. --- +-- -- The @{Wrapper.Airbase#AIRBASE} object must refer to a valid airbase known in the sim. -- You can use the following enumerations to search for the pre-defined airbases on the current known maps of DCS: --- --- * @{Wrapper.Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map. --- * @{Wrapper.Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map. --- * @{Wrapper.Airbase#AIRBASE.Normandy}: The airbases on the Normandy map. --- --- Use the method @{Wrapper.Airbase#AIRBASE.FindByName}() to retrieve the airbase object. +-- +-- * @{Wrapper.Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map. +-- * @{Wrapper.Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map. +-- * @{Wrapper.Airbase#AIRBASE.Normandy}: The airbases on the Normandy map. +-- +-- Use the method @{Wrapper.Airbase#AIRBASE.FindByName}() to retrieve the airbase object. -- The known AIRBASE objects are automatically imported at mission start by MOOSE. -- Therefore, there isn't any New() constructor defined for AIRBASE objects. --- --- Ships and Farps are added within the mission, and are therefore not known. +-- +-- Ships and FARPs are added within the mission, and are therefore not known. -- For these AIRBASE objects, there isn't an @{Wrapper.Airbase#AIRBASE} enumeration defined. -- You need to provide the **exact name** of the airbase as the parameter to the @{Wrapper.Airbase#AIRBASE.FindByName}() method! --- +-- -- @param #SPAWN self -- @param Wrapper.Airbase#AIRBASE SpawnAirbase The @{Wrapper.Airbase} where to spawn the group. -- @param Wrapper.Airbase#AIRBASE.TerminalType TerminalType (optional) The terminal type the aircraft should be spawned at. See @{Wrapper.Airbase#AIRBASE.TerminalType}. @@ -2392,15 +2372,15 @@ end -- @usage -- Spawn_Plane = SPAWN:New( "Plane" ) -- Spawn_Plane:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ) ) --- +-- -- Spawn_Heli = SPAWN:New( "Heli") --- +-- -- Spawn_Heli:ParkAtAirbase( AIRBASE:FindByName( "FARP Cold" ) ) --- +-- -- Spawn_Heli:ParkAtAirbase( AIRBASE:FindByName( "Carrier" ) ) --- +-- -- Spawn_Plane:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), AIRBASE.TerminalType.OpenBig ) --- +-- function SPAWN:ParkAtAirbase( SpawnAirbase, TerminalType, Parkingdata ) -- R2.2, R2.4, R2.5 self:F( { self.SpawnTemplatePrefix, SpawnAirbase, TerminalType } ) @@ -2408,15 +2388,15 @@ function SPAWN:ParkAtAirbase( SpawnAirbase, TerminalType, Parkingdata ) -- R2.2, for SpawnIndex = 2, self.SpawnMaxGroups do self:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ) - --self:ScheduleOnce( SpawnIndex * 0.1, SPAWN.ParkAircraft, self, SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ) + -- self:ScheduleOnce( SpawnIndex * 0.1, SPAWN.ParkAircraft, self, SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ) end - + self:SetSpawnIndex( 0 ) - + return nil end ---- Will spawn a group from a Vec3 in 3D space. +--- Will spawn a group from a Vec3 in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. @@ -2428,21 +2408,21 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - self:T2(PointVec3) + 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 } ) - + local TemplateHeight = SpawnTemplate.route and SpawnTemplate.route.points[1].alt or nil SpawnTemplate.route = SpawnTemplate.route or {} @@ -2453,20 +2433,20 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) -- Translate the position of the Group Template to the 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 ) + -- 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 or 0 - local SY = UnitTemplate.y or 0 + local SY = UnitTemplate.y or 0 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 ) + local TX = Vec3.x + (SX - BX) + local TY = Vec3.z + (SY - BY) SpawnTemplate.units[UnitID].x = TX SpawnTemplate.units[UnitID].y = TY if SpawnTemplate.CategoryID ~= Group.Category.SHIP then SpawnTemplate.units[UnitID].alt = Vec3.y or TemplateHeight end - self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].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 @@ -2476,16 +2456,15 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) SpawnTemplate.x = Vec3.x SpawnTemplate.y = Vec3.z SpawnTemplate.alt = Vec3.y or TemplateHeight - + return self:SpawnWithIndex( self.SpawnIndex ) end end - + return nil end - ---- Will spawn a group from a Coordinate in 3D space. +--- Will spawn a group from a Coordinate in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. @@ -2499,8 +2478,6 @@ function SPAWN:SpawnFromCoordinate( Coordinate, SpawnIndex ) return self:SpawnFromVec3( Coordinate:GetVec3(), SpawnIndex ) end - - --- Will spawn a group from a PointVec3 in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. @@ -2510,19 +2487,18 @@ end -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. -- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage --- +-- -- local SpawnPointVec3 = ZONE:New( ZoneName ):GetPointVec3( 2000 ) -- Get the center of the ZONE object at 2000 meters from the ground. --- +-- -- -- Spawn at the zone center position at 2000 meters from the ground! --- SpawnAirplanes:SpawnFromPointVec3( SpawnPointVec3 ) --- +-- SpawnAirplanes:SpawnFromPointVec3( SpawnPointVec3 ) +-- function SPAWN:SpawnFromPointVec3( PointVec3, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) return self:SpawnFromVec3( PointVec3:GetVec3(), SpawnIndex ) end - --- Will spawn a group from a Vec2 in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. @@ -2534,29 +2510,28 @@ end -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. -- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage --- +-- -- local SpawnVec2 = ZONE:New( ZoneName ):GetVec2() --- +-- -- -- Spawn at the zone center position at the height specified in the ME of the group template! --- SpawnAirplanes:SpawnFromVec2( SpawnVec2 ) --- +-- SpawnAirplanes:SpawnFromVec2( SpawnVec2 ) +-- -- -- Spawn from the static position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnFromVec2( SpawnVec2, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnFromVec2( SpawnVec2, 2000, 4000 ) +-- function SPAWN:SpawnFromVec2( Vec2, MinHeight, MaxHeight, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, Vec2, MinHeight, MaxHeight, SpawnIndex } ) local Height = nil if MinHeight and MaxHeight then - Height = math.random( MinHeight, MaxHeight) + Height = math.random( MinHeight, MaxHeight ) end - + return self:SpawnFromVec3( { x = Vec2.x, y = Height, z = Vec2.y }, SpawnIndex ) -- y can be nil. In this case, spawn on the ground for vehicles, and in the template altitude for air. end - ---- Will spawn a group from a POINT_VEC2 in 3D space. +--- Will spawn a group from a POINT_VEC2 in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. @@ -2567,23 +2542,21 @@ end -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. -- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage --- +-- -- local SpawnPointVec2 = ZONE:New( ZoneName ):GetPointVec2() --- +-- -- -- Spawn at the zone center position at the height specified in the ME of the group template! --- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2 ) --- +-- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2 ) +-- -- -- Spawn from the static position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2, 2000, 4000 ) +-- function SPAWN:SpawnFromPointVec2( PointVec2, MinHeight, MaxHeight, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) return self:SpawnFromVec2( PointVec2:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) end - - --- Will spawn a group from a hosting unit. This method is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. @@ -2595,22 +2568,22 @@ end -- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -- @usage --- +-- -- local SpawnStatic = STATIC:FindByName( StaticName ) --- +-- -- -- Spawn from the static position at the height specified in the ME of the group template! --- SpawnAirplanes:SpawnFromUnit( SpawnStatic ) --- +-- SpawnAirplanes:SpawnFromUnit( SpawnStatic ) +-- -- -- Spawn from the static position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnFromUnit( SpawnStatic, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnFromUnit( SpawnStatic, 2000, 4000 ) +-- function SPAWN:SpawnFromUnit( HostUnit, MinHeight, MaxHeight, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostUnit, MinHeight, MaxHeight, SpawnIndex } ) + self:F( { self.SpawnTemplatePrefix, HostUnit, MinHeight, MaxHeight, SpawnIndex } ) if HostUnit and HostUnit:IsAlive() ~= nil then -- and HostUnit:getUnit(1):inAir() == false then return self:SpawnFromVec2( HostUnit:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) end - + return nil end @@ -2623,22 +2596,22 @@ end -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. -- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage --- +-- -- local SpawnStatic = STATIC:FindByName( StaticName ) --- +-- -- -- Spawn from the static position at the height specified in the ME of the group template! --- SpawnAirplanes:SpawnFromStatic( SpawnStatic ) --- +-- SpawnAirplanes:SpawnFromStatic( SpawnStatic ) +-- -- -- Spawn from the static position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnFromStatic( SpawnStatic, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnFromStatic( SpawnStatic, 2000, 4000 ) +-- function SPAWN:SpawnFromStatic( HostStatic, MinHeight, MaxHeight, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, HostStatic, MinHeight, MaxHeight, SpawnIndex } ) if HostStatic and HostStatic:IsAlive() then return self:SpawnFromVec2( HostStatic:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) end - + return nil end @@ -2654,27 +2627,27 @@ end -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. -- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage --- +-- -- local SpawnZone = ZONE:New( ZoneName ) --- +-- -- -- Spawn at the zone center position at the height specified in the ME of the group template! --- SpawnAirplanes:SpawnInZone( SpawnZone ) --- +-- SpawnAirplanes:SpawnInZone( SpawnZone ) +-- -- -- Spawn in the zone at a random position at the height specified in the Me of the group template. --- SpawnAirplanes:SpawnInZone( SpawnZone, true ) --- +-- SpawnAirplanes:SpawnInZone( SpawnZone, true ) +-- -- -- Spawn in the zone at a random position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnInZone( SpawnZone, true, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnInZone( SpawnZone, true, 2000, 4000 ) +-- -- -- Spawn at the zone center position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnInZone( SpawnZone, false, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnInZone( SpawnZone, false, 2000, 4000 ) +-- -- -- Spawn at the zone center position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnInZone( SpawnZone, nil, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnInZone( SpawnZone, nil, 2000, 4000 ) +-- function SPAWN:SpawnInZone( Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex } ) - + self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex } ) + if Zone then if RandomizeGroup then return self:SpawnFromVec2( Zone:GetRandomVec2(), MinHeight, MaxHeight, SpawnIndex ) @@ -2682,11 +2655,11 @@ function SPAWN:SpawnInZone( Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnInd return self:SpawnFromVec2( Zone:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) end end - + return nil end ---- (**AIR**) Will spawn a plane group in UnControlled or Controlled mode... +--- (**AIR**) Will spawn a plane group in UnControlled or Controlled mode... -- This will be similar to the uncontrolled flag setting in the ME. -- You can use UnControlled mode to simulate planes startup and ready for take-off but aren't moving (yet). -- ReSpawn the plane in Controlled mode, and the plane will move... @@ -2694,17 +2667,16 @@ end -- @param #boolean UnControlled true if UnControlled, false if Controlled. -- @return #SPAWN self function SPAWN:InitUnControlled( UnControlled ) - self:F2( { self.SpawnTemplatePrefix, UnControlled } ) - - self.SpawnUnControlled = ( UnControlled == true ) and true or nil - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self.SpawnGroups[SpawnGroupID].UnControlled = self.SpawnUnControlled - end - - return self -end + self:F2( { self.SpawnTemplatePrefix, UnControlled } ) + self.SpawnUnControlled = (UnControlled == true) and true or nil + + for SpawnGroupID = 1, self.SpawnMaxGroups do + self.SpawnGroups[SpawnGroupID].UnControlled = self.SpawnUnControlled + end + + return self +end --- Get the Coordinate of the Group that is Late Activated as the template for the SPAWN object. -- @param #SPAWN self @@ -2715,32 +2687,31 @@ function SPAWN:GetCoordinate() if LateGroup then return LateGroup:GetCoordinate() end - + return nil end - --- Will return the SpawnGroupName either with with a specific count number or without any count. -- @param #SPAWN self -- @param #number SpawnIndex Is the number of the Group that is to be spawned. -- @return #string SpawnGroupName function SPAWN:SpawnGroupName( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) + self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) - local SpawnPrefix = self.SpawnTemplatePrefix - if self.SpawnAliasPrefix then - SpawnPrefix = self.SpawnAliasPrefix - end + 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 - if SpawnIndex then - local SpawnName = string.format( '%s#%03d', SpawnPrefix, SpawnIndex ) - self:T( SpawnName ) - return SpawnName - else - self:T( SpawnPrefix ) - return SpawnPrefix - end - end --- Will find the first alive @{Wrapper.Group} it has spawned, and return the alive @{Wrapper.Group} object and the first Index where the first alive @{Wrapper.Group} object has been found. @@ -2748,14 +2719,16 @@ end -- @return Wrapper.Group#GROUP, #number The @{Wrapper.Group} object found, the new Index where the group was found. -- @return #nil, #nil When no group is found, #nil is returned. -- @usage --- -- Find the first alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.Group} collection that it has spawned during the mission. --- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() --- while GroupPlane ~= nil do --- -- Do actions with the GroupPlane object. --- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) --- end +-- +-- -- Find the first alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.Group} collection that it has spawned during the mission. +-- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() +-- while GroupPlane ~= nil do +-- -- Do actions with the GroupPlane object. +-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) +-- end +-- function SPAWN:GetFirstAliveGroup() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) for SpawnIndex = 1, self.SpawnCount do local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) @@ -2763,25 +2736,26 @@ function SPAWN:GetFirstAliveGroup() return SpawnGroup, SpawnIndex end end - + return nil, nil end - --- Will find the next alive @{Wrapper.Group} object from a given Index, and return a reference to the alive @{Wrapper.Group} object and the next Index where the alive @{Wrapper.Group} has been found. -- @param #SPAWN self -- @param #number SpawnIndexStart A Index holding the start position to search from. This method can also be used to find the first alive @{Wrapper.Group} object from the given Index. -- @return Wrapper.Group#GROUP, #number The next alive @{Wrapper.Group} object found, the next Index where the next alive @{Wrapper.Group} object was found. -- @return #nil, #nil When no alive @{Wrapper.Group} object is found from the start Index position, #nil is returned. -- @usage --- -- Find the first alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.Group} collection that it has spawned during the mission. --- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() --- while GroupPlane ~= nil do --- -- Do actions with the GroupPlane object. --- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) --- end +-- +-- -- Find the first alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.Group} collection that it has spawned during the mission. +-- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() +-- while GroupPlane ~= nil do +-- -- Do actions with the GroupPlane object. +-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) +-- end +-- function SPAWN:GetNextAliveGroup( SpawnIndexStart ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } ) SpawnIndexStart = SpawnIndexStart + 1 for SpawnIndex = SpawnIndexStart, self.SpawnCount do @@ -2790,7 +2764,7 @@ function SPAWN:GetNextAliveGroup( SpawnIndexStart ) return SpawnGroup, SpawnIndex end end - + return nil, nil end @@ -2799,14 +2773,16 @@ end -- @return Wrapper.Group#GROUP, #number The last alive @{Wrapper.Group} object found, the last Index where the last alive @{Wrapper.Group} object was found. -- @return #nil, #nil When no alive @{Wrapper.Group} object is found, #nil is returned. -- @usage --- -- Find the last alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.Group} collection that it has spawned during the mission. --- local GroupPlane, Index = SpawnPlanes:GetLastAliveGroup() --- if GroupPlane then -- GroupPlane can be nil!!! --- -- Do actions with the GroupPlane object. --- end +-- +-- -- Find the last alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.Group} collection that it has spawned during the mission. +-- local GroupPlane, Index = SpawnPlanes:GetLastAliveGroup() +-- if GroupPlane then -- GroupPlane can be nil!!! +-- -- Do actions with the GroupPlane object. +-- end +-- function SPAWN:GetLastAliveGroup() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + for SpawnIndex = self.SpawnCount, 1, -1 do -- Added local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) if SpawnGroup and SpawnGroup:IsAlive() then @@ -2819,8 +2795,6 @@ function SPAWN:GetLastAliveGroup() return nil end - - --- Get the group from an index. -- Returns the group from the SpawnGroups list. -- If no index is given, it will return the first group in the list. @@ -2828,20 +2802,19 @@ end -- @param #number SpawnIndex The index of the group to return. -- @return Wrapper.Group#GROUP self 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 + 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 --- Return the prefix of a SpawnUnit. -- The method will search for a #-mark, and will return the text before the #-mark. @@ -2884,86 +2857,83 @@ function SPAWN:_GetPrefixFromGroupName(SpawnGroupName) return nil end - --- Get the index from a given group. -- The function will search the name of the group for a #, and will return the number behind the #-mark. function SPAWN:GetSpawnIndexFromGroup( SpawnGroup ) - self:F2( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } ) - - local IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):sub( 2 ) - local Index = tonumber( IndexString ) - - self:T3( IndexString, Index ) - return Index - + self:F2( { 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 --- Return the last maximum index that can be used. function SPAWN:_GetLastIndex() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - return self.SpawnMaxGroups + return self.SpawnMaxGroups end --- Initalize the SpawnGroups collection. -- @param #SPAWN self function SPAWN:_InitializeSpawnGroups( SpawnIndex ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, 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 ) - --self:_TranslateRotate( SpawnIndex ) - - return self.SpawnGroups[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 ) + -- self:_TranslateRotate( SpawnIndex ) + + return self.SpawnGroups[SpawnIndex] end - - --- Gets the CategoryID of the Group with the given SpawnPrefix function SPAWN:_GetGroupCategoryID( SpawnPrefix ) - local TemplateGroup = Group.getByName( SpawnPrefix ) - - if TemplateGroup then - return TemplateGroup:getCategory() - else - return nil - end + local TemplateGroup = Group.getByName( SpawnPrefix ) + + if TemplateGroup then + return TemplateGroup:getCategory() + else + return nil + end end --- Gets the CoalitionID of the Group with the given SpawnPrefix function SPAWN:_GetGroupCoalitionID( SpawnPrefix ) - local TemplateGroup = Group.getByName( SpawnPrefix ) - - if TemplateGroup then - return TemplateGroup:getCoalition() - else - return nil - end + local TemplateGroup = Group.getByName( SpawnPrefix ) + + if TemplateGroup then + return TemplateGroup:getCoalition() + else + return nil + end end --- Gets the CountryID of the Group with the given SpawnPrefix 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 + 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 --- Gets the Group Template from the ME environment definition. @@ -2972,25 +2942,25 @@ end -- @param #string SpawnTemplatePrefix -- @return @SPAWN self function SPAWN:_GetTemplate( SpawnTemplatePrefix ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } ) - local SpawnTemplate = nil + local SpawnTemplate = nil local Template = _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template self:F( { Template = Template } ) - SpawnTemplate = UTILS.DeepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template ) - - if SpawnTemplate == nil then - error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix ) - end + SpawnTemplate = UTILS.DeepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template ) - --SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix ) - --SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix ) - --SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix ) - - self:T3( { SpawnTemplate } ) - return SpawnTemplate + if SpawnTemplate == nil then + error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix ) + end + + -- SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix ) + -- SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix ) + -- SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix ) + + self:T3( { SpawnTemplate } ) + return SpawnTemplate end --- Prepares the new Group Template. @@ -2998,36 +2968,35 @@ end -- @param #string SpawnTemplatePrefix -- @param #number SpawnIndex -- @return #SPAWN self -function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2 - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - --- if not self.SpawnTemplate then --- self.SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) --- end - +function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + + -- if not self.SpawnTemplate then + -- self.SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) + -- end + local SpawnTemplate if self.TweakedTemplate ~= nil and self.TweakedTemplate == true then - BASE:I("WARNING: You are using a tweaked template.") + BASE:I( "WARNING: You are using a tweaked template." ) SpawnTemplate = self.SpawnTemplate else SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) SpawnTemplate.name = self:SpawnGroupName( SpawnIndex ) end - - SpawnTemplate.groupId = nil - --SpawnTemplate.lateActivation = false - SpawnTemplate.lateActivation = self.LateActivated or false + SpawnTemplate.groupId = nil + -- SpawnTemplate.lateActivation = false + SpawnTemplate.lateActivation = self.LateActivated or 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 + 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 @@ -3040,17 +3009,17 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2 end end end - + if self.SpawnInitKeepUnitNames == false then - for UnitID = 1, #SpawnTemplate.units do - SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) - SpawnTemplate.units[UnitID].unitId = nil - end + for UnitID = 1, #SpawnTemplate.units do + SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) + SpawnTemplate.units[UnitID].unitId = nil + end else for UnitID = 1, #SpawnTemplate.units do local UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" ) self:T( { UnitPrefix, Rest } ) - + SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID ) SpawnTemplate.units[UnitID].unitId = nil end @@ -3060,20 +3029,20 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2 for UnitID = 1, #SpawnTemplate.units do local Callsign = SpawnTemplate.units[UnitID].callsign if Callsign then - if type(Callsign) ~= "number" then -- blue callsign - Callsign[2] = ( ( SpawnIndex - 1 ) % 10 ) + 1 + if type( Callsign ) ~= "number" then -- blue callsign + Callsign[2] = ((SpawnIndex - 1) % 10) + 1 local CallsignName = SpawnTemplate.units[UnitID].callsign["name"] -- #string local CallsignLen = CallsignName:len() - SpawnTemplate.units[UnitID].callsign["name"] = CallsignName:sub(1,CallsignLen) .. SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3] + SpawnTemplate.units[UnitID].callsign["name"] = CallsignName:sub( 1, CallsignLen ) .. SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3] else SpawnTemplate.units[UnitID].callsign = Callsign + SpawnIndex end end end - - self:T3( { "Template:", SpawnTemplate } ) - return SpawnTemplate - + + self:T3( { "Template:", SpawnTemplate } ) + return SpawnTemplate + end --- Private method randomizing the routes. @@ -3081,17 +3050,17 @@ end -- @param #number SpawnIndex The index of the group to be spawned. -- @return #SPAWN function SPAWN:_RandomizeRoute( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } ) + self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } ) if self.SpawnRandomizeRoute then local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate local RouteCount = #SpawnTemplate.route.points - - for t = self.SpawnRandomizeRouteStartPoint + 1, ( RouteCount - self.SpawnRandomizeRouteEndPoint ) do - + + for t = self.SpawnRandomizeRouteStartPoint + 1, (RouteCount - self.SpawnRandomizeRouteEndPoint) do + SpawnTemplate.route.points[t].x = SpawnTemplate.route.points[t].x + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius ) SpawnTemplate.route.points[t].y = SpawnTemplate.route.points[t].y + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius ) - + -- Manage randomization of altitude for airborne units ... if SpawnTemplate.CategoryID == Group.Category.AIRPLANE or SpawnTemplate.CategoryID == Group.Category.HELICOPTER then if SpawnTemplate.route.points[t].alt and self.SpawnRandomizeRouteHeight then @@ -3100,13 +3069,13 @@ function SPAWN:_RandomizeRoute( SpawnIndex ) else SpawnTemplate.route.points[t].alt = nil end - + self:T( 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y ) end end - + self:_RandomizeZones( SpawnIndex ) - + return self end @@ -3115,10 +3084,10 @@ end -- @param #number SpawnIndex -- @return #SPAWN self function SPAWN:_RandomizeTemplate( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } ) + self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } ) if self.SpawnRandomizeTemplate then - self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[ math.random( 1, #self.SpawnTemplatePrefixTable ) ] + self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[math.random( 1, #self.SpawnTemplatePrefixTable )] self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex ) self.SpawnGroups[SpawnIndex].SpawnTemplate.route = UTILS.DeepCopy( self.SpawnTemplate.route ) self.SpawnGroups[SpawnIndex].SpawnTemplate.x = self.SpawnTemplate.x @@ -3128,14 +3097,14 @@ function SPAWN:_RandomizeTemplate( SpawnIndex ) local OldY = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].y for UnitID = 1, #self.SpawnGroups[SpawnIndex].SpawnTemplate.units do self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].heading = self.SpawnTemplate.units[1].heading - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x = self.SpawnTemplate.units[1].x + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x - OldX ) - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y = self.SpawnTemplate.units[1].y + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y - OldY ) + self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x = self.SpawnTemplate.units[1].x + (self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x - OldX) + self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y = self.SpawnTemplate.units[1].y + (self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y - OldY) self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].alt = self.SpawnTemplate.units[1].alt end end - + self:_RandomizeRoute( SpawnIndex ) - + return self end @@ -3152,84 +3121,79 @@ function SPAWN:_RandomizeZones( SpawnIndex ) self:T( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } ) local ZoneID = math.random( #self.SpawnZoneTable ) self:T( ZoneID ) - SpawnZone = self.SpawnZoneTable[ ZoneID ]:GetZoneMaybe() + SpawnZone = self.SpawnZoneTable[ZoneID]:GetZoneMaybe() end - + self:T( "Preparing Spawn in Zone", SpawnZone:GetName() ) - + local SpawnVec2 = SpawnZone:GetRandomVec2() - + self:T( { SpawnVec2 = SpawnVec2 } ) - + local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate - + self:T( { Route = SpawnTemplate.route } ) - + for UnitID = 1, #SpawnTemplate.units do local UnitTemplate = SpawnTemplate.units[UnitID] - self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) + self:T( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) local SX = UnitTemplate.x - local SY = UnitTemplate.y + local SY = UnitTemplate.y local BX = SpawnTemplate.route.points[1].x local BY = SpawnTemplate.route.points[1].y - local TX = SpawnVec2.x + ( SX - BX ) - local TY = SpawnVec2.y + ( SY - BY ) + local TX = SpawnVec2.x + (SX - BX) + local TY = SpawnVec2.y + (SY - BY) UnitTemplate.x = TX UnitTemplate.y = TY -- TODO: Manage altitude based on landheight... - --SpawnTemplate.units[UnitID].alt = SpawnVec2: - self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) + -- SpawnTemplate.units[UnitID].alt = SpawnVec2: + self:T( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) end SpawnTemplate.x = SpawnVec2.x SpawnTemplate.y = SpawnVec2.y SpawnTemplate.route.points[1].x = SpawnVec2.x SpawnTemplate.route.points[1].y = SpawnVec2.y end - + return self - + end function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } ) - + self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } ) + -- Translate local TranslatedX = SpawnX local TranslatedY = SpawnY - + -- Rotate -- From Wikipedia: https://en.wikipedia.org/wiki/Rotation_matrix#Common_rotations -- x' = x \cos \theta - y \sin \theta\ -- y' = x \sin \theta + y \cos \theta\ - local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) ) - + TranslatedY * math.sin( math.rad( SpawnAngle ) ) - local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) ) - + TranslatedY * math.cos( math.rad( SpawnAngle ) ) - + local RotatedX = -TranslatedX * math.cos( math.rad( SpawnAngle ) ) + TranslatedY * math.sin( math.rad( SpawnAngle ) ) + local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) ) + TranslatedY * math.cos( math.rad( SpawnAngle ) ) + -- Assign self.SpawnGroups[SpawnIndex].SpawnTemplate.x = SpawnRootX - RotatedX self.SpawnGroups[SpawnIndex].SpawnTemplate.y = SpawnRootY + RotatedY - local SpawnUnitCount = table.getn( self.SpawnGroups[SpawnIndex].SpawnTemplate.units ) for u = 1, SpawnUnitCount do - + -- Translate - local TranslatedX = SpawnX - local TranslatedY = SpawnY - 10 * ( u - 1 ) - + local TranslatedX = SpawnX + local TranslatedY = SpawnY - 10 * (u - 1) + -- Rotate - local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) ) - + TranslatedY * math.sin( math.rad( SpawnAngle ) ) - local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) ) - + TranslatedY * math.cos( math.rad( SpawnAngle ) ) - + local RotatedX = -TranslatedX * math.cos( math.rad( SpawnAngle ) ) + TranslatedY * math.sin( math.rad( SpawnAngle ) ) + local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) ) + TranslatedY * math.cos( math.rad( SpawnAngle ) ) + -- Assign self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].x = SpawnRootX - RotatedX self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].y = SpawnRootY + RotatedY self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading + math.rad( SpawnAngle ) end - + return self end @@ -3238,10 +3202,10 @@ end -- @param #number SpawnIndex Spawn index. -- @return #number self.SpawnIndex function SPAWN:_GetSpawnIndex( SpawnIndex ) - self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } ) - - if ( self.SpawnMaxGroups == 0 ) or ( SpawnIndex <= self.SpawnMaxGroups ) then - if ( self.SpawnMaxUnitsAlive == 0 ) or ( self.AliveUnits + #self.SpawnTemplate.units <= self.SpawnMaxUnitsAlive ) or self.UnControlled == true then + self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } ) + + if (self.SpawnMaxGroups == 0) or (SpawnIndex <= self.SpawnMaxGroups) then + if (self.SpawnMaxUnitsAlive == 0) or (self.AliveUnits + #self.SpawnTemplate.units <= self.SpawnMaxUnitsAlive) or self.UnControlled == true then self:F( { SpawnCount = self.SpawnCount, SpawnIndex = SpawnIndex } ) if SpawnIndex and SpawnIndex >= self.SpawnCount + 1 then self.SpawnCount = self.SpawnCount + 1 @@ -3257,11 +3221,10 @@ function SPAWN:_GetSpawnIndex( SpawnIndex ) else return nil end - + return self.SpawnIndex end - -- TODO Need to delete this... _DATABASE does this now ... --- @param #SPAWN self @@ -3270,23 +3233,20 @@ function SPAWN:_OnBirth( EventData ) self:F( self.SpawnTemplatePrefix ) local SpawnGroup = EventData.IniGroup - + if SpawnGroup then local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - 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 + 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 end ---- Obscolete --- @todo Need to delete this... _DATABASE does this now ... - --- @param #SPAWN self -- @param Core.Event#EVENTDATA EventData function SPAWN:_OnDeadOrCrash( EventData ) @@ -3324,12 +3284,12 @@ function SPAWN:_OnTakeOff( EventData ) local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! 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 ) + 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 end --- Will detect AIR Units landing... When the event takes place, the spawned Group is registered as landed. @@ -3344,20 +3304,20 @@ function SPAWN:_OnLand( EventData ) local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! self:T( { "Land event: " .. EventPrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - -- TODO: Check if this is the last unit of the group that lands. - 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 ) - -- Delay respawn by three seconds due to DCS 2.5.4.26368 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 - -- Bug was initially only for engine shutdown event but after ED "fixed" it, it now happens on landing events. - SCHEDULER:New(nil, self.ReSpawn, {self, SpawnGroupIndex}, 3) - end - end + if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then + -- TODO: Check if this is the last unit of the group that lands. + 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 ) + -- Delay respawn by three seconds due to DCS 2.5.4.26368 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 + -- Bug was initially only for engine shutdown event but after ED "fixed" it, it now happens on landing events. + SCHEDULER:New( nil, self.ReSpawn, { self, SpawnGroupIndex }, 3 ) + end + end end - end + end end --- Will detect AIR Units shutting down their engines ... @@ -3373,76 +3333,75 @@ function SPAWN:_OnEngineShutDown( EventData ) local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! self:T( { "EngineShutdown event: " .. EventPrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - -- todo: test if on the runway - 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 ) - -- Delay respawn by three seconds due to DCS 2.5.4 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 - SCHEDULER:New(nil, self.ReSpawn, {self, SpawnGroupIndex}, 3) - end - end + if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then + -- todo: test if on the runway + 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 ) + -- Delay respawn by three seconds due to DCS 2.5.4 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 + SCHEDULER:New( nil, self.ReSpawn, { self, SpawnGroupIndex }, 3 ) + end + end end - end + end end --- This function is called automatically by the Spawning scheduler. -- It is the internal worker method SPAWNing new Groups on the defined time intervals. -- @param #SPAWN self function SPAWN:_Scheduler() - self:F2( { "_Scheduler", self.SpawnTemplatePrefix, self.SpawnAliasPrefix, self.SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive } ) - - -- Validate if there are still groups left in the batch... - self:Spawn() - - return true + self:F2( { "_Scheduler", self.SpawnTemplatePrefix, self.SpawnAliasPrefix, self.SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive } ) + + -- Validate if there are still groups left in the batch... + self:Spawn() + + return true end --- Schedules the CleanUp of Groups -- @param #SPAWN self -- @return #boolean True = Continue Scheduler function SPAWN:_SpawnCleanUpScheduler() - self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) + self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) - local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() - self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) + local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() + self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) - while SpawnGroup do + while SpawnGroup do local SpawnUnits = SpawnGroup:GetUnits() - - for UnitID, UnitData in pairs( SpawnUnits ) do - - local SpawnUnit = UnitData -- Wrapper.Unit#UNIT - local SpawnUnitName = SpawnUnit:GetName() - - - self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {} - local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName] + + for UnitID, UnitData in pairs( SpawnUnits ) do + + local SpawnUnit = UnitData -- Wrapper.Unit#UNIT + 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) or (SpawnUnit:GetLife() <= 1) then - -- If the plane is not moving or dead , and is on the ground, assign it with a timestamp... - if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then - self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) - self:ReSpawn( SpawnCursor ) + + 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) or (SpawnUnit:GetLife() <= 1) then + -- If the plane is not moving or dead , and is on the ground, assign it with a timestamp... + if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then + self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) + self:ReSpawn( SpawnCursor ) Stamp.Vec2 = nil Stamp.Time = nil - end - else - Stamp.Time = timer.getTime() + end + else + Stamp.Time = timer.getTime() Stamp.Vec2 = SpawnUnit:GetVec2() - end - else - Stamp.Vec2 = nil - Stamp.Time = nil - end - else + end + else + Stamp.Vec2 = nil + Stamp.Time = nil + end + else if SpawnUnit:InAir() == false then Stamp.Vec2 = SpawnUnit:GetVec2() if (SpawnUnit:GetVelocityKMH() < 1) then @@ -3454,13 +3413,13 @@ function SPAWN:_SpawnCleanUpScheduler() end end end - - SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor ) - - self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) - - end - - return true -- Repeat - + + SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor ) + + self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) + + end + + return true -- Repeat + end diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 8f7192641..f611ff58d 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -73,7 +73,7 @@ -- @field Core.Point#COORDINATE RearmingPlaceCoord Coordinates of the rearming place. If the place is more than 100 m away from the ARTY group, the group will go there. -- @field #boolean RearmingArtyOnRoad If true, ARTY group will move to rearming place using mainly roads. Default false. -- @field Core.Point#COORDINATE InitialCoord Initial coordinates of the ARTY group. --- @field #boolean report Arty group sends messages about their current state or target to its coaliton. +-- @field #boolean report Arty group sends messages about their current state or target to its coalition. -- @field #table ammoshells Table holding names of the shell types which are included when counting the ammo. Default is {"weapons.shells"} which include most shells. -- @field #table ammorockets Table holding names of the rocket types which are included when counting the ammo. Default is {"weapons.nurs"} which includes most unguided rockets. -- @field #table ammomissiles Table holding names of the missile types which are included when counting the ammo. Default is {"weapons.missiles"} which includes some guided missiles. diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index fd6320ae0..04f8e237c 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -1,43 +1,42 @@ --- **Functional** -- Models the detection of enemy units by FACs or RECCEs and group them according various methods. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Detection of targets by recce units. -- * Group detected targets per unit, type or area (zone). -- * Keep persistency of detected targets, if when detection is lost. -- * Provide an indication of detected targets. -- * Report detected targets. -- * Refresh detection upon specified time intervals. --- +-- -- === --- +-- -- ## Missions: --- +-- -- [DET - Detection](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/DET%20-%20Detection) --- +-- -- === --- --- Facilitate the detection of enemy units within the battle zone executed by FACs (Forward Air Controllers) or RECCEs (Reconnassance Units). +-- +-- Facilitate the detection of enemy units within the battle zone executed by FACs (Forward Air Controllers) or RECCEs (Reconnaissance Units). -- It uses the in-built detection capabilities of DCS World, but adds new functionalities. --- +-- -- === --- --- ### Contributions: --- +-- +-- ### Contributions: +-- -- * Mechanist : Early concept of DETECTION_AREAS. --- --- ### Authors: --- +-- +-- ### Authors: +-- -- * FlightControl : Analysis, Design, Programming, Testing --- +-- -- === --- +-- -- @module Functional.Detection -- @image Detection.JPG - do -- DETECTION_BASE --- @type DETECTION_BASE @@ -47,220 +46,218 @@ do -- DETECTION_BASE -- @field #table DetectedObjectsIdentified Map of the DetectedObjects identified. -- @field #number DetectionRun -- @extends Core.Fsm#FSM - + --- Defines the core functions to administer detected objects. -- The DETECTION_BASE class will detect objects within the battle zone for a list of @{Wrapper.Group}s detecting targets following (a) detection method(s). - -- + -- -- ## DETECTION_BASE constructor - -- + -- -- Construct a new DETECTION_BASE instance using the @{#DETECTION_BASE.New}() method. - -- + -- -- ## Initialization - -- + -- -- By default, detection will return detected objects with all the detection sensors available. - -- However, you can ask how the objects were found with specific detection methods. + -- However, you can ask how the objects were found with specific detection methods. -- If you use one of the below methods, the detection will work with the detection method specified. -- You can specify to apply multiple detection methods. - -- + -- -- Use the following functions to report the objects it detected using the methods Visual, Optical, Radar, IRST, RWR, DLINK: - -- + -- -- * @{#DETECTION_BASE.InitDetectVisual}(): Detected using Visual. -- * @{#DETECTION_BASE.InitDetectOptical}(): Detected using Optical. -- * @{#DETECTION_BASE.InitDetectRadar}(): Detected using Radar. -- * @{#DETECTION_BASE.InitDetectIRST}(): Detected using IRST. -- * @{#DETECTION_BASE.InitDetectRWR}(): Detected using RWR. -- * @{#DETECTION_BASE.InitDetectDLINK}(): Detected using DLINK. - -- + -- -- ## **Filter** detected units based on **category of the unit** - -- - -- Filter the detected units based on Unit.Category using the method @{#DETECTION_BASE.FilterCategories}(). + -- + -- Filter the detected units based on Unit.Category using the method @{#DETECTION_BASE.FilterCategories}(). -- The different values of Unit.Category can be: - -- + -- -- * Unit.Category.AIRPLANE -- * Unit.Category.GROUND_UNIT -- * Unit.Category.HELICOPTER -- * Unit.Category.SHIP -- * Unit.Category.STRUCTURE - -- + -- -- Multiple Unit.Category entries can be given as a table and then these will be evaluated as an OR expression. - -- + -- -- Example to filter a single category (Unit.Category.AIRPLANE). - -- - -- DetectionObject:FilterCategories( Unit.Category.AIRPLANE ) - -- + -- + -- DetectionObject:FilterCategories( Unit.Category.AIRPLANE ) + -- -- Example to filter multiple categories (Unit.Category.AIRPLANE, Unit.Category.HELICOPTER). Note the {}. - -- + -- -- DetectionObject:FilterCategories( { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) - -- - -- + -- -- ## **DETECTION_ derived classes** group the detected units into a **DetectedItems[]** list - -- - -- DETECTION_BASE derived classes build a list called DetectedItems[], which is essentially a first later + -- + -- DETECTION_BASE derived classes build a list called DetectedItems[], which is essentially a first later -- of grouping of detected units. Each DetectedItem within the DetectedItems[] list contains -- a SET_UNIT object that contains the detected units that belong to that group. - -- - -- Derived classes will apply different methods to group the detected units. + -- + -- Derived classes will apply different methods to group the detected units. -- Examples are per area, per quadrant, per distance, per type. - -- See further the derived DETECTION classes on which grouping methods are currently supported. - -- + -- See further the derived DETECTION classes on which grouping methods are currently supported. + -- -- Various methods exist how to retrieve the grouped items from a DETECTION_BASE derived class: - -- + -- -- * The method @{Functional.Detection#DETECTION_BASE.GetDetectedItems}() retrieves the DetectedItems[] list. -- * A DetectedItem from the DetectedItems[] list can be retrieved using the method @{Functional.Detection#DETECTION_BASE.GetDetectedItem}( DetectedItemIndex ). -- Note that this method returns a DetectedItem element from the list, that contains a Set variable and further information -- about the DetectedItem that is set by the DETECTION_BASE derived classes, used to group the DetectedItem. -- * A DetectedSet from the DetectedItems[] list can be retrieved using the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}( DetectedItemIndex ). -- This method retrieves the Set from a DetectedItem element from the DetectedItem list (DetectedItems[ DetectedItemIndex ].Set ). - -- + -- -- ## **Visual filters** to fine-tune the probability of the detected objects - -- + -- -- By default, DCS World will return any object that is in LOS and within "visual reach", or detectable through one of the electronic detection means. -- That being said, the DCS World detection algorithm can sometimes be unrealistic. -- Especially for a visual detection, DCS World is able to report within 1 second a detailed detection of a group of 20 units (including types of the units) that are 10 kilometers away, using only visual capabilities. -- Additionally, trees and other obstacles are not accounted during the DCS World detection. - -- + -- -- Therefore, an additional (optional) filtering has been built into the DETECTION_BASE class, that can be set for visual detected units. -- For electronic detection, this filtering is not applied, only for visually detected targets. - -- + -- -- The following additional filtering can be applied for visual filtering: - -- + -- -- * A probability factor per kilometer distance. -- * A probability factor based on the alpha angle between the detected object and the unit detecting. -- A detection from a higher altitude allows for better detection than when on the ground. -- * Define a probability factor for "cloudy zones", which are zones where forests or villages are located. In these zones, detection will be much more difficult. - -- The mission designer needs to define these cloudy zones within the mission, and needs to register these zones in the DETECTION_ objects additing a probability factor per zone. - -- + -- The mission designer needs to define these cloudy zones within the mission, and needs to register these zones in the DETECTION_ objects adding a probability factor per zone. + -- -- I advise however, that, when you first use the DETECTION derived classes, that you don't use these filters. - -- Only when you experience unrealistic behaviour in your missions, these filters could be applied. - -- - -- + -- Only when you experience unrealistic behavior in your missions, these filters could be applied. + -- -- ### Distance visual detection probability - -- + -- -- Upon a **visual** detection, the further away a detected object is, the less likely it is to be detected properly. -- Also, the speed of accurate detection plays a role. - -- + -- -- A distance probability factor between 0 and 1 can be given, that will model a linear extrapolated probability over 10 km distance. - -- + -- -- For example, if a probability factor of 0.6 (60%) is given, the extrapolated probabilities over 15 kilometers would like like: -- 1 km: 96%, 2 km: 92%, 3 km: 88%, 4 km: 84%, 5 km: 80%, 6 km: 76%, 7 km: 72%, 8 km: 68%, 9 km: 64%, 10 km: 60%, 11 km: 56%, 12 km: 52%, 13 km: 48%, 14 km: 44%, 15 km: 40%. - -- + -- -- Note that based on this probability factor, not only the detection but also the **type** of the unit will be applied! - -- + -- -- Use the method @{Functional.Detection#DETECTION_BASE.SetDistanceProbability}() to set the probability factor upon a 10 km distance. - -- + -- -- ### Alpha Angle visual detection probability - -- + -- -- Upon a **visual** detection, the higher the unit is during the detecting process, the more likely the detected unit is to be detected properly. -- A detection at a 90% alpha angle is the most optimal, a detection at 10% is less and a detection at 0% is less likely to be correct. - -- + -- -- A probability factor between 0 and 1 can be given, that will model a progressive extrapolated probability if the target would be detected at a 0° angle. - -- + -- -- For example, if a alpha angle probability factor of 0.7 is given, the extrapolated probabilities of the different angles would look like: -- 0°: 70%, 10°: 75,21%, 20°: 80,26%, 30°: 85%, 40°: 89,28%, 50°: 92,98%, 60°: 95,98%, 70°: 98,19%, 80°: 99,54%, 90°: 100% - -- + -- -- Use the method @{Functional.Detection#DETECTION_BASE.SetAlphaAngleProbability}() to set the probability factor if 0°. - -- + -- -- ### Cloudy Zones detection probability - -- + -- -- Upon a **visual** detection, the more a detected unit is within a cloudy zone, the less likely the detected unit is to be detected successfully. -- The Cloudy Zones work with the ZONE_BASE derived classes. The mission designer can define within the mission -- zones that reflect cloudy areas where detected units may not be so easily visually detected. - -- + -- -- Use the method @{Functional.Detection#DETECTION_BASE.SetZoneProbability}() to set for a defined number of zones, the probability factors. - -- + -- -- Note however, that the more zones are defined to be "cloudy" within a detection, the more performance it will take -- from the DETECTION_BASE to calculate the presence of the detected unit within each zone. - -- Expecially for ZONE_POLYGON, try to limit the amount of nodes of the polygon! - -- - -- Typically, this kind of filter would be applied for very specific areas were a detection needs to be very realisting for + -- Especially for ZONE_POLYGON, try to limit the amount of nodes of the polygon! + -- + -- Typically, this kind of filter would be applied for very specific areas where a detection needs to be very realistic for -- AI not to detect so easily targets within a forrest or village rich area. - -- + -- -- ## Accept / Reject detected units - -- - -- DETECTION_BASE can accept or reject successful detections based on the location of the detected object, + -- + -- DETECTION_BASE can accept or reject successful detections based on the location of the detected object, -- if it is located in range or located inside or outside of specific zones. - -- + -- -- ### Detection acceptance of within range limit - -- + -- -- A range can be set that will limit a successful detection for a unit. -- Use the method @{Functional.Detection#DETECTION_BASE.SetAcceptRange}() to apply a range in meters till where detected units will be accepted. - -- + -- -- local SetGroup = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterStart() -- Build a SetGroup of Forward Air Controllers. - -- + -- -- -- Build a detect object. -- local Detection = DETECTION_UNITS:New( SetGroup ) - -- + -- -- -- This will accept detected units if the range is below 5000 meters. - -- Detection:SetAcceptRange( 5000 ) - -- + -- Detection:SetAcceptRange( 5000 ) + -- -- -- Start the Detection. -- Detection:Start() - -- - -- + -- + -- -- ### Detection acceptance if within zone(s). - -- + -- -- Specific ZONE_BASE object(s) can be given as a parameter, which will only accept a detection if the unit is within the specified ZONE_BASE object(s). -- Use the method @{Functional.Detection#DETECTION_BASE.SetAcceptZones}() will accept detected units if they are within the specified zones. - -- + -- -- local SetGroup = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterStart() -- Build a SetGroup of Forward Air Controllers. - -- + -- -- -- Search fo the zones where units are to be accepted. -- local ZoneAccept1 = ZONE:New( "AcceptZone1" ) -- local ZoneAccept2 = ZONE:New( "AcceptZone2" ) - -- + -- -- -- Build a detect object. -- local Detection = DETECTION_UNITS:New( SetGroup ) - -- + -- -- -- This will accept detected units by Detection when the unit is within ZoneAccept1 OR ZoneAccept2. -- Detection:SetAcceptZones( { ZoneAccept1, ZoneAccept2 } ) - -- + -- -- -- Start the Detection. -- Detection:Start() - -- - -- ### Detection rejectance if within zone(s). - -- + -- + -- ### Detection rejection if within zone(s). + -- -- Specific ZONE_BASE object(s) can be given as a parameter, which will reject detection if the unit is within the specified ZONE_BASE object(s). -- Use the method @{Functional.Detection#DETECTION_BASE.SetRejectZones}() will reject detected units if they are within the specified zones. -- An example of how to use the method is shown below. - -- + -- -- local SetGroup = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterStart() -- Build a SetGroup of Forward Air Controllers. - -- + -- -- -- Search fo the zones where units are to be rejected. -- local ZoneReject1 = ZONE:New( "RejectZone1" ) -- local ZoneReject2 = ZONE:New( "RejectZone2" ) - -- + -- -- -- Build a detect object. -- local Detection = DETECTION_UNITS:New( SetGroup ) - -- + -- -- -- This will reject detected units by Detection when the unit is within ZoneReject1 OR ZoneReject2. - -- Detection:SetRejectZones( { ZoneReject1, ZoneReject2 } ) - -- + -- Detection:SetRejectZones( { ZoneReject1, ZoneReject2 } ) + -- -- -- Start the Detection. -- Detection:Start() - -- + -- -- ## Detection of Friendlies Nearby - -- + -- -- Use the method @{Functional.Detection#DETECTION_BASE.SetFriendliesRange}() to set the range what will indicate when friendlies are nearby -- a DetectedItem. The default range is 6000 meters. For air detections, it is advisory to use about 30.000 meters. - -- + -- -- ## DETECTION_BASE is a Finite State Machine -- -- Various Events and State Transitions can be tailored using DETECTION_BASE. - -- + -- -- ### DETECTION_BASE States - -- + -- -- * **Detecting**: The detection is running. -- * **Stopped**: The detection is stopped. - -- + -- -- ### DETECTION_BASE Events - -- + -- -- * **Start**: Start the detection process. -- * **Detect**: Detect new units. -- * **Detected**: New units have been detected. -- * **Stop**: Stop the detection process. - -- + -- -- @field #DETECTION_BASE DETECTION_BASE - -- + -- DETECTION_BASE = { ClassName = "DETECTION_BASE", DetectionSetGroup = nil, @@ -269,12 +266,12 @@ do -- DETECTION_BASE DetectionRun = 0, DetectedObjectsIdentified = {}, DetectedItems = {}, - DetectedItemsByIndex = {}, + DetectedItemsByIndex = {}, } - + --- @type DETECTION_BASE.DetectedObjects -- @list <#DETECTION_BASE.DetectedObject> - + --- @type DETECTION_BASE.DetectedObject -- @field #string Name -- @field #boolean IsVisible @@ -287,11 +284,10 @@ do -- DETECTION_BASE -- @field #boolean LastPos -- @field #number LastVelocity - --- @type DETECTION_BASE.DetectedItems -- @list <#DETECTION_BASE.DetectedItem> - - --- Detected item data structrue. + + --- Detected item data structure. -- @type DETECTION_BASE.DetectedItem -- @field #boolean IsDetected Indicates if the DetectedItem has been detected or not. -- @field Core.Set#SET_UNIT Set The Set of Units in the detected area. @@ -302,7 +298,7 @@ do -- DETECTION_BASE -- @field #boolean FriendliesNearBy Indicates if there are friendlies within the detected area. -- @field Wrapper.Unit#UNIT NearestFAC The nearest FAC near the Area. -- @field Core.Point#COORDINATE Coordinate The last known coordinate of the DetectedItem. - -- @field Core.Point#COORDINATE InterceptCoord Intercept coordiante. + -- @field Core.Point#COORDINATE InterceptCoord Intercept coordinate. -- @field #number DistanceRecce Distance in meters of the Recce. -- @field #number Index Detected item key. Could also be a string. -- @field #string ItemID ItemPrefix .. "." .. self.DetectedItemMax. @@ -310,7 +306,7 @@ do -- DETECTION_BASE -- @field #table PlayersNearBy Table of nearby players. -- @field #table FriendliesDistance Table of distances to friendly units. -- @field #string TypeName Type name of the detected unit. - -- @field #string CategoryName Catetory name of the detected unit. + -- @field #string CategoryName Category name of the detected unit. -- @field #string Name Name of the detected object. -- @field #boolean IsVisible If true, detected object is visible. -- @field #number LastTime Last time the detected item was seen. @@ -319,31 +315,31 @@ do -- DETECTION_BASE -- @field #boolean KnowType Type of detected item is known. -- @field #boolean KnowDistance Distance to the detected item is known. -- @field #number Distance Distance to the detected item. - + --- DETECTION constructor. -- @param #DETECTION_BASE self -- @param Core.Set#SET_GROUP DetectionSet The @{Set} of @{Group}s that is used to detect the units. -- @return #DETECTION_BASE self function DETECTION_BASE:New( DetectionSet ) - + -- Inherits from BASE local self = BASE:Inherit( self, FSM:New() ) -- #DETECTION_BASE - + self.DetectedItemCount = 0 self.DetectedItemMax = 0 self.DetectedItems = {} - + self.DetectionSet = DetectionSet - + 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, @@ -351,15 +347,15 @@ do -- DETECTION_BASE Unit.Category.SHIP, Unit.Category.STRUCTURE } ) - + self:SetFriendliesRange( 6000 ) - + -- Create FSM transitions. - + self:SetStartState( "Stopped" ) - - self:AddTransition( "Stopped", "Start", "Detecting") - + + self:AddTransition( "Stopped", "Start", "Detecting" ) + --- OnLeave Transition Handler for State Stopped. -- @function [parent=#DETECTION_BASE] OnLeaveStopped -- @param #DETECTION_BASE self @@ -367,14 +363,14 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnEnter Transition Handler for State Stopped. -- @function [parent=#DETECTION_BASE] OnEnterStopped -- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - + --- OnBefore Transition Handler for Event Start. -- @function [parent=#DETECTION_BASE] OnBeforeStart -- @param #DETECTION_BASE self @@ -382,23 +378,23 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnAfter Transition Handler for Event Start. -- @function [parent=#DETECTION_BASE] OnAfterStart -- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - + --- Synchronous Event Trigger for Event Start. -- @function [parent=#DETECTION_BASE] Start -- @param #DETECTION_BASE self - + --- Asynchronous Event Trigger for Event Start. -- @function [parent=#DETECTION_BASE] __Start -- @param #DETECTION_BASE self -- @param #number Delay The delay in seconds. - + --- OnLeave Transition Handler for State Detecting. -- @function [parent=#DETECTION_BASE] OnLeaveDetecting -- @param #DETECTION_BASE self @@ -406,17 +402,17 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnEnter Transition Handler for State Detecting. -- @function [parent=#DETECTION_BASE] OnEnterDetecting -- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - + self:AddTransition( "Detecting", "Detect", "Detecting" ) self:AddTransition( "Detecting", "Detection", "Detecting" ) - + --- OnBefore Transition Handler for Event Detect. -- @function [parent=#DETECTION_BASE] OnBeforeDetect -- @param #DETECTION_BASE self @@ -424,26 +420,25 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnAfter Transition Handler for Event Detect. -- @function [parent=#DETECTION_BASE] OnAfterDetect -- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - + --- Synchronous Event Trigger for Event Detect. -- @function [parent=#DETECTION_BASE] Detect -- @param #DETECTION_BASE self - + --- Asynchronous Event Trigger for Event Detect. -- @function [parent=#DETECTION_BASE] __Detect -- @param #DETECTION_BASE self -- @param #number Delay The delay in seconds. - - + self:AddTransition( "Detecting", "Detected", "Detecting" ) - + --- OnBefore Transition Handler for Event Detected. -- @function [parent=#DETECTION_BASE] OnBeforeDetected -- @param #DETECTION_BASE self @@ -451,7 +446,7 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnAfter Transition Handler for Event Detected. -- @function [parent=#DETECTION_BASE] OnAfterDetected -- @param #DETECTION_BASE self @@ -459,20 +454,20 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @param #table Units Table of detected units. - + --- Synchronous Event Trigger for Event Detected. -- @function [parent=#DETECTION_BASE] Detected -- @param #DETECTION_BASE self -- @param #table Units Table of detected units. - + --- Asynchronous Event Trigger for Event Detected. -- @function [parent=#DETECTION_BASE] __Detected -- @param #DETECTION_BASE self -- @param #number Delay The delay in seconds. -- @param #table Units Table of detected units. - + self:AddTransition( "Detecting", "DetectedItem", "Detecting" ) - + --- OnAfter Transition Handler for Event DetectedItem. -- @function [parent=#DETECTION_BASE] OnAfterDetectedItem -- @param #DETECTION_BASE self @@ -480,9 +475,9 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @param #table DetectedItem The DetectedItem. - + self:AddTransition( "*", "Stop", "Stopped" ) - + --- OnBefore Transition Handler for Event Stop. -- @function [parent=#DETECTION_BASE] OnBeforeStop -- @param #DETECTION_BASE self @@ -490,23 +485,23 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnAfter Transition Handler for Event Stop. -- @function [parent=#DETECTION_BASE] OnAfterStop -- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - + --- Synchronous Event Trigger for Event Stop. -- @function [parent=#DETECTION_BASE] Stop -- @param #DETECTION_BASE self - + --- Asynchronous Event Trigger for Event Stop. -- @function [parent=#DETECTION_BASE] __Stop -- @param #DETECTION_BASE self -- @param #number Delay The delay in seconds. - + --- OnLeave Transition Handler for State Stopped. -- @function [parent=#DETECTION_BASE] OnLeaveStopped -- @param #DETECTION_BASE self @@ -514,24 +509,24 @@ do -- DETECTION_BASE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnEnter Transition Handler for State Stopped. -- @function [parent=#DETECTION_BASE] OnEnterStopped -- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - + return self end - + do -- State Transition Handling - + --- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - function DETECTION_BASE:onafterStart(From,Event,To) + function DETECTION_BASE:onafterStart( From, Event, To ) self:__Detect( 1 ) end @@ -539,18 +534,18 @@ do -- DETECTION_BASE -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - function DETECTION_BASE:onafterDetect(From,Event,To) + function DETECTION_BASE:onafterDetect( From, Event, To ) local DetectDelay = 0.1 self.DetectionCount = 0 self.DetectionRun = 0 self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table - + local DetectionTimeStamp = timer.getTime() - + -- Reset detection cache for the next detection run. for DetectionObjectName, DetectedObjectData in pairs( self.DetectedObjects ) do - + self.DetectedObjects[DetectionObjectName].IsDetected = false self.DetectedObjects[DetectionObjectName].IsVisible = false self.DetectedObjects[DetectionObjectName].KnowDistance = nil @@ -558,23 +553,21 @@ do -- DETECTION_BASE self.DetectedObjects[DetectionObjectName].LastPos = nil self.DetectedObjects[DetectionObjectName].LastVelocity = nil self.DetectedObjects[DetectionObjectName].Distance = 10000000 - + end - + -- Count alive(!) groups only. Solves issue #1173 https://github.com/FlightControl-Master/MOOSE/issues/1173 self.DetectionCount = self:CountAliveRecce() - - local DetectionInterval = self.DetectionCount / ( self.RefreshTimeInterval - 1 ) - - self:ForEachAliveRecce( - function( DetectionGroup ) - self:__Detection( DetectDelay, DetectionGroup, DetectionTimeStamp ) -- Process each detection asynchronously. - DetectDelay = DetectDelay + DetectionInterval - end - ) - + + local DetectionInterval = self.DetectionCount / (self.RefreshTimeInterval - 1) + + self:ForEachAliveRecce( function( DetectionGroup ) + self:__Detection( DetectDelay, DetectionGroup, DetectionTimeStamp ) -- Process each detection asynchronously. + DetectDelay = DetectDelay + DetectionInterval + end ) + self:__Detect( -self.RefreshTimeInterval ) - + end --- @param #DETECTION_BASE self @@ -583,41 +576,40 @@ do -- DETECTION_BASE return self.DetectionSet:CountAlive() - end - + end + --- @param #DETECTION_BASE self function DETECTION_BASE:ForEachAliveRecce( IteratorFunction, ... ) self:F2( arg ) - + self.DetectionSet:ForEachGroupAlive( IteratorFunction, arg ) - + return self end - - + --- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. -- @param Wrapper.Group#GROUP Detection The Group detecting. -- @param #number DetectionTimeStamp Time stamp of detection event. - function DETECTION_BASE:onafterDetection( From, Event, To, Detection, DetectionTimeStamp ) - - --self:F( { DetectedObjects = self.DetectedObjects } ) - + function DETECTION_BASE:onafterDetection( From, Event, To, Detection, DetectionTimeStamp ) + + -- self:F( { DetectedObjects = self.DetectedObjects } ) + self.DetectionRun = self.DetectionRun + 1 - + local HasDetectedObjects = false - + if Detection and Detection:IsAlive() then - - --self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } ) - + + -- self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } ) + local DetectionGroupName = Detection:GetName() - local DetectionUnit = Detection:GetUnit(1) - + local DetectionUnit = Detection:GetUnit( 1 ) + local DetectedUnits = {} - + local DetectedTargets = Detection:GetDetectedTargets( self.DetectVisual, self.DetectOptical, @@ -626,29 +618,29 @@ do -- DETECTION_BASE self.DetectRWR, self.DetectDLINK ) - + self:F( { DetectedTargets = DetectedTargets } ) - + for DetectionObjectID, Detection in pairs( DetectedTargets ) do local DetectedObject = Detection.object -- DCS#Object - + if DetectedObject and DetectedObject:isExist() and DetectedObject.id_ < 50000000 then -- and ( DetectedObject:getCategory() == Object.Category.UNIT or DetectedObject:getCategory() == Object.Category.STATIC ) then local DetectedObjectName = DetectedObject:getName() if not self.DetectedObjects[DetectedObjectName] then - self.DetectedObjects[DetectedObjectName] = self.DetectedObjects[DetectedObjectName] or {} + self.DetectedObjects[DetectedObjectName] = self.DetectedObjects[DetectedObjectName] or {} self.DetectedObjects[DetectedObjectName].Name = DetectedObjectName self.DetectedObjects[DetectedObjectName].Object = DetectedObject end end end - + for DetectionObjectName, DetectedObjectData in pairs( self.DetectedObjects ) do - + local DetectedObject = DetectedObjectData.Object - + if DetectedObject:isExist() then - - local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity = DetectionUnit:IsTargetDetected( + + local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity = DetectionUnit:IsTargetDetected( DetectedObject, self.DetectVisual, self.DetectOptical, @@ -657,9 +649,9 @@ do -- DETECTION_BASE self.DetectRWR, self.DetectDLINK ) - - --self:T2( { TargetIsDetected = TargetIsDetected, TargetIsVisible = TargetIsVisible, TargetLastTime = TargetLastTime, TargetKnowType = TargetKnowType, TargetKnowDistance = TargetKnowDistance, TargetLastPos = TargetLastPos, TargetLastVelocity = TargetLastVelocity } ) - + + -- self:T2( { TargetIsDetected = TargetIsDetected, TargetIsVisible = TargetIsVisible, TargetLastTime = TargetLastTime, TargetKnowType = TargetKnowType, TargetKnowDistance = TargetKnowDistance, TargetLastPos = TargetLastPos, TargetLastVelocity = TargetLastVelocity } ) + -- Only process if the target is visible. Detection also returns invisible units. --if Detection.visible == true then @@ -787,47 +779,47 @@ do -- DETECTION_BASE if TargetIsDetected and not self.DetectedObjects[DetectedObjectName].KnowType then self.DetectedObjects[DetectedObjectName].KnowType = TargetIsDetected and TargetKnowType - end - self.DetectedObjects[DetectedObjectName].KnowDistance = TargetKnowDistance -- Detection.distance -- TargetKnowDistance - self.DetectedObjects[DetectedObjectName].LastTime = ( TargetIsDetected and TargetIsVisible == false ) and TargetLastTime - self.DetectedObjects[DetectedObjectName].LastPos = ( TargetIsDetected and TargetIsVisible == false ) and TargetLastPos - self.DetectedObjects[DetectedObjectName].LastVelocity = ( TargetIsDetected and TargetIsVisible == false ) and TargetLastVelocity - - if not self.DetectedObjects[DetectedObjectName].Distance or ( Distance and self.DetectedObjects[DetectedObjectName].Distance > Distance ) then - self.DetectedObjects[DetectedObjectName].Distance = Distance - end - - self.DetectedObjects[DetectedObjectName].DetectionTimeStamp = DetectionTimeStamp - - self:F( { DetectedObject = self.DetectedObjects[DetectedObjectName] } ) - - local DetectedUnit = UNIT:FindByName( DetectedObjectName ) - - DetectedUnits[DetectedObjectName] = DetectedUnit - else - -- if beyond the DetectionRange then nullify... - self:F( { DetectedObject = "No more detection for " .. DetectedObjectName } ) - if self.DetectedObjects[DetectedObjectName] then - self.DetectedObjects[DetectedObjectName] = nil - end end - - --self:T2( self.DetectedObjects ) + self.DetectedObjects[DetectedObjectName].KnowDistance = TargetKnowDistance -- Detection.distance -- TargetKnowDistance + self.DetectedObjects[DetectedObjectName].LastTime = (TargetIsDetected and TargetIsVisible == false) and TargetLastTime + self.DetectedObjects[DetectedObjectName].LastPos = (TargetIsDetected and TargetIsVisible == false) and TargetLastPos + self.DetectedObjects[DetectedObjectName].LastVelocity = (TargetIsDetected and TargetIsVisible == false) and TargetLastVelocity + + if not self.DetectedObjects[DetectedObjectName].Distance or (Distance and self.DetectedObjects[DetectedObjectName].Distance > Distance) then + self.DetectedObjects[DetectedObjectName].Distance = Distance + end + + self.DetectedObjects[DetectedObjectName].DetectionTimeStamp = DetectionTimeStamp + + self:F( { DetectedObject = self.DetectedObjects[DetectedObjectName] } ) + + local DetectedUnit = UNIT:FindByName( DetectedObjectName ) + + DetectedUnits[DetectedObjectName] = DetectedUnit + else + -- if beyond the DetectionRange then nullify... + self:F( { DetectedObject = "No more detection for " .. DetectedObjectName } ) + if self.DetectedObjects[DetectedObjectName] then + self.DetectedObjects[DetectedObjectName] = nil + end + end + + -- self:T2( self.DetectedObjects ) else -- The previously detected object does not exist anymore, delete from the cache. self:F( "Removing from DetectedObjects: " .. DetectionObjectName ) self.DetectedObjects[DetectionObjectName] = nil end end - + if HasDetectedObjects then self:__Detected( 0.1, DetectedUnits ) end - + end - + if self.DetectionCount > 0 and self.DetectionRun == self.DetectionCount then - + -- First check if all DetectedObjects were detected. -- This is important. When there are DetectedObjects in the list, but were not detected, -- And these remain undetected for more than 60 seconds, then these DetectedObjects will be flagged as not Detected. @@ -840,45 +832,43 @@ do -- DETECTION_BASE end self:CreateDetectionItems() -- Polymorphic call to Create/Update the DetectionItems list for the DETECTION_ class grouping method. - + for DetectedItemID, DetectedItem in pairs( self.DetectedItems ) do - + self:UpdateDetectedItemDetection( DetectedItem ) - + self:CleanDetectionItem( DetectedItem, DetectedItemID ) -- Any DetectionItem that has a Set with zero elements in it, must be removed from the DetectionItems list. - + if DetectedItem then self:__DetectedItem( 0.1, DetectedItem ) end - + end end - end - - + end - + do -- DetectionItems Creation - + --- Clean the DetectedItem table. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE function DETECTION_BASE:CleanDetectionItem( DetectedItem, DetectedItemID ) - + -- We clean all DetectedItems. -- if there are any remaining DetectedItems with no Set Objects then the Item in the DetectedItems must be deleted. - + local DetectedSet = DetectedItem.Set - + if DetectedSet:Count() == 0 then self:RemoveDetectedItem( DetectedItemID ) end return self end - + --- Forget a Unit from a DetectionItem -- @param #DETECTION_BASE self -- @param #string UnitName The UnitName that needs to be forgotten from the DetectionItem Sets. @@ -886,7 +876,7 @@ do -- DETECTION_BASE function DETECTION_BASE:ForgetDetectedUnit( UnitName ) local DetectedItems = self:GetDetectedItems() - + for DetectedItemIndex, DetectedItem in pairs( DetectedItems ) do local DetectedSet = self:GetDetectedItemSet( DetectedItem ) if DetectedSet then @@ -896,96 +886,95 @@ do -- DETECTION_BASE return self end - + --- Make a DetectionSet table. This function will be overridden in the derived clsses. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE function DETECTION_BASE:CreateDetectionItems() - + self:F( "Error, in DETECTION_BASE class..." ) return self end - end - + do -- Initialization methods - + --- Detect Visual. -- @param #DETECTION_BASE self -- @param #boolean DetectVisual -- @return #DETECTION_BASE self function DETECTION_BASE:InitDetectVisual( DetectVisual ) - + self.DetectVisual = DetectVisual - + return self end - + --- Detect Optical. -- @param #DETECTION_BASE self -- @param #boolean DetectOptical -- @return #DETECTION_BASE self function DETECTION_BASE:InitDetectOptical( DetectOptical ) - self:F2() - + self:F2() + self.DetectOptical = DetectOptical - + return self end - + --- Detect Radar. -- @param #DETECTION_BASE self -- @param #boolean DetectRadar -- @return #DETECTION_BASE self function DETECTION_BASE:InitDetectRadar( DetectRadar ) self:F2() - + self.DetectRadar = DetectRadar - + return self end - + --- Detect IRST. -- @param #DETECTION_BASE self -- @param #boolean DetectIRST -- @return #DETECTION_BASE self function DETECTION_BASE:InitDetectIRST( DetectIRST ) self:F2() - + self.DetectIRST = DetectIRST - + return self end - + --- Detect RWR. -- @param #DETECTION_BASE self -- @param #boolean DetectRWR -- @return #DETECTION_BASE self function DETECTION_BASE:InitDetectRWR( DetectRWR ) self:F2() - + self.DetectRWR = DetectRWR - + return self end - + --- Detect DLINK. -- @param #DETECTION_BASE self -- @param #boolean DetectDLINK -- @return #DETECTION_BASE self function DETECTION_BASE:InitDetectDLINK( DetectDLINK ) self:F2() - + self.DetectDLINK = DetectDLINK - + return self end - + end - + do -- Filter methods - + --- Filter the detected units based on Unit.Category -- The different values of Unit.Category can be: -- @@ -1010,35 +999,35 @@ do -- DETECTION_BASE -- @return #DETECTION_BASE self 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 + end else self._.FilterCategories[FilterCategories] = FilterCategories end return self - + end end do - + --- Set the detection interval time in seconds. -- @param #DETECTION_BASE self -- @param #number RefreshTimeInterval Interval in seconds. -- @return #DETECTION_BASE self function DETECTION_BASE:SetRefreshTimeInterval( RefreshTimeInterval ) self:F2() - + self.RefreshTimeInterval = RefreshTimeInterval - + return self end - + end do -- Friendlies Radius @@ -1047,18 +1036,18 @@ do -- DETECTION_BASE -- @param #DETECTION_BASE self -- @param #number FriendliesRange Radius to use when checking if Friendlies are nearby. -- @return #DETECTION_BASE self - function DETECTION_BASE:SetFriendliesRange( FriendliesRange ) --R2.2 Friendlies range + function DETECTION_BASE:SetFriendliesRange( FriendliesRange ) -- R2.2 Friendlies range self:F2() - + self.FriendliesRange = FriendliesRange - + return self end - + end - + do -- Intercept Point - + --- Set the parameters to calculate to optimal intercept point. -- @param #DETECTION_BASE self -- @param #boolean Intercept Intercept is true if an intercept point is calculated. Intercept is false if it is disabled. The default Intercept is false. @@ -1066,36 +1055,36 @@ do -- DETECTION_BASE -- @return #DETECTION_BASE self function DETECTION_BASE:SetIntercept( Intercept, InterceptDelay ) self:F2() - + self.Intercept = Intercept self.InterceptDelay = InterceptDelay - + return self end end - + do -- Accept / Reject detected units - + --- Accept detections if within a range in meters. -- @param #DETECTION_BASE self -- @param #number AcceptRange Accept a detection if the unit is within the AcceptRange in meters. -- @return #DETECTION_BASE self function DETECTION_BASE:SetAcceptRange( AcceptRange ) self:F2() - + self.AcceptRange = AcceptRange - + return self end - + --- Accept detections if within the specified zone(s). -- @param #DETECTION_BASE self -- @param Core.Zone#ZONE_BASE AcceptZones Can be a list or ZONE_BASE objects, or a single ZONE_BASE object. -- @return #DETECTION_BASE self function DETECTION_BASE:SetAcceptZones( AcceptZones ) self:F2() - + if type( AcceptZones ) == "table" then if AcceptZones.ClassName and AcceptZones:IsInstanceOf( ZONE_BASE ) then self.AcceptZones = { AcceptZones } @@ -1106,17 +1095,17 @@ do -- DETECTION_BASE self:F( { "AcceptZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object", AcceptZones } ) error() end - + return self end - + --- Reject detections if within the specified zone(s). -- @param #DETECTION_BASE self -- @param Core.Zone#ZONE_BASE RejectZones Can be a list or ZONE_BASE objects, or a single ZONE_BASE object. -- @return #DETECTION_BASE self function DETECTION_BASE:SetRejectZones( RejectZones ) self:F2() - + if type( RejectZones ) == "table" then if RejectZones.ClassName and RejectZones:IsInstanceOf( ZONE_BASE ) then self.RejectZones = { RejectZones } @@ -1127,14 +1116,14 @@ do -- DETECTION_BASE self:F( { "RejectZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object", RejectZones } ) error() end - + return self end - + end - + do -- Probability methods - + --- Upon a **visual** detection, the further away a detected object is, the less likely it is to be detected properly. -- Also, the speed of accurate detection plays a role. -- A distance probability factor between 0 and 1 can be given, that will model a linear extrapolated probability over 10 km distance. @@ -1145,13 +1134,12 @@ do -- DETECTION_BASE -- @return #DETECTION_BASE self function DETECTION_BASE:SetDistanceProbability( DistanceProbability ) self:F2() - + self.DistanceProbability = DistanceProbability - + return self end - - + --- Upon a **visual** detection, the higher the unit is during the detecting process, the more likely the detected unit is to be detected properly. -- A detection at a 90% alpha angle is the most optimal, a detection at 10% is less and a detection at 0% is less likely to be correct. -- @@ -1164,12 +1152,12 @@ do -- DETECTION_BASE -- @return #DETECTION_BASE self function DETECTION_BASE:SetAlphaAngleProbability( AlphaAngleProbability ) self:F2() - + self.AlphaAngleProbability = AlphaAngleProbability - + return self end - + --- Upon a **visual** detection, the more a detected unit is within a cloudy zone, the less likely the detected unit is to be detected successfully. -- The Cloudy Zones work with the ZONE_BASE derived classes. The mission designer can define within the mission -- zones that reflect cloudy areas where detected units may not be so easily visually detected. @@ -1178,26 +1166,25 @@ do -- DETECTION_BASE -- @return #DETECTION_BASE self function DETECTION_BASE:SetZoneProbability( ZoneArray ) self:F2() - - self.ZoneProbability = ZoneArray - + + self.ZoneProbability = ZoneArray + return self end - - + end - + do -- Change processing - + --- Accepts changes from the detected item. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return #DETECTION_BASE self function DETECTION_BASE:AcceptChanges( DetectedItem ) - + DetectedItem.Changed = false DetectedItem.Changes = {} - + return self end @@ -1207,21 +1194,20 @@ do -- DETECTION_BASE -- @param #string ChangeCode -- @return #DETECTION_BASE self 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:F( { "Change on Detected Item:", DetectedItemID = DetectedItem.ID, ChangeCode = ChangeCode, ItemUnitType = ItemUnitType } ) - + return self end - - + --- Add a change to the detected zone. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem @@ -1229,25 +1215,25 @@ do -- DETECTION_BASE -- @param #string ChangeUnitType -- @return #DETECTION_BASE self 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:F( { "Change on Detected Unit:", DetectedItemID = DetectedItem.ID, ChangeCode = ChangeCode, ChangeUnitType = ChangeUnitType } ) - + return self end - + end - + do -- Friendly calculations - + --- This will allow during friendly search any recce or detection unit to be also considered as a friendly. -- By default, recce aren't considered friendly, because that would mean that a recce would be also an attacking friendly, -- and this is wrong. @@ -1266,9 +1252,9 @@ do -- DETECTION_BASE self:F( { FriendlyPrefix = Prefix } ) self.FriendlyPrefixes[Prefix] = Prefix end - return self + return self end - + --- This will allow during friendly search only units of the specified list of categories. -- @param #DETECTION_BASE self -- @param #string FriendlyCategories A list of unit categories. @@ -1276,113 +1262,112 @@ do -- DETECTION_BASE -- @usage -- -- Only allow Ships and Vehicles to be part of the friendly team. -- Detection:SetFriendlyCategories( { Unit.Category.SHIP, Unit.Category.GROUND_UNIT } ) - + --- Returns if there are friendlies nearby the FAC units ... -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @param DCS#Unit.Category Category The category of the unit. -- @return #boolean true if there are friendlies nearby function DETECTION_BASE:IsFriendliesNearBy( DetectedItem, Category ) --- self:F( { "FriendliesNearBy Test", DetectedItem.FriendliesNearBy } ) - return ( DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category] ~= nil ) or false + -- self:F( { "FriendliesNearBy Test", DetectedItem.FriendliesNearBy } ) + return (DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category] ~= nil) or false end - + --- Returns friendly units nearby the FAC units ... -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @param DCS#Unit.Category Category The category of the unit. - -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. + -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. function DETECTION_BASE:GetFriendliesNearBy( DetectedItem, Category ) - + return DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category] end - + --- Returns if there are friendlies nearby the intercept ... -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return #boolean trhe if there are friendlies near the intercept. function DETECTION_BASE:IsFriendliesNearIntercept( DetectedItem ) - + return DetectedItem.FriendliesNearIntercept ~= nil or false end - + --- Returns friendly units nearby the intercept point ... -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The detected item. -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. function DETECTION_BASE:GetFriendliesNearIntercept( DetectedItem ) - + return DetectedItem.FriendliesNearIntercept end - - --- Returns the distance used to identify friendlies near the deteted item ... + + --- Returns the distance used to identify friendlies near the detected item ... -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The detected item. -- @return #table A table of distances to friendlies. function DETECTION_BASE:GetFriendliesDistance( DetectedItem ) - + return DetectedItem.FriendliesDistance end - + --- Returns if there are friendlies nearby the FAC units ... -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem - -- @return #boolean trhe if there are friendlies nearby + -- @return #boolean true if there are friendlies nearby function DETECTION_BASE:IsPlayersNearBy( DetectedItem ) - + return DetectedItem.PlayersNearBy ~= nil end - + --- Returns friendly units nearby the FAC units ... -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The detected item. - -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. + -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. function DETECTION_BASE:GetPlayersNearBy( DetectedItem ) - + return DetectedItem.PlayersNearBy end - + --- Background worker function to determine if there are friendlies nearby ... -- @param #DETECTION_BASE self -- @param #table TargetData function DETECTION_BASE:ReportFriendliesNearBy( TargetData ) - --self:F( { "Search Friendlies", DetectedItem = TargetData.DetectedItem } ) - - local DetectedItem = TargetData.DetectedItem --#DETECTION_BASE.DetectedItem + -- self:F( { "Search Friendlies", DetectedItem = TargetData.DetectedItem } ) + + local DetectedItem = TargetData.DetectedItem -- #DETECTION_BASE.DetectedItem local DetectedSet = TargetData.DetectedItem.Set local DetectedUnit = DetectedSet:GetFirst() -- Wrapper.Unit#UNIT - + DetectedItem.FriendliesNearBy = nil -- We need to ensure that the DetectedUnit is alive! if DetectedUnit and DetectedUnit:IsAlive() then - + local DetectedUnitCoord = DetectedUnit:GetCoordinate() local InterceptCoord = TargetData.InterceptCoord or DetectedUnitCoord - + local SphereSearch = { - id = world.VolumeType.SPHERE, + id = world.VolumeType.SPHERE, params = { - point = InterceptCoord:GetVec3(), - radius = self.FriendliesRange, + point = InterceptCoord:GetVec3(), + radius = self.FriendliesRange, } - - } - + } + --- @param DCS#Unit FoundDCSUnit -- @param Wrapper.Group#GROUP ReportGroup -- @param Core.Set#SET_GROUP ReportSetGroup local FindNearByFriendlies = function( FoundDCSUnit, ReportGroupData ) - - local DetectedItem = ReportGroupData.DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem + + local DetectedItem = ReportGroupData.DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedSet = ReportGroupData.DetectedItem.Set local DetectedUnit = DetectedSet:GetFirst() -- Wrapper.Unit#UNIT local DetectedUnitCoord = DetectedUnit:GetCoordinate() local InterceptCoord = ReportGroupData.InterceptCoord or DetectedUnitCoord local ReportSetGroup = ReportGroupData.ReportSetGroup - + local EnemyCoalition = DetectedUnit:GetCoalition() - + local FoundUnitCoalition = FoundDCSUnit:getCoalition() local FoundUnitCategory = FoundDCSUnit:getDesc().category local FoundUnitName = FoundDCSUnit:getName() @@ -1390,25 +1375,25 @@ do -- DETECTION_BASE local EnemyUnitName = DetectedUnit:GetName() local FoundUnitInReportSetGroup = ReportSetGroup:FindGroup( FoundUnitGroupName ) ~= nil - --self:T( { "Friendlies search:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) - + -- self:T( { "Friendlies search:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) + if FoundUnitInReportSetGroup == true then -- If the recce was part of the friendlies found, then check if the recce is part of the allowed friendly unit prefixes. for PrefixID, Prefix in pairs( self.FriendlyPrefixes or {} ) do - --self:F( { "Friendly Prefix:", Prefix = Prefix } ) + -- self:F( { "Friendly Prefix:", Prefix = Prefix } ) -- In case a match is found (so a recce unit name is part of the friendly prefixes), then report that recce to be part of the friendlies. -- This is important if CAP planes (so planes using their own radar) to be scanning for targets as part of the EWR network. -- But CAP planes are also attackers, so they need to be considered friendlies too! -- I chose to use prefixes because it is the fastest way to check. - if string.find( FoundUnitName, Prefix:gsub ("-", "%%-"), 1 ) then + if string.find( FoundUnitName, Prefix:gsub( "-", "%%-" ), 1 ) then FoundUnitInReportSetGroup = false break end end end - - --self:F( { "Friendlies near Target:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) - + + -- self:F( { "Friendlies near Target:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) + if FoundUnitCoalition ~= EnemyCoalition and FoundUnitInReportSetGroup == false then local FriendlyUnit = UNIT:Find( FoundDCSUnit ) local FriendlyUnitName = FriendlyUnit:GetName() @@ -1422,65 +1407,64 @@ do -- DETECTION_BASE local Distance = DetectedUnitCoord:Get2DDistance( FriendlyUnit:GetCoordinate() ) DetectedItem.FriendliesDistance = DetectedItem.FriendliesDistance or {} DetectedItem.FriendliesDistance[Distance] = FriendlyUnit - --self:F( { "Friendlies Found:", FriendlyUnitName = FriendlyUnitName, Distance = Distance, FriendlyUnitCategory = FriendlyUnitCategory, FriendliesCategory = self.FriendliesCategory } ) + -- self:F( { "Friendlies Found:", FriendlyUnitName = FriendlyUnitName, Distance = Distance, FriendlyUnitCategory = FriendlyUnitCategory, FriendliesCategory = self.FriendliesCategory } ) return true end - + return true end - + world.searchObjects( Object.Category.UNIT, SphereSearch, FindNearByFriendlies, TargetData ) DetectedItem.PlayersNearBy = nil - + _DATABASE:ForEachPlayer( - --- @param Wrapper.Unit#UNIT PlayerUnit - function( PlayerUnitName ) - local PlayerUnit = UNIT:FindByName( PlayerUnitName ) + --- @param Wrapper.Unit#UNIT PlayerUnit + function( PlayerUnitName ) + local PlayerUnit = UNIT:FindByName( PlayerUnitName ) - -- Fix for issue https://github.com/FlightControl-Master/MOOSE/issues/1225 - if PlayerUnit and PlayerUnit:IsAlive() then - local coord=PlayerUnit:GetCoordinate() - - if coord and coord:IsInRadius( DetectedUnitCoord, self.FriendliesRange ) then + -- Fix for issue https://github.com/FlightControl-Master/MOOSE/issues/1225 + if PlayerUnit and PlayerUnit:IsAlive() then + local coord = PlayerUnit:GetCoordinate() + + if coord and coord:IsInRadius( DetectedUnitCoord, self.FriendliesRange ) then + + local PlayerUnitCategory = PlayerUnit:GetDesc().category + + if (not self.FriendliesCategory) or (self.FriendliesCategory and (self.FriendliesCategory == PlayerUnitCategory)) then + + local PlayerUnitName = PlayerUnit:GetName() + + DetectedItem.PlayersNearBy = DetectedItem.PlayersNearBy or {} + DetectedItem.PlayersNearBy[PlayerUnitName] = PlayerUnit + + -- Friendlies are sorted per unit category. + DetectedItem.FriendliesNearBy = DetectedItem.FriendliesNearBy or {} + DetectedItem.FriendliesNearBy[PlayerUnitCategory] = DetectedItem.FriendliesNearBy[PlayerUnitCategory] or {} + DetectedItem.FriendliesNearBy[PlayerUnitCategory][PlayerUnitName] = PlayerUnit + + local Distance = DetectedUnitCoord:Get2DDistance( PlayerUnit:GetCoordinate() ) + DetectedItem.FriendliesDistance = DetectedItem.FriendliesDistance or {} + DetectedItem.FriendliesDistance[Distance] = PlayerUnit - local PlayerUnitCategory = PlayerUnit:GetDesc().category - - if ( not self.FriendliesCategory ) or ( self.FriendliesCategory and ( self.FriendliesCategory == PlayerUnitCategory ) ) then - - local PlayerUnitName = PlayerUnit:GetName() - - DetectedItem.PlayersNearBy = DetectedItem.PlayersNearBy or {} - DetectedItem.PlayersNearBy[PlayerUnitName] = PlayerUnit - - -- Friendlies are sorted per unit category. - DetectedItem.FriendliesNearBy = DetectedItem.FriendliesNearBy or {} - DetectedItem.FriendliesNearBy[PlayerUnitCategory] = DetectedItem.FriendliesNearBy[PlayerUnitCategory] or {} - DetectedItem.FriendliesNearBy[PlayerUnitCategory][PlayerUnitName] = PlayerUnit - - local Distance = DetectedUnitCoord:Get2DDistance( PlayerUnit:GetCoordinate() ) - DetectedItem.FriendliesDistance = DetectedItem.FriendliesDistance or {} - DetectedItem.FriendliesDistance[Distance] = PlayerUnit - - end end end end - ) - end + end ) + end self:F( { Friendlies = DetectedItem.FriendliesNearBy, Players = DetectedItem.PlayersNearBy } ) - + end - + end - + --- Determines if a detected object has already been identified during detection processing. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedObject DetectedObject -- @return #boolean true if already identified. function DETECTION_BASE:IsDetectedObjectIdentified( DetectedObject ) - + local DetectedObjectName = DetectedObject.Name if DetectedObjectName then local DetectedObjectIdentified = self.DetectedObjectsIdentified[DetectedObjectName] == true @@ -1489,71 +1473,70 @@ do -- DETECTION_BASE return nil end end - + --- Identifies a detected object during detection processing. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedObject DetectedObject function DETECTION_BASE:IdentifyDetectedObject( DetectedObject ) - --self:F( { "Identified:", DetectedObject.Name } ) - + -- self:F( { "Identified:", DetectedObject.Name } ) + local DetectedObjectName = DetectedObject.Name self.DetectedObjectsIdentified[DetectedObjectName] = true end - + --- UnIdentify a detected object during detection processing. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedObject DetectedObject function DETECTION_BASE:UnIdentifyDetectedObject( DetectedObject ) - + local DetectedObjectName = DetectedObject.Name self.DetectedObjectsIdentified[DetectedObjectName] = false end - + --- UnIdentify all detected objects during detection processing. -- @param #DETECTION_BASE self function DETECTION_BASE:UnIdentifyAllDetectedObjects() - + self.DetectedObjectsIdentified = {} -- Table will be garbage collected. end - + --- Gets a detected object with a given name. -- @param #DETECTION_BASE self -- @param #string ObjectName -- @return #DETECTION_BASE.DetectedObject function DETECTION_BASE:GetDetectedObject( ObjectName ) - self:F2( { ObjectName = ObjectName } ) - + self:F2( { ObjectName = ObjectName } ) + if ObjectName then local DetectedObject = self.DetectedObjects[ObjectName] - + if DetectedObject then - --self:F( { DetectedObjects = self.DetectedObjects } ) + -- self:F( { DetectedObjects = self.DetectedObjects } ) -- Only return detected objects that are alive! local DetectedUnit = UNIT:FindByName( ObjectName ) if DetectedUnit and DetectedUnit:IsAlive() then if self:IsDetectedObjectIdentified( DetectedObject ) == false then - --self:F( { DetectedObject = DetectedObject } ) + -- self:F( { DetectedObject = DetectedObject } ) return DetectedObject end end end end - + return nil end - --- Gets a detected unit type name, taking into account the detection results. -- @param #DETECTION_BASE self -- @param Wrapper.Unit#UNIT DetectedUnit -- @return #string The type name function DETECTION_BASE:GetDetectedUnitTypeName( DetectedUnit ) - --self:F2( ObjectName ) - + -- self:F2( ObjectName ) + 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() @@ -1566,11 +1549,10 @@ do -- DETECTION_BASE else return "Dead:" .. DetectedUnit:GetName() end - + return "Undetected:" .. DetectedUnit:GetName() end - --- Adds a new DetectedItem to the DetectedItems list. -- The DetectedItem is a table and contains a SET_UNIT in the field Set. -- @param #DETECTION_BASE self @@ -1579,29 +1561,28 @@ do -- DETECTION_BASE -- @param Core.Set#SET_UNIT Set (optional) The Set of Units to be added. -- @return #DETECTION_BASE.DetectedItem function DETECTION_BASE:AddDetectedItem( ItemPrefix, DetectedItemKey, Set ) - - local DetectedItem = {} --#DETECTION_BASE.DetectedItem + + local DetectedItem = {} -- #DETECTION_BASE.DetectedItem self.DetectedItemCount = self.DetectedItemCount + 1 self.DetectedItemMax = self.DetectedItemMax + 1 - - + DetectedItemKey = DetectedItemKey or self.DetectedItemMax self.DetectedItems[DetectedItemKey] = DetectedItem self.DetectedItemsByIndex[DetectedItemKey] = DetectedItem DetectedItem.Index = DetectedItemKey - + DetectedItem.Set = Set or SET_UNIT:New():FilterDeads():FilterCrashes() DetectedItem.ItemID = ItemPrefix .. "." .. self.DetectedItemMax DetectedItem.ID = self.DetectedItemMax DetectedItem.Removed = false - + if self.Locking then self:LockDetectedItem( DetectedItem ) end - + return DetectedItem end - + --- Adds a new DetectedItem to the DetectedItems list. -- The DetectedItem is a table and contains a SET_UNIT in the field Set. -- @param #DETECTION_BASE self @@ -1610,22 +1591,22 @@ do -- DETECTION_BASE -- @param Core.Zone#ZONE_UNIT Zone (optional) The Zone to be added where the Units are located. -- @return #DETECTION_BASE.DetectedItem function DETECTION_BASE:AddDetectedItemZone( ItemPrefix, DetectedItemKey, Set, Zone ) - + self:F( { ItemPrefix, DetectedItemKey, Set, Zone } ) - + local DetectedItem = self:AddDetectedItem( ItemPrefix, DetectedItemKey, Set ) DetectedItem.Zone = Zone - + return DetectedItem end - + --- Removes an existing DetectedItem from the DetectedItems list. -- The DetectedItem is a table and contains a SET_UNIT in the field Set. -- @param #DETECTION_BASE self -- @param DetectedItemKey The key in the DetectedItems list where the item needs to be removed. function DETECTION_BASE:RemoveDetectedItem( DetectedItemKey ) - + local DetectedItem = self.DetectedItems[DetectedItemKey] if DetectedItem then @@ -1635,125 +1616,124 @@ do -- DETECTION_BASE self.DetectedItems[DetectedItemKey] = nil end end - - + --- Get the DetectedItems by Key. -- This will return the DetectedItems collection, indexed by the Key, which can be any object that acts as the key of the detection. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE.DetectedItems function DETECTION_BASE:GetDetectedItems() - + return self.DetectedItems end - + --- Get the DetectedItems by Index. -- This will return the DetectedItems collection, indexed by an internal numerical Index. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE.DetectedItems function DETECTION_BASE:GetDetectedItemsByIndex() - + return self.DetectedItemsByIndex end - + --- Get the amount of SETs with detected objects. -- @param #DETECTION_BASE self - -- @return #number The amount of detected items. Note that the amount of detected items can differ with the reality, because detections are not real-time but doen in intervals! + -- @return #number The amount of detected items. Note that the amount of detected items can differ with the reality, because detections are not real-time but done in intervals! function DETECTION_BASE:GetDetectedItemsCount() - + local DetectedCount = self.DetectedItemCount return DetectedCount end - + --- Get a detected item using a given Key. -- @param #DETECTION_BASE self -- @param Key -- @return #DETECTION_BASE.DetectedItem function DETECTION_BASE:GetDetectedItemByKey( Key ) - + self:F( { DetectedItems = self.DetectedItems } ) - + local DetectedItem = self.DetectedItems[Key] if DetectedItem then return DetectedItem end - + return nil end - + --- Get a detected item using a given numeric index. -- @param #DETECTION_BASE self -- @param #number Index -- @return #DETECTION_BASE.DetectedItem function DETECTION_BASE:GetDetectedItemByIndex( Index ) - + self:F( { self.DetectedItemsByIndex } ) - + local DetectedItem = self.DetectedItemsByIndex[Index] if DetectedItem then return DetectedItem end - + return nil end - + --- Get a detected ItemID using a given numeric index. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. -- @return #string DetectedItemID - function DETECTION_BASE:GetDetectedItemID( DetectedItem ) --R2.1 - + function DETECTION_BASE:GetDetectedItemID( DetectedItem ) -- R2.1 + return DetectedItem and DetectedItem.ItemID or "" end - + --- Get a detected ID using a given numeric index. -- @param #DETECTION_BASE self -- @param #number Index -- @return #string DetectedItemID - function DETECTION_BASE:GetDetectedID( Index ) --R2.1 - + function DETECTION_BASE:GetDetectedID( Index ) -- R2.1 + local DetectedItem = self.DetectedItemsByIndex[Index] if DetectedItem then return DetectedItem.ID end - + return "" end - - --- Get the @{Core.Set#SET_UNIT} of a detecttion area using a given numeric index. + + --- Get the @{Core.Set#SET_UNIT} of a detection area using a given numeric index. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return Core.Set#SET_UNIT DetectedSet function DETECTION_BASE:GetDetectedItemSet( DetectedItem ) - + local DetectedSetUnit = DetectedItem and DetectedItem.Set if DetectedSetUnit then return DetectedSetUnit end - + return nil end - + --- Set IsDetected flag for the DetectedItem, which can have more units. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE.DetectedItem DetectedItem -- @return #boolean true if at least one UNIT is detected from the DetectedSet, false if no UNIT was detected from the DetectedSet. 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}) + 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 @@ -1761,31 +1741,30 @@ do -- DETECTION_BASE -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return #boolean true if at least one UNIT is detected from the DetectedSet, false if no UNIT was detected from the DetectedSet. - function DETECTION_BASE:IsDetectedItemDetected( DetectedItem ) - + function DETECTION_BASE:IsDetectedItemDetected( DetectedItem ) + return DetectedItem.IsDetected end - do -- Zones - + --- Get the @{Core.Zone#ZONE_UNIT} of a detection area using a given numeric index. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. -- @return Core.Zone#ZONE_UNIT DetectedZone function DETECTION_BASE:GetDetectedItemZone( DetectedItem ) - + local DetectedZone = DetectedItem and DetectedItem.Zone if DetectedZone then return DetectedZone end - + local Detected - + return nil end - end + end --- Lock the detected items when created and lock all existing detected items. -- @param #DETECTION_BASE self @@ -1800,7 +1779,6 @@ do -- DETECTION_BASE return self end - --- Unlock the detected items when created and unlock all existing detected items. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE @@ -1813,7 +1791,7 @@ do -- DETECTION_BASE return self end - + --- Validate if the detected item is locked. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. @@ -1823,7 +1801,6 @@ do -- DETECTION_BASE return self.Locking and DetectedItem.Locked == true end - --- Lock a detected item. -- @param #DETECTION_BASE self @@ -1847,9 +1824,6 @@ do -- DETECTION_BASE return self end - - - --- Set the detected item coordinate. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem to set the coordinate at. @@ -1858,7 +1832,7 @@ do -- DETECTION_BASE -- @return #DETECTION_BASE function DETECTION_BASE:SetDetectedItemCoordinate( DetectedItem, Coordinate, DetectedItemUnit ) self:F( { Coordinate = Coordinate } ) - + if DetectedItem then if DetectedItemUnit then DetectedItem.Coordinate = Coordinate @@ -1869,18 +1843,17 @@ do -- DETECTION_BASE end end - --- Get the detected item coordinate. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem to set the coordinate at. -- @return Core.Point#COORDINATE function DETECTION_BASE:GetDetectedItemCoordinate( DetectedItem ) self:F( { DetectedItem = DetectedItem } ) - + if DetectedItem then return DetectedItem.Coordinate end - + return nil end @@ -1888,30 +1861,28 @@ do -- DETECTION_BASE -- @param #DETECTION_BASE self -- @return #table A table of Core.Point#COORDINATE function DETECTION_BASE:GetDetectedItemCoordinates() - + local Coordinates = {} - + for DetectedItemID, DetectedItem in pairs( self:GetDetectedItems() ) do Coordinates[DetectedItem] = self:GetDetectedItemCoordinate( DetectedItem ) end - + return Coordinates end - --- Set the detected item threatlevel. + --- Set the detected item threat level. -- @param #DETECTION_BASE self - -- @param #DETECTION_BASE.DetectedItem The DetectedItem to calculate the threatlevel for. + -- @param #DETECTION_BASE.DetectedItem The DetectedItem to calculate the threat level for. -- @return #DETECTION_BASE function DETECTION_BASE:SetDetectedItemThreatLevel( DetectedItem ) - + local DetectedSet = DetectedItem.Set - + if DetectedItem then DetectedItem.ThreatLevel, DetectedItem.ThreatText = DetectedSet:CalculateThreatLevelA2G() end end - - --- Get the detected item coordinate. -- @param #DETECTION_BASE self @@ -1919,15 +1890,14 @@ do -- DETECTION_BASE -- @return #number ThreatLevel function DETECTION_BASE:GetDetectedItemThreatLevel( DetectedItem ) self:F( { DetectedItem = DetectedItem } ) - + if DetectedItem then self:F( { ThreatLevel = DetectedItem.ThreatLevel, ThreatText = DetectedItem.ThreatText } ) return DetectedItem.ThreatLevel or 0, DetectedItem.ThreatText or "" end - + return nil, "" end - --- Report summary of a detected item using a given numeric index. -- @param #DETECTION_BASE self @@ -1939,8 +1909,8 @@ do -- DETECTION_BASE self:F() return nil end - - --- Report detailed of a detectedion result. + + --- Report detailed of a detection result. -- @param #DETECTION_BASE self -- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for. -- @return #string @@ -1948,25 +1918,25 @@ do -- DETECTION_BASE self:F() return nil end - + --- Get the Detection Set. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE self function DETECTION_BASE:GetDetectionSet() - + local DetectionSet = self.DetectionSet return DetectionSet end - - --- Find the nearest Recce of the DetectedItem. + + --- Find the nearest Recce of the DetectedItem. -- @param #DETECTION_BASE self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return Wrapper.Unit#UNIT The nearest FAC unit function DETECTION_BASE:NearestRecce( DetectedItem ) - + local NearestRecce = nil local DistanceRecce = 1000000000 -- Units are not further than 1000000 km away from an area :-) - + for RecceGroupName, RecceGroup in pairs( self.DetectionSet:GetSet() ) do if RecceGroup and RecceGroup:IsAlive() then for RecceUnit, RecceUnit in pairs( RecceGroup:GetUnits() ) do @@ -1981,14 +1951,12 @@ do -- DETECTION_BASE end end end - + DetectedItem.NearestFAC = NearestRecce DetectedItem.DistanceRecce = DistanceRecce - + end - - - + --- Schedule the DETECTION construction. -- @param #DETECTION_BASE self -- @param #number DelayTime The delay in seconds to wait the reporting. @@ -1996,10 +1964,10 @@ do -- DETECTION_BASE -- @return #DETECTION_BASE self 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 @@ -2013,31 +1981,31 @@ do -- DETECTION_UNITS -- @extends Functional.Detection#DETECTION_BASE --- Will detect units within the battle zone. - -- + -- -- It will build a DetectedItems list filled with DetectedItems. Each DetectedItem will contain a field Set, which contains a @{Core.Set#SET_UNIT} containing ONE @{UNIT} object reference. - -- Beware that when the amount of units detected is large, the DetectedItems list will be large also. - -- + -- Beware that when the amount of units detected is large, the DetectedItems list will be large also. + -- -- @field #DETECTION_UNITS DETECTION_UNITS = { ClassName = "DETECTION_UNITS", DetectionRange = nil, } - + --- DETECTION_UNITS constructor. -- @param Functional.Detection#DETECTION_UNITS self -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. -- @return Functional.Detection#DETECTION_UNITS self function DETECTION_UNITS:New( DetectionSetGroup ) - + -- Inherits from DETECTION_BASE local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) ) -- #DETECTION_UNITS - + self._SmokeDetectedUnits = false self._FlareDetectedUnits = false self._SmokeDetectedZones = false self._FlareDetectedZones = false self._BoundDetectedZones = false - + return self end @@ -2047,70 +2015,69 @@ do -- DETECTION_UNITS -- @return #string The Changes text 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 + if ChangeUnitType ~= "ID" then + MTUT[#MTUT + 1] = ChangeUnitCount .. " of " .. ChangeUnitType end end - MT[#MT+1] = " New target(s) detected: " .. table.concat( MTUT, ", " ) .. "." + 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 + if ChangeUnitType ~= "ID" then + MTUT[#MTUT + 1] = ChangeUnitCount .. " of " .. ChangeUnitType end end - MT[#MT+1] = " Invisible or destroyed target(s): " .. table.concat( MTUT, ", " ) .. "." + MT[#MT + 1] = " Invisible or destroyed target(s): " .. table.concat( MTUT, ", " ) .. "." end - + end - + return table.concat( MT, "\n" ) - + end - - - --- Create the DetectedItems list from the DetectedObjects table. + + --- Create the DetectedItems list from the DetectedObjects table. -- For each DetectedItem, a one field array is created containing the Unit detected. -- @param #DETECTION_UNITS self -- @return #DETECTION_UNITS self function DETECTION_UNITS:CreateDetectionItems() -- Loop the current detected items, and check if each object still exists and is detected. - + for DetectedItemKey, _DetectedItem in pairs( self.DetectedItems ) do - local DetectedItem=_DetectedItem --#DETECTION_BASE.DetectedItem - + local DetectedItem = _DetectedItem -- #DETECTION_BASE.DetectedItem + local DetectedItemSet = DetectedItem.Set -- Core.Set#SET_UNIT - + for DetectedUnitName, DetectedUnitData in pairs( DetectedItemSet:GetSet() ) do local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT local DetectedObject = nil - --self:F( DetectedUnit ) + -- self:F( DetectedUnit ) if DetectedUnit:IsAlive() then - --self:F(DetectedUnit:GetName()) + -- self:F(DetectedUnit:GetName()) DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) end if DetectedObject then - + -- Yes, the DetectedUnit is still detected or exists. Flag as identified. self:IdentifyDetectedObject( DetectedObject ) - + self:F( { "**DETECTED**", IsVisible = DetectedObject.IsVisible } ) -- Update the detection with the new data provided. - DetectedItem.TypeName = DetectedUnit:GetTypeName() - DetectedItem.CategoryName = DetectedUnit:GetCategoryName() + DetectedItem.TypeName = DetectedUnit:GetTypeName() + DetectedItem.CategoryName = DetectedUnit:GetCategoryName() DetectedItem.Name = DetectedObject.Name - DetectedItem.IsVisible = DetectedObject.IsVisible + DetectedItem.IsVisible = DetectedObject.IsVisible DetectedItem.LastTime = DetectedObject.LastTime DetectedItem.LastPos = DetectedObject.LastPos DetectedItem.LastVelocity = DetectedObject.LastVelocity @@ -2130,25 +2097,24 @@ do -- DETECTION_UNITS end end - -- Now we need to loop through the unidentified detected units and add these... These are all new items. 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 ) -- Wrapper.Unit#UNIT - + if DetectedUnit then local DetectedTypeName = DetectedUnit:GetTypeName() local DetectedItem = self:GetDetectedItemByKey( DetectedUnitName ) if not DetectedItem then self:T( "Added new DetectedItem" ) DetectedItem = self:AddDetectedItem( "UNIT", DetectedUnitName ) - DetectedItem.TypeName = DetectedUnit:GetTypeName() + DetectedItem.TypeName = DetectedUnit:GetTypeName() DetectedItem.Name = DetectedObject.Name - DetectedItem.IsVisible = DetectedObject.IsVisible + DetectedItem.IsVisible = DetectedObject.IsVisible DetectedItem.LastTime = DetectedObject.LastTime DetectedItem.LastPos = DetectedObject.LastPos DetectedItem.LastVelocity = DetectedObject.LastVelocity @@ -2156,18 +2122,18 @@ do -- DETECTION_UNITS DetectedItem.KnowDistance = DetectedObject.KnowDistance DetectedItem.Distance = DetectedObject.Distance end - + DetectedItem.Set:AddUnit( DetectedUnit ) self:AddChangeUnit( DetectedItem, "AU", DetectedTypeName ) end - end + end end - + for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - + local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set - + -- Set the last known coordinate. local DetectedFirstUnit = DetectedSet:GetFirst() local DetectedFirstUnitCoord = DetectedFirstUnit:GetCoordinate() @@ -2176,12 +2142,11 @@ do -- DETECTION_UNITS self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSet } ) -- Fill the Friendlies table self:SetDetectedItemThreatLevel( DetectedItem ) self:NearestRecce( DetectedItem ) - + end - + end - --- Report summary of a DetectedItem using a given numeric index. -- @param #DETECTION_UNITS self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. @@ -2190,14 +2155,14 @@ do -- DETECTION_UNITS -- @return Core.Report#REPORT The report of the detection items. function DETECTION_UNITS:DetectedItemReportSummary( DetectedItem, AttackGroup, Settings ) self:F( { DetectedItem = DetectedItem } ) - + local DetectedItemID = self:GetDetectedItemID( DetectedItem ) - + if DetectedItem then local ReportSummary = "" local UnitDistanceText = "" local UnitCategoryText = "" - + if DetectedItem.KnowType then local UnitCategoryName = DetectedItem.CategoryName if UnitCategoryName then @@ -2209,7 +2174,7 @@ do -- DETECTION_UNITS else UnitCategoryText = "Unknown" end - + if DetectedItem.KnowDistance then if DetectedItem.IsVisible then UnitDistanceText = " at " .. string.format( "%.2f", DetectedItem.Distance ) .. " km" @@ -2219,33 +2184,32 @@ do -- DETECTION_UNITS UnitDistanceText = " at +/- " .. string.format( "%.0f", DetectedItem.Distance ) .. " km" end end - - --TODO: solve Index reference + + -- TODO: solve Index reference local DetectedItemCoordinate = self:GetDetectedItemCoordinate( DetectedItem ) local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup, Settings ) - + local ThreatLevelA2G = self:GetDetectedItemThreatLevel( DetectedItem ) - + local Report = REPORT:New() - Report:Add(DetectedItemID .. ", " .. DetectedItemCoordText) - Report:Add( string.format( "Threat: [%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10-ThreatLevelA2G ) ) ) - Report:Add( string.format("Type: %s%s", UnitCategoryText, UnitDistanceText ) ) - Report:Add( string.format("Visible: %s", DetectedItem.IsVisible and "yes" or "no" ) ) - Report:Add( string.format("Detected: %s", DetectedItem.IsDetected and "yes" or "no" ) ) - Report:Add( string.format("Distance: %s", DetectedItem.KnowDistance and "yes" or "no" ) ) + Report:Add( DetectedItemID .. ", " .. DetectedItemCoordText ) + Report:Add( string.format( "Threat: [%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10 - ThreatLevelA2G ) ) ) + Report:Add( string.format( "Type: %s%s", UnitCategoryText, UnitDistanceText ) ) + Report:Add( string.format( "Visible: %s", DetectedItem.IsVisible and "yes" or "no" ) ) + Report:Add( string.format( "Detected: %s", DetectedItem.IsDetected and "yes" or "no" ) ) + Report:Add( string.format( "Distance: %s", DetectedItem.KnowDistance and "yes" or "no" ) ) return Report end return nil end - --- Report detailed of a detection result. -- @param #DETECTION_UNITS self -- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for. -- @return #string function DETECTION_UNITS:DetectedReportDetailed( AttackGroup ) self:F() - + local Report = REPORT:New() for DetectedItemIndex, DetectedItem in pairs( self.DetectedItems ) do local DetectedItem = DetectedItem -- #DETECTION_BASE.DetectedItem @@ -2253,9 +2217,9 @@ do -- DETECTION_UNITS Report:SetTitle( "Detected units:" ) Report:Add( ReportSummary:Text() ) end - + local ReportText = Report:Text() - + return ReportText end @@ -2267,31 +2231,31 @@ do -- DETECTION_TYPES -- @extends Functional.Detection#DETECTION_BASE --- Will detect units within the battle zone. - -- It will build a DetectedItems[] list filled with DetectedItems, grouped by the type of units detected. + -- It will build a DetectedItems[] list filled with DetectedItems, grouped by the type of units detected. -- Each DetectedItem will contain a field Set, which contains a @{Core.Set#SET_UNIT} containing ONE @{UNIT} object reference. - -- Beware that when the amount of different types detected is large, the DetectedItems[] list will be large also. - -- + -- Beware that when the amount of different types detected is large, the DetectedItems[] list will be large also. + -- -- @field #DETECTION_TYPES DETECTION_TYPES = { ClassName = "DETECTION_TYPES", DetectionRange = nil, } - + --- DETECTION_TYPES constructor. -- @param Functional.Detection#DETECTION_TYPES self -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Recce role. -- @return Functional.Detection#DETECTION_TYPES self function DETECTION_TYPES:New( DetectionSetGroup ) - + -- Inherits from DETECTION_BASE local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) ) -- #DETECTION_TYPES - + self._SmokeDetectedUnits = false self._FlareDetectedUnits = false self._SmokeDetectedZones = false self._FlareDetectedZones = false self._BoundDetectedZones = false - + return self end @@ -2301,61 +2265,60 @@ do -- DETECTION_TYPES -- @return #string The Changes text 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 + if ChangeUnitType ~= "ID" then + MTUT[#MTUT + 1] = ChangeUnitCount .. " of " .. ChangeUnitType end end - MT[#MT+1] = " New target(s) detected: " .. table.concat( MTUT, ", " ) .. "." + 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 + if ChangeUnitType ~= "ID" then + MTUT[#MTUT + 1] = ChangeUnitCount .. " of " .. ChangeUnitType end end - MT[#MT+1] = " Invisible or destroyed target(s): " .. table.concat( MTUT, ", " ) .. "." + MT[#MT + 1] = " Invisible or destroyed target(s): " .. table.concat( MTUT, ", " ) .. "." end - + end - + return table.concat( MT, "\n" ) - + end - - - --- Create the DetectedItems list from the DetectedObjects table. + + --- Create the DetectedItems list from the DetectedObjects table. -- For each DetectedItem, a one field array is created containing the Unit detected. -- @param #DETECTION_TYPES self -- @return #DETECTION_TYPES self function DETECTION_TYPES:CreateDetectionItems() - + -- Loop the current detected items, and check if each object still exists and is detected. - + for DetectedItemKey, DetectedItem in pairs( self.DetectedItems ) do - + local DetectedItemSet = DetectedItem.Set -- Core.Set#SET_UNIT local DetectedTypeName = DetectedItem.TypeName - + for DetectedUnitName, DetectedUnitData in pairs( DetectedItemSet:GetSet() ) do local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT local DetectedObject = nil if DetectedUnit:IsAlive() then - --self:F(DetectedUnit:GetName()) + -- self:F(DetectedUnit:GetName()) DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) end if DetectedObject then - + -- Yes, the DetectedUnit is still detected or exists. Flag as identified. self:IdentifyDetectedObject( DetectedObject ) else @@ -2371,16 +2334,15 @@ do -- DETECTION_TYPES end end - -- Now we need to loop through the unidentified detected units and add these... These are all new items. 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 ) -- Wrapper.Unit#UNIT - + if DetectedUnit then local DetectedTypeName = DetectedUnit:GetTypeName() local DetectedItem = self:GetDetectedItemByKey( DetectedTypeName ) @@ -2388,21 +2350,19 @@ do -- DETECTION_TYPES DetectedItem = self:AddDetectedItem( "TYPE", DetectedTypeName ) DetectedItem.TypeName = DetectedTypeName end - + DetectedItem.Set:AddUnit( DetectedUnit ) self:AddChangeUnit( DetectedItem, "AU", DetectedTypeName ) end - end + end end - - -- Check if there are any friendlies nearby. for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - + local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set - + -- Set the last known coordinate. local DetectedFirstUnit = DetectedSet:GetFirst() local DetectedUnitCoord = DetectedFirstUnit:GetCoordinate() @@ -2412,8 +2372,6 @@ do -- DETECTION_TYPES self:SetDetectedItemThreatLevel( DetectedItem ) self:NearestRecce( DetectedItem ) end - - end @@ -2425,35 +2383,35 @@ do -- DETECTION_TYPES -- @return Core.Report#REPORT The report of the detection items. function DETECTION_TYPES:DetectedItemReportSummary( DetectedItem, AttackGroup, Settings ) self:F( { DetectedItem = DetectedItem } ) - + local DetectedSet = self:GetDetectedItemSet( DetectedItem ) local DetectedItemID = self:GetDetectedItemID( DetectedItem ) - + self:T( DetectedItem ) if DetectedItem then local ThreatLevelA2G = self:GetDetectedItemThreatLevel( DetectedItem ) local DetectedItemsCount = DetectedSet:Count() local DetectedItemType = DetectedItem.TypeName - + local DetectedItemCoordinate = self:GetDetectedItemCoordinate( DetectedItem ) local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup, Settings ) local Report = REPORT:New() - Report:Add(DetectedItemID .. ", " .. DetectedItemCoordText) - Report:Add( string.format( "Threat: [%s%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10-ThreatLevelA2G ) ) ) - Report:Add( string.format("Type: %2d of %s", DetectedItemsCount, DetectedItemType ) ) + Report:Add( DetectedItemID .. ", " .. DetectedItemCoordText ) + Report:Add( string.format( "Threat: [%s%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10 - ThreatLevelA2G ) ) ) + Report:Add( string.format( "Type: %2d of %s", DetectedItemsCount, DetectedItemType ) ) return Report end end - + --- Report detailed of a detection result. -- @param #DETECTION_TYPES self -- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for. -- @return #string function DETECTION_TYPES:DetectedReportDetailed( AttackGroup ) self:F() - + local Report = REPORT:New() for DetectedItemIndex, DetectedItem in pairs( self.DetectedItems ) do local DetectedItem = DetectedItem -- #DETECTION_BASE.DetectedItem @@ -2461,15 +2419,14 @@ do -- DETECTION_TYPES Report:SetTitle( "Detected types:" ) Report:Add( ReportSummary:Text() ) end - + local ReportText = Report:Text() - + return ReportText end end - do -- DETECTION_AREAS --- @type DETECTION_AREAS @@ -2477,11 +2434,11 @@ do -- DETECTION_AREAS -- @field #DETECTION_BASE.DetectedItems DetectedItems A list of areas containing the set of @{Wrapper.Unit}s, @{Zone}s, the center @{Wrapper.Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange. -- @extends Functional.Detection#DETECTION_BASE - --- Detect units within the battle zone for a list of @{Wrapper.Group}s detecting targets following (a) detection method(s), + --- Detect units within the battle zone for a list of @{Wrapper.Group}s detecting targets following (a) detection method(s), -- and will build a list (table) of @{Core.Set#SET_UNIT}s containing the @{Wrapper.Unit#UNIT}s detected. -- The class is group the detected units within zones given a DetectedZoneRange parameter. -- A set with multiple detected zones will be created as there are groups of units detected. - -- + -- -- ## 4.1) Retrieve the Detected Unit Sets and Detected Zones -- -- The methods to manage the DetectedItems[].Set(s) are implemented in @{Functional.Detection#DECTECTION_BASE} and @@ -2494,44 +2451,43 @@ do -- DETECTION_AREAS -- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZoneByID}() with a given index. -- -- ## 4.4) Flare or Smoke detected units - -- + -- -- Use the methods @{Functional.Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Functional.Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. - -- + -- -- ## 4.5) Flare or Smoke or Bound detected zones - -- + -- -- Use the methods: - -- - -- * @{Functional.Detection#DETECTION_AREAS.FlareDetectedZones}() to flare in a color + -- + -- * @{Functional.Detection#DETECTION_AREAS.FlareDetectedZones}() to flare in a color -- * @{Functional.Detection#DETECTION_AREAS.SmokeDetectedZones}() to smoke in a color -- * @{Functional.Detection#DETECTION_AREAS.SmokeDetectedZones}() to bound with a tire with a white flag - -- + -- -- the detected zones when a new detection has taken place. - -- + -- -- @field #DETECTION_AREAS DETECTION_AREAS = { ClassName = "DETECTION_AREAS", DetectionZoneRange = nil, } - - + --- DETECTION_AREAS constructor. -- @param #DETECTION_AREAS self -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. -- @param DCS#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. -- @return #DETECTION_AREAS function DETECTION_AREAS:New( DetectionSetGroup, DetectionZoneRange ) - + -- Inherits from DETECTION_BASE 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 @@ -2586,26 +2542,26 @@ do -- DETECTION_AREAS -- @return Core.Report#REPORT The report of the detection items. function DETECTION_AREAS:DetectedItemReportMenu( DetectedItem, AttackGroup, Settings ) self:F( { DetectedItem = DetectedItem } ) - + local DetectedItemID = self:GetDetectedItemID( DetectedItem ) - + if DetectedItem then local DetectedSet = self:GetDetectedItemSet( DetectedItem ) local ReportSummaryItem - + local DetectedZone = self:GetDetectedItemZone( DetectedItem ) local DetectedItemCoordinate = DetectedZone:GetCoordinate() local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup, Settings ) local ThreatLevelA2G = self:GetDetectedItemThreatLevel( DetectedItem ) - + local Report = REPORT:New() Report:Add( DetectedItemID ) - Report:Add( string.format( "Threat: [%s%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10-ThreatLevelA2G ) ) ) - + Report:Add( string.format( "Threat: [%s%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10 - ThreatLevelA2G ) ) ) + return Report end - + return nil end @@ -2617,14 +2573,14 @@ do -- DETECTION_AREAS -- @return Core.Report#REPORT The report of the detection items. function DETECTION_AREAS:DetectedItemReportSummary( DetectedItem, AttackGroup, Settings ) self:F( { DetectedItem = DetectedItem } ) - + local DetectedItemID = self:GetDetectedItemID( DetectedItem ) - + if DetectedItem then local DetectedSet = self:GetDetectedItemSet( DetectedItem ) local ReportSummaryItem - - --local DetectedZone = self:GetDetectedItemZone( DetectedItem ) + + -- local DetectedZone = self:GetDetectedItemZone( DetectedItem ) local DetectedItemCoordinate = self:GetDetectedItemCoordinate( DetectedItem ) local DetectedAir = DetectedSet:HasAirUnits() local DetectedAltitude = self:GetDetectedItemCoordinate( DetectedItem ) @@ -2634,21 +2590,20 @@ do -- DETECTION_AREAS else DetectedItemCoordText = DetectedItemCoordinate:ToStringA2G( AttackGroup, Settings ) end - local ThreatLevelA2G = self:GetDetectedItemThreatLevel( DetectedItem ) local DetectedItemsCount = DetectedSet:Count() local DetectedItemsTypes = DetectedSet:GetTypeNames() - + local Report = REPORT:New() - Report:Add(DetectedItemID .. ", " .. DetectedItemCoordText) - Report:Add( string.format( "Threat: [%s%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10-ThreatLevelA2G ) ) ) - Report:Add( string.format("Type: %2d of %s", DetectedItemsCount, DetectedItemsTypes ) ) - --Report:Add( string.format("Detected: %s", DetectedItem.IsDetected and "yes" or "no" ) ) - + Report:Add( DetectedItemID .. ", " .. DetectedItemCoordText ) + Report:Add( string.format( "Threat: [%s%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10 - ThreatLevelA2G ) ) ) + Report:Add( string.format( "Type: %2d of %s", DetectedItemsCount, DetectedItemsTypes ) ) + -- Report:Add( string.format("Detected: %s", DetectedItem.IsDetected and "yes" or "no" ) ) + return Report end - + return nil end @@ -2656,9 +2611,9 @@ do -- DETECTION_AREAS -- @param #DETECTION_AREAS self -- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for. -- @return #string - function DETECTION_AREAS:DetectedReportDetailed( AttackGroup ) --R2.1 Fixed missing report + function DETECTION_AREAS:DetectedReportDetailed( AttackGroup ) -- R2.1 Fixed missing report self:F() - + local Report = REPORT:New() for DetectedItemIndex, DetectedItem in pairs( self.DetectedItems ) do local DetectedItem = DetectedItem -- #DETECTION_BASE.DetectedItem @@ -2666,13 +2621,12 @@ do -- DETECTION_AREAS Report:SetTitle( "Detected areas:" ) Report:Add( ReportSummary:Text() ) end - + local ReportText = Report:Text() - + return ReportText end - --- Calculate the optimal intercept point of the DetectedItem. -- @param #DETECTION_AREAS self -- @param #DETECTION_BASE.DetectedItem DetectedItem @@ -2685,56 +2639,54 @@ do -- DETECTION_AREAS if self.Intercept then local DetectedSet = DetectedItem.Set -- todo: speed - + local TranslateDistance = DetectedSpeed * self.InterceptDelay - + local InterceptCoord = DetectedCoord:Translate( TranslateDistance, DetectedHeading ) - + DetectedItem.InterceptCoord = InterceptCoord else DetectedItem.InterceptCoord = DetectedCoord end - + end - - --- Smoke the detected units -- @param #DETECTION_AREAS self -- @return #DETECTION_AREAS self function DETECTION_AREAS:SmokeDetectedUnits() self:F2() - + self._SmokeDetectedUnits = true return self end - + --- Flare the detected units -- @param #DETECTION_AREAS self -- @return #DETECTION_AREAS self function DETECTION_AREAS:FlareDetectedUnits() self:F2() - + self._FlareDetectedUnits = true return self end - + --- Smoke the detected zones -- @param #DETECTION_AREAS self -- @return #DETECTION_AREAS self function DETECTION_AREAS:SmokeDetectedZones() self:F2() - + self._SmokeDetectedZones = true return self end - + --- Flare the detected zones -- @param #DETECTION_AREAS self -- @return #DETECTION_AREAS self function DETECTION_AREAS:FlareDetectedZones() self:F2() - + self._FlareDetectedZones = true return self end @@ -2744,127 +2696,123 @@ do -- DETECTION_AREAS -- @return #DETECTION_AREAS self function DETECTION_AREAS:BoundDetectedZones() self:F2() - + self._BoundDetectedZones = true return self end - + --- Make text documenting the changes of the detected zone. -- @param #DETECTION_AREAS self -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return #string The Changes text function DETECTION_AREAS:GetChangeText( DetectedItem ) self:F( DetectedItem ) - + local MT = {} - + for ChangeCode, ChangeData in pairs( DetectedItem.Changes ) do - + if ChangeCode == "AA" then - MT[#MT+1] = "Detected new area " .. ChangeData.ID .. ". The center target is a " .. ChangeData.ItemUnitType .. "." + MT[#MT + 1] = "Detected new area " .. ChangeData.ID .. ". The center target is a " .. ChangeData.ItemUnitType .. "." end - + if ChangeCode == "RAU" then - MT[#MT+1] = "Changed area " .. ChangeData.ID .. ". Removed the center target." + MT[#MT + 1] = "Changed area " .. ChangeData.ID .. ". Removed the center target." end - + if ChangeCode == "AAU" then - MT[#MT+1] = "Changed area " .. ChangeData.ID .. ". The new center target is a " .. ChangeData.ItemUnitType .. "." + MT[#MT + 1] = "Changed area " .. ChangeData.ID .. ". The new center target is a " .. ChangeData.ItemUnitType .. "." end - + if ChangeCode == "RA" then - MT[#MT+1] = "Removed old area " .. ChangeData.ID .. ". No more targets in this area." + MT[#MT + 1] = "Removed old area " .. ChangeData.ID .. ". No more targets in this area." end - + if ChangeCode == "AU" then local MTUT = {} for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType + if ChangeUnitType ~= "ID" then + MTUT[#MTUT + 1] = ChangeUnitCount .. " of " .. ChangeUnitType end end - MT[#MT+1] = "Detected for area " .. ChangeData.ID .. " new target(s) " .. table.concat( MTUT, ", " ) .. "." + MT[#MT + 1] = "Detected for area " .. ChangeData.ID .. " new target(s) " .. 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 + if ChangeUnitType ~= "ID" then + MTUT[#MTUT + 1] = ChangeUnitCount .. " of " .. ChangeUnitType end end - MT[#MT+1] = "Removed for area " .. ChangeData.ID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "." + MT[#MT + 1] = "Removed for area " .. ChangeData.ID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "." end - + end - + return table.concat( MT, "\n" ) - + end - - - --- Make a DetectionSet table. This function will be overridden in the derived clsses. + + --- Make a DetectionSet table. This function will be overridden in the derived classes. -- @param #DETECTION_AREAS self -- @return #DETECTION_AREAS self function DETECTION_AREAS:CreateDetectionItems() - - + self:F( "Checking Detected Items for new Detected Units ..." ) - --self:F( { DetectedObjects = self.DetectedObjects } ) - + -- self:F( { DetectedObjects = self.DetectedObjects } ) + -- First go through all detected sets, and check if there are new detected units, match all existing detected units and identify undetected units. -- Regroup when needed, split groups when needed. for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - + local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem - + if DetectedItem then - + self:T2( { "Detected Item ID: ", DetectedItemID } ) - + local DetectedSet = DetectedItem.Set - + local AreaExists = false -- This flag will determine of the detected area is still existing. - + -- First test if the center unit is detected in the detection area. self:T3( { "Zone Center Unit:", DetectedItem.Zone.ZoneUNIT.UnitName } ) local DetectedZoneObject = self:GetDetectedObject( DetectedItem.Zone.ZoneUNIT.UnitName ) self:T3( { "Detected Zone Object:", DetectedItem.Zone:GetName(), DetectedZoneObject } ) - + if DetectedZoneObject then - - --self:IdentifyDetectedObject( DetectedZoneObject ) + + -- self:IdentifyDetectedObject( DetectedZoneObject ) AreaExists = true - - - + else -- The center object of the detected area has not been detected. Find an other unit of the set to become the center of the area. -- First remove the center unit from the set. DetectedSet:RemoveUnitsByName( DetectedItem.Zone.ZoneUNIT.UnitName ) - + self:AddChangeItem( DetectedItem, 'RAU', self:GetDetectedUnitTypeName( DetectedItem.Zone.ZoneUNIT ) ) - + -- Then search for a new center area unit within the set. Note that the new area unit candidate must be within the area range. for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - + local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT - local DetectedObject = self:GetDetectedObject( DetectedUnit.UnitName ) + local DetectedObject = self:GetDetectedObject( DetectedUnit.UnitName ) local DetectedUnitTypeName = self:GetDetectedUnitTypeName( DetectedUnit ) - + -- The DetectedObject can be nil when the DetectedUnit is not alive anymore or it is not in the DetectedObjects map. -- If the DetectedUnit was already identified, DetectedObject will be nil. if DetectedObject then self:IdentifyDetectedObject( DetectedObject ) AreaExists = true - - --DetectedItem.Zone:BoundZone( 12, self.CountryID, true) - + + -- DetectedItem.Zone:BoundZone( 12, self.CountryID, true) + -- Assign the Unit as the new center unit of the detected area. DetectedItem.Zone = ZONE_UNIT:New( DetectedUnit:GetName(), DetectedUnit, self.DetectionZoneRange ) - + self:AddChangeItem( DetectedItem, "AAU", DetectedUnitTypeName ) - + -- We don't need to add the DetectedObject to the area set, because it is already there ... break else @@ -2873,30 +2821,30 @@ do -- DETECTION_AREAS end end end - + -- Now we've determined the center unit of the area, now we can iterate the units in the detected area. -- Note that the position of the area may have moved due to the center unit repositioning. -- If no center unit was identified, then the detected area does not exist anymore and should be deleted, as there are no valid units that can be the center unit. if AreaExists then - + -- ok, we found the center unit of the area, now iterate through the detected area set and see which units are still within the center unit zone ... -- Those units within the zone are flagged as Identified. -- If a unit was not found in the set, remove it from the set. This may be added later to other existing or new sets. for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - + local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT local DetectedUnitTypeName = self:GetDetectedUnitTypeName( DetectedUnit ) local DetectedObject = nil if DetectedUnit:IsAlive() then - --self:F(DetectedUnit:GetName()) + -- self:F(DetectedUnit:GetName()) DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) end if DetectedObject then -- Check if the DetectedUnit is within the DetectedItem.Zone if DetectedUnit:IsInZone( DetectedItem.Zone ) then - + -- Yes, the DetectedUnit is within the DetectedItem.Zone, no changes, DetectedUnit can be kept within the Set. self:IdentifyDetectedObject( DetectedObject ) DetectedSet:AddUnit( DetectedUnit ) @@ -2906,47 +2854,45 @@ do -- DETECTION_AREAS DetectedSet:Remove( DetectedUnitName ) self:AddChangeUnit( DetectedItem, "RU", DetectedUnitTypeName ) end - + else -- There was no DetectedObject, remove DetectedUnit from the Set. self:AddChangeUnit( DetectedItem, "RU", "destroyed target" ) DetectedSet:Remove( DetectedUnitName ) - + -- The DetectedObject has been identified, because it does not exist ... -- self:IdentifyDetectedObject( DetectedObject ) end end else - --DetectedItem.Zone:BoundZone( 12, self.CountryID, true) + -- DetectedItem.Zone:BoundZone( 12, self.CountryID, true) self:RemoveDetectedItem( DetectedItemID ) self:AddChangeItem( DetectedItem, "RA" ) end end end - - - + -- We iterated through the existing detection areas and: -- - We checked which units are still detected in each detection area. Those units were flagged as Identified. - -- - We recentered the detection area to new center units where it was needed. + -- - We re-centered the detection area to new center units where it was needed. -- -- Now we need to loop through the unidentified detected units and see where they belong: -- - They can be added to a new detection area and become the new center unit. -- - They can be added to a new detection area. for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do - + local DetectedObject = self:GetDetectedObject( DetectedUnitName ) - + if DetectedObject then - + -- We found an unidentified unit outside of any existing detection area. local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Wrapper.Unit#UNIT local DetectedUnitTypeName = self:GetDetectedUnitTypeName( DetectedUnit ) - + local AddedToDetectionArea = false - + for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - + local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem if DetectedItem then local DetectedSet = DetectedItem.Set @@ -2958,74 +2904,71 @@ do -- DETECTION_AREAS end end end - + if AddedToDetectionArea == false then - + -- New detection area - local DetectedItem = self:AddDetectedItemZone( "AREA", nil, + local DetectedItem = self:AddDetectedItemZone( "AREA", nil, SET_UNIT:New():FilterDeads():FilterCrashes(), ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) ) - --self:F( DetectedItem.Zone.ZoneUNIT.UnitName ) + -- self:F( DetectedItem.Zone.ZoneUNIT.UnitName ) DetectedItem.Set:AddUnit( DetectedUnit ) self:AddChangeItem( DetectedItem, "AA", DetectedUnitTypeName ) - end + end end end - + -- Now all the tests should have been build, now make some smoke and flares... -- We also report here the friendlies within the detected areas. - + for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - + local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set local DetectedFirstUnit = DetectedSet:GetFirst() local DetectedZone = DetectedItem.Zone - + -- Set the last known coordinate to the detection item. local DetectedZoneCoord = DetectedZone:GetCoordinate() self:SetDetectedItemCoordinate( DetectedItem, DetectedZoneCoord, DetectedFirstUnit ) - + self:CalculateIntercept( DetectedItem ) - + -- We search for friendlies nearby. -- If there weren't any friendlies nearby, and now there are friendlies nearby, we flag the area as "changed". -- If there were friendlies nearby, and now there aren't any friendlies nearby, we flag the area as "changed". -- This is for the A2G dispatcher to detect if there is a change in the tactical situation. local OldFriendliesNearbyGround = self:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT ) self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSet } ) -- Fill the Friendlies table - local NewFriendliesNearbyGround = self:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT ) + local NewFriendliesNearbyGround = self:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT ) if OldFriendliesNearbyGround ~= NewFriendliesNearbyGround then DetectedItem.Changed = true end - self:SetDetectedItemThreatLevel( DetectedItem ) -- Calculate A2G threat level + self:SetDetectedItemThreatLevel( DetectedItem ) -- Calculate A2G threat level self:NearestRecce( DetectedItem ) - if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then DetectedZone.ZoneUNIT:SmokeRed() end - - --DetectedSet:Flush( self ) - - DetectedSet:ForEachUnit( - --- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit ) - if DetectedUnit:IsAlive() then - --self:T( "Detected Set #" .. DetectedItem.ID .. ":" .. DetectedUnit:GetName() ) - if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then - DetectedUnit:FlareGreen() - end - if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then - DetectedUnit:SmokeGreen() - end + + -- DetectedSet:Flush( self ) + + DetectedSet:ForEachUnit( --- @param Wrapper.Unit#UNIT DetectedUnit + function( DetectedUnit ) + if DetectedUnit:IsAlive() then + -- self:T( "Detected Set #" .. DetectedItem.ID .. ":" .. DetectedUnit:GetName() ) + if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then + DetectedUnit:FlareGreen() + end + if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then + DetectedUnit:SmokeGreen() end end - ) + end ) if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then - DetectedZone:FlareZone( SMOKECOLOR.White, 30, math.random( 0,90 ) ) + DetectedZone:FlareZone( SMOKECOLOR.White, 30, math.random( 0, 90 ) ) end if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then DetectedZone:SmokeZone( SMOKECOLOR.White, 30 ) @@ -3036,9 +2979,8 @@ do -- DETECTION_AREAS DetectedZone:BoundZone( 12, self.CountryID ) end end - - end - -end - + end + +end + diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 356a4ab49..2dde61915 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -123,7 +123,7 @@ -- Set up your SHORAD systems. They need to be **close** to (i.e. around) the SAM sites to be effective. Use **one** group per SAM location. SA-15 TOR systems offer a good missile defense. -- -- [optional] Set up your HQ. Can be any group, e.g. a command vehicle. --- +-- -- # 1. Basic tactical considerations when setting up your SAM sites -- -- ## 1.1 Radar systems and AWACS diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 89330344e..b33285764 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -69,7 +69,7 @@ -- @field #string category Category of aircarft: "plane" or "heli". -- @field #number groupsize Number of aircraft in group. -- @field #string friendly Possible departure/destination airport: all=blue+red+neutral, same=spawn+neutral, spawnonly=spawn, blue=blue+neutral, blueonly=blue, red=red+neutral, redonly=red. --- @field #table ctable Table with the valid coalitons from choice self.friendly. +-- @field #table ctable Table with the valid coalitions from choice self.friendly. -- @field #table aircraft Table which holds the basic aircraft properties (speed, range, ...). -- @field #number Vcruisemax Max cruise speed in m/s (250 m/s = 900 km/h = 486 kt) set by user. -- @field #number Vclimb Default climb rate in ft/min. @@ -348,7 +348,7 @@ RAT={ category = nil, -- Category of aircarft: "plane" or "heli". groupsize=nil, -- Number of aircraft in the group. friendly = "same", -- Possible departure/destination airport: same=spawn+neutral, spawnonly=spawn, blue=blue+neutral, blueonly=blue, red=red+neutral, redonly=red, neutral. - ctable = {}, -- Table with the valid coalitons from choice self.friendly. + ctable = {}, -- Table with the valid coalitions from choice self.friendly. aircraft = {}, -- Table which holds the basic aircraft properties (speed, range, ...). Vcruisemax=nil, -- Max cruise speed in set by user. Vclimb=1500, -- Default climb rate in ft/min. @@ -657,7 +657,7 @@ end -- @param #RAT self -- @param #number naircraft (Optional) Number of aircraft to spawn. Default is one aircraft. -- @return #boolean True if spawning was successful or nil if nothing was spawned. --- @usage yak:Spawn(5) will spawn five aircraft. By default aircraft will spawn at neutral and red airports if the template group is part of the red coaliton. +-- @usage yak:Spawn(5) will spawn five aircraft. By default aircraft will spawn at neutral and red airports if the template group is part of the red coalition. function RAT:Spawn(naircraft) -- Make sure that this function is only been called once per RAT object. @@ -1289,7 +1289,7 @@ end --- Include all airports which lie in a zone as possible destinations. -- @param #RAT self --- @param Core.Zone#ZONE zone Zone in which the departure airports lie. Has to be a MOOSE zone. +-- @param Core.Zone#ZONE zone Zone in which the destination airports lie. Has to be a MOOSE zone. -- @return #RAT RAT self object. function RAT:SetDestinationsFromZone(zone) self:F2(zone) @@ -1305,7 +1305,7 @@ end --- Include all airports which lie in a zone as possible destinations. -- @param #RAT self --- @param Core.Zone#ZONE zone Zone in which the destination airports lie. Has to be a MOOSE zone. +-- @param Core.Zone#ZONE zone Zone in which the departure airports lie. Has to be a MOOSE zone. -- @return #RAT RAT self object. function RAT:SetDeparturesFromZone(zone) self:F2(zone) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index f2f4b6613..35cbdddd6 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -449,6 +449,13 @@ RANGE.TargetType = { -- @field #string rangename Name of the range. -- @field #boolean invalid Invalid pass. +--- Strafe result. +-- @type RANGE.StrafeResult +-- @field #string player Player name. +-- @field #string airframe Aircraft type of player. +-- @field #number time Time via timer.getAbsTime() in seconds of impact. +-- @field #string date OS date. + --- Sound file data. -- @type RANGE.Soundfile -- @field #string filename Name of the file diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index 50903687d..6b6e05619 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -1,9 +1,9 @@ --- **Functional** - Administer the scoring of player achievements, and create a CSV file logging the scoring events for use at team or squadron websites. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Set the scoring scales based on threat level. -- * Positive scores and negative scores. -- * A contribution model to score achievements. @@ -14,169 +14,167 @@ -- * Score the hits and destroys of scenery. -- * Log scores into a CSV file. -- * Connect to a remote server using JSON and IP. --- +-- -- === --- +-- -- ## Missions: --- +-- -- [SCO - Scoring](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCO%20-%20Scoring) --- +-- -- === --- --- Administers the scoring of player achievements, +-- +-- Administers the scoring of player achievements, -- and creates a CSV file logging the scoring events and results for use at team or squadron websites. --- --- SCORING automatically calculates the threat level of the objects hit and destroyed by players, +-- +-- SCORING automatically calculates the threat level of the objects hit and destroyed by players, -- which can be @{Wrapper.Unit}, @{Static) and @{Scenery} objects. --- --- Positive score points are granted when enemy or neutral targets are destroyed. --- Negative score points or penalties are given when a friendly target is hit or destroyed. +-- +-- Positive score points are granted when enemy or neutral targets are destroyed. +-- Negative score points or penalties are given when a friendly target is hit or destroyed. -- This brings a lot of dynamism in the scoring, where players need to take care to inflict damage on the right target. -- By default, penalties weight heavier in the scoring, to ensure that players don't commit fratricide. -- The total score of the player is calculated by **adding the scores minus the penalties**. --- +-- -- ![Banner Image](..\Presentations\SCORING\Dia4.JPG) --- +-- -- The score value is calculated based on the **threat level of the player** and the **threat level of the target**. --- A calculated score takes the threat level of the target divided by a balanced threat level of the player unit. --- As such, if the threat level of the target is high, and the player threat level is low, a higher score will be given than +-- A calculated score takes the threat level of the target divided by a balanced threat level of the player unit. +-- As such, if the threat level of the target is high, and the player threat level is low, a higher score will be given than -- if the threat level of the player would be high too. --- +-- -- ![Banner Image](..\Presentations\SCORING\Dia5.JPG) --- +-- -- When multiple players hit the same target, and finally succeed in destroying the target, then each player who contributed to the target -- destruction, will receive a score. This is important for targets that require significant damage before it can be destroyed, like -- ships or heavy planes. --- +-- -- ![Banner Image](..\Presentations\SCORING\Dia13.JPG) --- +-- -- Optionally, the score values can be **scaled** by a **scale**. Specific scales can be set for positive cores or negative penalties. -- The default range of the scores granted is a value between 0 and 10. The default range of penalties given is a value between 0 and 30. --- +-- -- ![Banner Image](..\Presentations\SCORING\Dia7.JPG) --- +-- -- **Additional scores** can be granted to **specific objects**, when the player(s) destroy these objects. --- +-- -- ![Banner Image](..\Presentations\SCORING\Dia9.JPG) --- +-- -- Various @{Zone}s can be defined for which scores are also granted when objects in that @{Zone} are destroyed. -- This is **specifically useful** to designate **scenery targets on the map** that will generate points when destroyed. --- --- With a small change in MissionScripting.lua, the scoring results can also be logged in a **CSV file**. +-- +-- With a small change in MissionScripting.lua, the scoring results can also be logged in a **CSV file**. -- These CSV files can be used to: --- +-- -- * Upload scoring to a database or a BI tool to publish the scoring results to the player community. -- * Upload scoring in an (online) Excel like tool, using pivot tables and pivot charts to show mission results. --- * Share scoring amoung players after the mission to discuss mission results. --- +-- * Share scoring among players after the mission to discuss mission results. +-- -- Scores can be **reported**. **Menu options** are automatically added to **each player group** when a player joins a client slot or a CA unit. --- Use the radio menu F10 to consult the scores while running the mission. +-- Use the radio menu F10 to consult the scores while running the mission. -- Scores can be reported for your user, or an overall score can be reported of all players currently active in the mission. --- +-- -- === --- +-- -- ### Authors: **FlightControl** --- +-- -- ### Contributions: --- +-- -- * **Wingthor (TAW)**: Testing & Advice. -- * **Dutch-Baron (TAW)**: Testing & Advice. -- * **[Whisper](http://forums.eagle.ru/member.php?u=3829): Testing and Advice. --- --- === --- +-- +-- === +-- -- @module Functional.Scoring -- @image Scoring.JPG - --- @type SCORING -- @field Players A collection of the current players that have joined the game. -- @extends Core.Base#BASE --- SCORING class --- +-- -- # Constructor: --- +-- -- local Scoring = SCORING:New( "Scoring File" ) --- --- +-- -- # Set the destroy score or penalty scale: --- +-- -- Score scales can be set for scores granted when enemies or friendlies are destroyed. --- Use the method @{#SCORING.SetScaleDestroyScore}() to set the scale of enemy destroys (positive destroys). +-- Use the method @{#SCORING.SetScaleDestroyScore}() to set the scale of enemy destroys (positive destroys). -- Use the method @{#SCORING.SetScaleDestroyPenalty}() to set the scale of friendly destroys (negative destroys). --- +-- -- local Scoring = SCORING:New( "Scoring File" ) -- Scoring:SetScaleDestroyScore( 10 ) -- Scoring:SetScaleDestroyPenalty( 40 ) --- +-- -- The above sets the scale for valid scores to 10. So scores will be given in a scale from 0 to 10. -- The penalties will be given in a scale from 0 to 40. --- +-- -- # Define special targets that will give extra scores: --- +-- -- Special targets can be set that will give extra scores to the players when these are destroyed. --- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Wrapper.Unit}s. --- Use the methods @{#SCORING.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Static}s. --- Use the method @{#SCORING.SetGroupGroup}() to specify a special additional score for a specific @{Wrapper.Group}s. --- +-- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Wrapper.Unit}s. +-- Use the methods @{#SCORING.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Static}s. +-- Use the method @{#SCORING.SetGroupGroup}() to specify a special additional score for a specific @{Wrapper.Group}s. +-- -- local Scoring = SCORING:New( "Scoring File" ) -- Scoring:AddUnitScore( UNIT:FindByName( "Unit #001" ), 200 ) -- Scoring:AddStaticScore( STATIC:FindByName( "Static #1" ), 100 ) --- +-- -- The above grants an additional score of 200 points for Unit #001 and an additional 100 points of Static #1 if these are destroyed. -- Note that later in the mission, one can remove these scores set, for example, when the a goal achievement time limit is over. -- For example, this can be done as follows: --- +-- -- Scoring:RemoveUnitScore( UNIT:FindByName( "Unit #001" ) ) --- +-- -- # Define destruction zones that will give extra scores: --- +-- -- Define zones of destruction. Any object destroyed within the zone of the given category will give extra points. --- Use the method @{#SCORING.AddZoneScore}() to add a @{Zone} for additional scoring. --- Use the method @{#SCORING.RemoveZoneScore}() to remove a @{Zone} for additional scoring. --- There are interesting variations that can be achieved with this functionality. For example, if the @{Zone} is a @{Core.Zone#ZONE_UNIT}, +-- Use the method @{#SCORING.AddZoneScore}() to add a @{Zone} for additional scoring. +-- Use the method @{#SCORING.RemoveZoneScore}() to remove a @{Zone} for additional scoring. +-- There are interesting variations that can be achieved with this functionality. For example, if the @{Zone} is a @{Core.Zone#ZONE_UNIT}, -- then the zone is a moving zone, and anything destroyed within that @{Zone} will generate points. --- The other implementation could be to designate a scenery target (a building) in the mission editor surrounded by a @{Zone}, +-- The other implementation could be to designate a scenery target (a building) in the mission editor surrounded by a @{Zone}, -- just large enough around that building. --- +-- -- # Add extra Goal scores upon an event or a condition: --- +-- -- A mission has goals and achievements. The scoring system provides an API to set additional scores when a goal or achievement event happens. -- Use the method @{#SCORING.AddGoalScore}() to add a score for a Player at any time in your mission. --- +-- -- # (Decommissioned) Configure fratricide level. --- --- **This functionality is decomissioned until the DCS bug concerning Unit:destroy() not being functional in multi player for player units has been fixed by ED**. --- +-- +-- **This functionality is decommissioned until the DCS bug concerning Unit:destroy() not being functional in multi player for player units has been fixed by ED**. +-- -- When a player commits too much damage to friendlies, his penalty score will reach a certain level. --- Use the method @{#SCORING.SetFratricide}() to define the level when a player gets kicked. --- By default, the fratricide level is the default penalty mutiplier * 2 for the penalty score. --- +-- Use the method @{#SCORING.SetFratricide}() to define the level when a player gets kicked. +-- By default, the fratricide level is the default penalty multiplier * 2 for the penalty score. +-- -- # Penalty score when a player changes the coalition. --- +-- -- When a player changes the coalition, he can receive a penalty score. -- Use the method @{#SCORING.SetCoalitionChangePenalty}() to define the penalty when a player changes coalition. --- By default, the penalty for changing coalition is the default penalty scale. --- +-- By default, the penalty for changing coalition is the default penalty scale. +-- -- # Define output CSV files. --- +-- -- The CSV file is given the name of the string given in the @{#SCORING.New}{} constructor, followed by the .csv extension. -- The file is incrementally saved in the **\\Saved Games\\DCS\\Logs** folder, and has a time stamp indicating each mission run. -- See the following example: --- +-- -- local ScoringFirstMission = SCORING:New( "FirstMission" ) -- local ScoringSecondMission = SCORING:New( "SecondMission" ) --- +-- -- The above documents that 2 Scoring objects are created, ScoringFirstMission and ScoringSecondMission. --- --- ### **IMPORTANT!!!* +-- +-- ### **IMPORTANT!!!* -- In order to allow DCS world to write CSV files, you need to adapt a configuration file in your DCS world installation **on the server**. -- For this, browse to the **missionscripting.lua** file in your DCS world installation folder. -- For me, this installation folder is in _D:\\Program Files\\Eagle Dynamics\\DCS World\Scripts_. --- +-- -- Edit a few code lines in the MissionScripting.lua file. Comment out the lines **os**, **io** and **lfs**: --- +-- -- do -- --sanitizeModule('os') -- --sanitizeModule('io') @@ -184,44 +182,44 @@ -- require = nil -- loadlib = nil -- end --- --- When these lines are not sanitized, functions become available to check the time, and to write files to your system at the above specified location. +-- +-- When these lines are not sanitized, functions become available to check the time, and to write files to your system at the above specified location. -- Note that the MissionScripting.lua file provides a warning. So please beware of this warning as outlined by Eagle Dynamics! --- +-- -- --Sanitize Mission Scripting environment --- --This makes unavailable some unsecure functions. --- --Mission downloaded from server to client may contain potentialy harmful lua code that may use these functions. --- --You can remove the code below and make availble these functions at your own risk. --- +-- --This makes unavailable some unsecure functions. +-- --Mission downloaded from server to client may contain potentially harmful lua code that may use these functions. +-- --You can remove the code below and make available these functions at your own risk. +-- -- The MOOSE designer cannot take any responsibility of any damage inflicted as a result of the de-sanitization. -- That being said, I hope that the SCORING class provides you with a great add-on to score your squad mates achievements. --- +-- -- # Configure messages. --- +-- -- When players hit or destroy targets, messages are sent. -- Various methods exist to configure: --- +-- -- * Which messages are sent upon the event. -- * Which audience receives the message. --- +-- -- ## Configure the messages sent upon the event. --- +-- -- Use the following methods to configure when to send messages. By default, all messages are sent. --- +-- -- * @{#SCORING.SetMessagesHit}(): Configure to send messages after a target has been hit. -- * @{#SCORING.SetMessagesDestroy}(): Configure to send messages after a target has been destroyed. -- * @{#SCORING.SetMessagesAddon}(): Configure to send messages for additional score, after a target has been destroyed. -- * @{#SCORING.SetMessagesZone}(): Configure to send messages for additional score, after a target has been destroyed within a given zone. --- +-- -- ## Configure the audience of the messages. --- +-- -- Use the following methods to configure the audience of the messages. By default, the messages are sent to all players in the mission. --- +-- -- * @{#SCORING.SetMessagesToAll}(): Configure to send messages to all players. -- * @{#SCORING.SetMessagesToCoalition}(): Configure to send messages to only those players within the same coalition as the player. -- -- === --- +-- -- @field #SCORING SCORING = { ClassName = "SCORING", @@ -229,45 +227,42 @@ SCORING = { Players = {}, } -local _SCORINGCoalition = - { - [1] = "Red", - [2] = "Blue", - } +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", - } +local _SCORINGCategory = { + [Unit.Category.AIRPLANE] = "Plane", + [Unit.Category.HELICOPTER] = "Helicopter", + [Unit.Category.GROUND_UNIT] = "Vehicle", + [Unit.Category.SHIP] = "Ship", + [Unit.Category.STRUCTURE] = "Structure", +} --- Creates a new SCORING object to administer the scoring achieved by players. -- @param #SCORING self -- @param #string GameName The name of the game. This name is also logged in the CSV score file. -- @return #SCORING self -- @usage --- --- -- Define a new scoring object for the mission Gori Valley. --- ScoringObject = SCORING:New( "Gori Valley" ) --- +-- +-- -- Define a new scoring object for the mission Gori Valley. +-- ScoringObject = SCORING:New( "Gori Valley" ) +-- function SCORING:New( GameName ) -- Inherits from BASE local self = BASE:Inherit( self, BASE:New() ) -- #SCORING - - if GameName then + + if GameName then self.GameName = GameName else error( "A game name must be given to register the scoring results" ) end - - + -- Additional Object scores self.ScoringObjects = {} - + -- Additional Zone scores. self.ScoringZones = {} @@ -277,7 +272,7 @@ function SCORING:New( GameName ) self:SetMessagesDestroy( true ) self:SetMessagesScore( true ) self:SetMessagesZone( true ) - + -- Scales self:SetScaleDestroyScore( 10 ) self:SetScaleDestroyPenalty( 30 ) @@ -291,33 +286,30 @@ function SCORING:New( GameName ) self.penaltyoncoalitionchange = true self:SetDisplayMessagePrefix() - + -- Event handlers self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Hit, self._EventOnHit ) self:HandleEvent( EVENTS.Birth ) - --self:HandleEvent( EVENTS.PlayerEnterUnit ) + -- self:HandleEvent( EVENTS.PlayerEnterUnit ) self:HandleEvent( EVENTS.PlayerLeaveUnit ) - - -- During mission startup, especially for single player, + + -- During mission startup, especially for single player, -- iterate the database for the player that has joined, and add him to the scoring, and set the menu. -- But this can only be started one second after the mission has started, so i need to schedule this ... - self.ScoringPlayerScan = BASE:ScheduleOnce( 1, - function() - for PlayerName, PlayerUnit in pairs( _DATABASE:GetPlayerUnits() ) do - self:_AddPlayerFromUnit( PlayerUnit ) - self:SetScoringMenu( PlayerUnit:GetGroup() ) - end + self.ScoringPlayerScan = BASE:ScheduleOnce( 1, function() + for PlayerName, PlayerUnit in pairs( _DATABASE:GetPlayerUnits() ) do + self:_AddPlayerFromUnit( PlayerUnit ) + self:SetScoringMenu( PlayerUnit:GetGroup() ) end - ) - + end ) -- Create the CSV file. self:OpenCSV( GameName ) return self - + end --- Set a prefix string that will be displayed at each scoring message sent. @@ -329,7 +321,6 @@ function SCORING:SetDisplayMessagePrefix( DisplayMessagePrefix ) return self end - --- Set the scale for scoring valid destroys (enemy destroys). -- A default calculated score is a value between 1 and 10. -- The scale magnifies the scores given to the players. @@ -349,12 +340,12 @@ end function SCORING:SetScaleDestroyPenalty( Scale ) self.ScaleDestroyPenalty = Scale - + return self end --- Add a @{Wrapper.Unit} for additional scoring when the @{Wrapper.Unit} is destroyed. --- Note that if there was already a @{Wrapper.Unit} declared within the scoring with the same name, +-- Note that if there was already a @{Wrapper.Unit} declared within the scoring with the same name, -- then the old @{Wrapper.Unit} will be replaced with the new @{Wrapper.Unit}. -- @param #SCORING self -- @param Wrapper.Unit#UNIT ScoreUnit The @{Wrapper.Unit} for which the Score needs to be given. @@ -365,7 +356,7 @@ function SCORING:AddUnitScore( ScoreUnit, Score ) local UnitName = ScoreUnit:GetName() self.ScoringObjects[UnitName] = Score - + return self end @@ -378,12 +369,12 @@ function SCORING:RemoveUnitScore( ScoreUnit ) local UnitName = ScoreUnit:GetName() self.ScoringObjects[UnitName] = nil - + return self end --- Add a @{Static} for additional scoring when the @{Static} is destroyed. --- Note that if there was already a @{Static} declared within the scoring with the same name, +-- Note that if there was already a @{Static} declared within the scoring with the same name, -- then the old @{Static} will be replaced with the new @{Static}. -- @param #SCORING self -- @param Wrapper.Static#UNIT ScoreStatic The @{Static} for which the Score needs to be given. @@ -394,7 +385,7 @@ function SCORING:AddStaticScore( ScoreStatic, Score ) local StaticName = ScoreStatic:GetName() self.ScoringObjects[StaticName] = Score - + return self end @@ -407,11 +398,10 @@ function SCORING:RemoveStaticScore( ScoreStatic ) local StaticName = ScoreStatic:GetName() self.ScoringObjects[StaticName] = nil - + return self end - --- Specify a special additional score for a @{Wrapper.Group}. -- @param #SCORING self -- @param Wrapper.Group#GROUP ScoreGroup The @{Wrapper.Group} for which each @{Wrapper.Unit} a Score is given. @@ -425,7 +415,7 @@ function SCORING:AddScoreGroup( ScoreGroup, Score ) local UnitName = ScoreUnit:GetName() self.ScoringObjects[UnitName] = Score end - + return self end @@ -433,7 +423,7 @@ end -- Note that if a @{Zone} with the same name is already within the scoring added, the @{Zone} (type) and Score will be replaced! -- This allows for a dynamic destruction zone evolution within your mission. -- @param #SCORING self --- @param Core.Zone#ZONE_BASE ScoreZone The @{Zone} which defines the destruction score perimeters. +-- @param Core.Zone#ZONE_BASE ScoreZone The @{Zone} which defines the destruction score perimeters. -- Note that a zone can be a polygon or a moving zone. -- @param #number Score The Score value. -- @return #SCORING @@ -444,7 +434,7 @@ function SCORING:AddZoneScore( ScoreZone, Score ) self.ScoringZones[ZoneName] = {} self.ScoringZones[ZoneName].ScoreZone = ScoreZone self.ScoringZones[ZoneName].Score = Score - + return self end @@ -452,7 +442,7 @@ end -- The scoring will search if any @{Zone} is added with the given name, and will remove that zone from the scoring. -- This allows for a dynamic destruction zone evolution within your mission. -- @param #SCORING self --- @param Core.Zone#ZONE_BASE ScoreZone The @{Zone} which defines the destruction score perimeters. +-- @param Core.Zone#ZONE_BASE ScoreZone The @{Zone} which defines the destruction score perimeters. -- Note that a zone can be a polygon or a moving zone. -- @return #SCORING function SCORING:RemoveZoneScore( ScoreZone ) @@ -460,14 +450,13 @@ function SCORING:RemoveZoneScore( ScoreZone ) local ZoneName = ScoreZone:GetName() self.ScoringZones[ZoneName] = nil - + return self end - --- Configure to send messages after a target has been hit. -- @param #SCORING self --- @param #boolean OnOff If true is given, the messages are sent. +-- @param #boolean OnOff If true is given, the messages are sent. -- @return #SCORING function SCORING:SetMessagesHit( OnOff ) @@ -485,7 +474,7 @@ end --- Configure to send messages after a target has been destroyed. -- @param #SCORING self --- @param #boolean OnOff If true is given, the messages are sent. +-- @param #boolean OnOff If true is given, the messages are sent. -- @return #SCORING function SCORING:SetMessagesDestroy( OnOff ) @@ -503,7 +492,7 @@ end --- Configure to send messages after a target has been destroyed and receives additional scores. -- @param #SCORING self --- @param #boolean OnOff If true is given, the messages are sent. +-- @param #boolean OnOff If true is given, the messages are sent. -- @return #SCORING function SCORING:SetMessagesScore( OnOff ) @@ -521,7 +510,7 @@ end --- Configure to send messages after a target has been hit in a zone, and additional score is received. -- @param #SCORING self --- @param #boolean OnOff If true is given, the messages are sent. +-- @param #boolean OnOff If true is given, the messages are sent. -- @return #SCORING function SCORING:SetMessagesZone( OnOff ) @@ -571,10 +560,9 @@ function SCORING:IfMessagesToCoalition() return self.MessagesAudience == 2 end - --- When a player commits too much damage to friendlies, his penalty score will reach a certain level. --- Use this method to define the level when a player gets kicked. --- By default, the fratricide level is the default penalty mutiplier * 2 for the penalty score. +-- Use this method to define the level when a player gets kicked. +-- By default, the fratricide level is the default penalty multiplier * 2 for the penalty score. -- @param #SCORING self -- @param #number Fratricide The amount of maximum penalty that may be inflicted by a friendly player before he gets kicked. -- @return #SCORING @@ -604,9 +592,9 @@ end --- When a player changes the coalition, he can receive a penalty score. -- Use the method @{#SCORING.SetCoalitionChangePenalty}() to define the penalty when a player changes coalition. --- By default, the penalty for changing coalition is the default penalty scale. +-- By default, the penalty for changing coalition is the default penalty scale. -- @param #SCORING self --- @param #number CoalitionChangePenalty The amount of penalty that is given. +-- @param #number CoalitionChangePenalty The amount of penalty that is given. -- @return #SCORING function SCORING:SetCoalitionChangePenalty( CoalitionChangePenalty ) @@ -614,20 +602,18 @@ function SCORING:SetCoalitionChangePenalty( CoalitionChangePenalty ) return self end - --- Sets the scoring menu. -- @param #SCORING self -- @return #SCORING function SCORING:SetScoringMenu( ScoringGroup ) - local Menu = MENU_GROUP:New( ScoringGroup, 'Scoring and Statistics' ) - local ReportGroupSummary = MENU_GROUP_COMMAND:New( ScoringGroup, 'Summary report players in group', Menu, SCORING.ReportScoreGroupSummary, self, ScoringGroup ) - local ReportGroupDetailed = MENU_GROUP_COMMAND:New( ScoringGroup, 'Detailed report players in group', Menu, SCORING.ReportScoreGroupDetailed, self, ScoringGroup ) - local ReportToAllSummary = MENU_GROUP_COMMAND:New( ScoringGroup, 'Summary report all players', Menu, SCORING.ReportScoreAllSummary, self, ScoringGroup ) - self:SetState( ScoringGroup, "ScoringMenu", Menu ) + local Menu = MENU_GROUP:New( ScoringGroup, 'Scoring and Statistics' ) + local ReportGroupSummary = MENU_GROUP_COMMAND:New( ScoringGroup, 'Summary report players in group', Menu, SCORING.ReportScoreGroupSummary, self, ScoringGroup ) + local ReportGroupDetailed = MENU_GROUP_COMMAND:New( ScoringGroup, 'Detailed report players in group', Menu, SCORING.ReportScoreGroupDetailed, self, ScoringGroup ) + local ReportToAllSummary = MENU_GROUP_COMMAND:New( ScoringGroup, 'Summary report all players', Menu, SCORING.ReportScoreAllSummary, self, ScoringGroup ) + self:SetState( ScoringGroup, "ScoringMenu", Menu ) return self end - --- Add a new player entering a Unit. -- @param #SCORING self -- @param Wrapper.Unit#UNIT UnitData @@ -678,12 +664,12 @@ function SCORING:_AddPlayerFromUnit( UnitData ) 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].UNIT = UnitData self.Players[PlayerName].ThreatLevel = UnitThreatLevel self.Players[PlayerName].ThreatType = UnitThreatType @@ -691,22 +677,21 @@ function SCORING:_AddPlayerFromUnit( UnitData ) if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 and self.penaltyonfratricide 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() + MESSAGE.Type.Information ) + :ToAll() self.Players[PlayerName].PenaltyWarning = self.Players[PlayerName].PenaltyWarning + 1 end end if self.Players[PlayerName].Penalty > self.Fratricide and self.penaltyonfratricide then MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!", - MESSAGE.Type.Information - ):ToAll() + MESSAGE.Type.Information ) + :ToAll() UnitData:GetGroup():Destroy() end end end - --- Add a goal score for a player. -- The method takes the Player name for which the Goal score needs to be set. -- The GoalTag is a string or identifier that is taken into the CSV file scoring log to identify the goal. @@ -722,28 +707,28 @@ function SCORING:AddGoalScorePlayer( PlayerName, GoalTag, Text, Score ) self:F( { PlayerName, PlayerName, GoalTag, Text, Score } ) -- PlayerName can be nil, if the Unit with the player crashed or due to another reason. - if PlayerName then + 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.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score PlayerData.Score = PlayerData.Score + Score - - MESSAGE:NewType( self.DisplayMessagePrefix .. Text, MESSAGE.Type.Information ):ToAll() - + + MESSAGE:NewType( self.DisplayMessagePrefix .. Text, + MESSAGE.Type.Information ) + :ToAll() + self:ScoreCSV( PlayerName, "", "GOAL_" .. string.upper( GoalTag ), 1, Score, nil ) end end - - --- Add a goal score for a player. -- The method takes the PlayerUnit for which the Goal score needs to be set. -- The GoalTag is a string or identifier that is taken into the CSV file scoring log to identify the goal. -- A free text can be given that is shown to the players. -- The Score can be both positive and negative. -- @param #SCORING self --- @param Wrapper.Unit#UNIT PlayerUnit The @{Wrapper.Unit} of the Player. Other Properties for the scoring are taken from this PlayerUnit, like coalition, type etc. +-- @param Wrapper.Unit#UNIT PlayerUnit The @{Wrapper.Unit} of the Player. Other Properties for the scoring are taken from this PlayerUnit, like coalition, type etc. -- @param #string GoalTag The string or identifier that is used in the CSV file to identify the goal (sort or group later in Excel). -- @param #string Text A free text that is shown to the players. -- @param #number Score The score can be both positive or negative ( Penalty ). @@ -754,20 +739,21 @@ function SCORING:AddGoalScore( PlayerUnit, GoalTag, Text, Score ) self:F( { PlayerUnit.UnitName, PlayerName, GoalTag, Text, Score } ) -- PlayerName can be nil, if the Unit with the player crashed or due to another reason. - if PlayerName then + 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.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score PlayerData.Score = PlayerData.Score + Score - - MESSAGE:NewType( self.DisplayMessagePrefix .. Text, MESSAGE.Type.Information ):ToAll() - + + MESSAGE:NewType( self.DisplayMessagePrefix .. Text, + MESSAGE.Type.Information ) + :ToAll() + self:ScoreCSV( PlayerName, "", "GOAL_" .. string.upper( GoalTag ), 1, Score, PlayerUnit:GetName() ) end end - --- Registers Scores the players completing a Mission Task. -- @param #SCORING self -- @param Tasking.Mission#MISSION Mission @@ -782,23 +768,25 @@ function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score ) self:F( { Mission:GetName(), PlayerUnit.UnitName, PlayerName, Text, Score } ) -- PlayerName can be nil, if the Unit with the player crashed or due to another reason. - if PlayerName then + 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 .. Mission:GetText() .. " : " .. Text .. " Score: " .. Score, MESSAGE.Type.Information ):ToAll() - + + MESSAGE:NewType( self.DisplayMessagePrefix .. Mission:GetText() .. " : " .. Text .. " Score: " .. Score, + MESSAGE.Type.Information ) + :ToAll() + self:ScoreCSV( PlayerName, "", "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score, PlayerUnit:GetName() ) end end @@ -816,21 +804,21 @@ function SCORING:_AddMissionGoalScore( Mission, PlayerName, Text, Score ) self:F( { Mission:GetName(), PlayerName, Text, Score } ) -- PlayerName can be nil, if the Unit with the player crashed or due to another reason. - if PlayerName then + 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, Mission:GetText(), Text, PlayerName, Score ), MESSAGE.Type.Information ):ToAll() self:ScoreCSV( PlayerName, "", "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score ) @@ -844,7 +832,7 @@ end -- @param #string Text -- @param #number Score function SCORING:_AddMissionScore( Mission, Text, Score ) - + local MissionName = Mission:GetName() self:F( { Mission, Text, Score } ) @@ -858,32 +846,30 @@ function SCORING:_AddMissionScore( Mission, Text, Score ) PlayerData.Score = PlayerData.Score + Score PlayerData.Mission[MissionName].ScoreMission = PlayerData.Mission[MissionName].ScoreMission + Score - MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' has " .. Text .. " in " .. Mission:GetText() .. ". " .. - Score .. " mission score!", - MESSAGE.Type.Information ):ToAll() + MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' has " .. Text .. " in " .. Mission:GetText() .. ". " .. Score .. " mission score!", + MESSAGE.Type.Information ) + :ToAll() self:ScoreCSV( PlayerName, "", "MISSION_" .. MissionName:gsub( ' ', '_' ), 1, Score ) end end end - - --- Handles the OnPlayerEnterUnit event for the scoring. -- @param #SCORING self -- @param Core.Event#EVENTDATA Event ---function SCORING:OnEventPlayerEnterUnit( Event ) +-- function SCORING:OnEventPlayerEnterUnit( Event ) -- if Event.IniUnit then -- self:_AddPlayerFromUnit( Event.IniUnit ) -- self:SetScoringMenu( Event.IniGroup ) -- end ---end +-- end --- Handles the OnBirth event for the scoring. -- @param #SCORING self -- @param Core.Event#EVENTDATA Event function SCORING:OnEventBirth( Event ) - + if Event.IniUnit then if Event.IniObjectCategory == 1 then local PlayerName = Event.IniUnit:GetPlayerName() @@ -903,12 +889,11 @@ function SCORING:OnEventPlayerLeaveUnit( Event ) local Menu = self:GetState( Event.IniUnit:GetGroup(), "ScoringMenu" ) -- Core.Menu#MENU_GROUP if Menu then -- TODO: Check if this fixes #281. - --Menu:Remove() + -- Menu:Remove() end end end - --- Handles the OnHit event for the scoring. -- @param #SCORING self -- @param Core.Event#EVENTDATA Event @@ -953,9 +938,9 @@ function SCORING:_EventOnHit( Event ) InitPlayerName = Event.IniPlayerName InitCoalition = Event.IniCoalition - --TODO: Workaround Client DCS Bug - --InitCategory = InitUnit:getCategory() - --InitCategory = InitUnit:getDesc().category + -- TODO: Workaround Client DCS Bug + -- InitCategory = InitUnit:getCategory() + -- InitCategory = InitUnit:getDesc().category InitCategory = Event.IniCategory InitType = Event.IniTypeName @@ -963,10 +948,9 @@ function SCORING:_EventOnHit( Event ) InitUnitCategory = _SCORINGCategory[InitCategory] InitUnitType = InitType - self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType , InitUnitCoalition, InitUnitCategory, InitUnitType } ) + self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType, InitUnitCoalition, InitUnitCategory, InitUnitType } ) end - if Event.TgtDCSUnit then TargetUnit = Event.TgtDCSUnit @@ -977,9 +961,9 @@ function SCORING:_EventOnHit( Event ) TargetPlayerName = Event.TgtPlayerName TargetCoalition = Event.TgtCoalition - --TODO: Workaround Client DCS Bug - --TargetCategory = TargetUnit:getCategory() - --TargetCategory = TargetUnit:getDesc().category + -- TODO: Workaround Client DCS Bug + -- TargetCategory = TargetUnit:getCategory() + -- TargetCategory = TargetUnit:getDesc().category TargetCategory = Event.TgtCategory TargetType = Event.TgtTypeName @@ -998,21 +982,21 @@ function SCORING:_EventOnHit( Event ) end self:T( "Hitting Something" ) - + -- What is he hitting? if TargetCategory then - + -- A target got hit, score it. -- Player contains the score data from self.Players[InitPlayerName] local Player = self.Players[InitPlayerName] - + -- Ensure there is a hit table per TargetCategory and TargetUnitName. Player.Hit[TargetCategory] = Player.Hit[TargetCategory] or {} Player.Hit[TargetCategory][TargetUnitName] = Player.Hit[TargetCategory][TargetUnitName] or {} - + -- PlayerHit contains the score counters and data per unit that was hit. local PlayerHit = Player.Hit[TargetCategory][TargetUnitName] - + PlayerHit.Score = PlayerHit.Score or 0 PlayerHit.Penalty = PlayerHit.Penalty or 0 PlayerHit.ScoreHit = PlayerHit.ScoreHit or 0 @@ -1024,39 +1008,33 @@ function SCORING:_EventOnHit( Event ) -- Only grant hit scores if there was more than one second between the last hit. if timer.getTime() - PlayerHit.TimeStamp > 1 then PlayerHit.TimeStamp = timer.getTime() - + if TargetPlayerName ~= nil then -- It is a player hitting another player ... - + -- Ensure there is a Player to Player hit reference table. Player.HitPlayers[TargetPlayerName] = true end - + local Score = 0 - + if InitCoalition then -- A coalition object was hit. if InitCoalition == TargetCoalition then Player.Penalty = Player.Penalty + 10 PlayerHit.Penalty = PlayerHit.Penalty + 10 PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 - + if TargetPlayerName ~= nil then -- It is a player hitting another player ... - 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() ) + 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() ) + 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 @@ -1064,33 +1042,26 @@ function SCORING:_EventOnHit( Event ) PlayerHit.Score = PlayerHit.Score + 1 PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1 if TargetPlayerName ~= nil then -- It is a player hitting another player ... - 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() ) + 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() ) + 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 -- A scenery object was hit. - 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() ) + 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 @@ -1099,10 +1070,10 @@ function SCORING:_EventOnHit( Event ) elseif InitPlayerName == nil then -- It is an AI hitting a player??? 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 + if Event.WeaponPlayerName ~= nil then self:_AddPlayerFromUnit( Event.WeaponUNIT ) if self.Players[Event.WeaponPlayerName] then -- This should normally not happen, but i'll test it anyway. if TargetPlayerName ~= nil then -- It is a player hitting another player ... @@ -1110,21 +1081,21 @@ function SCORING:_EventOnHit( Event ) end self:T( "Hitting Scenery" ) - + -- What is he hitting? if TargetCategory then - + -- A scenery or static got hit, score it. -- Player contains the score data from self.Players[WeaponPlayerName] local Player = self.Players[Event.WeaponPlayerName] - + -- Ensure there is a hit table per TargetCategory and TargetUnitName. Player.Hit[TargetCategory] = Player.Hit[TargetCategory] or {} Player.Hit[TargetCategory][TargetUnitName] = Player.Hit[TargetCategory][TargetUnitName] or {} - + -- PlayerHit contains the score counters and data per unit that was hit. local PlayerHit = Player.Hit[TargetCategory][TargetUnitName] - + PlayerHit.Score = PlayerHit.Score or 0 PlayerHit.Penalty = PlayerHit.Penalty or 0 PlayerHit.ScoreHit = PlayerHit.ScoreHit or 0 @@ -1136,9 +1107,9 @@ function SCORING:_EventOnHit( Event ) -- Only grant hit scores if there was more than one second between the last hit. if timer.getTime() - PlayerHit.TimeStamp > 1 then PlayerHit.TimeStamp = timer.getTime() - + local Score = 0 - + if InitCoalition then -- A coalition object was hit, probably a static. if InitCoalition == TargetCoalition then -- TODO: Penalty according scale @@ -1159,23 +1130,20 @@ function SCORING:_EventOnHit( Event ) 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() ) + 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 -- A scenery object was hit. - 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() ) + 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 @@ -1211,8 +1179,8 @@ function SCORING:_EventOnDeadOrCrash( Event ) TargetPlayerName = Event.IniPlayerName TargetCoalition = Event.IniCoalition - --TargetCategory = TargetUnit:getCategory() - --TargetCategory = TargetUnit:getDesc().category -- Workaround + -- TargetCategory = TargetUnit:getCategory() + -- TargetCategory = TargetUnit:getDesc().category -- Workaround TargetCategory = Event.IniCategory TargetType = Event.IniTypeName @@ -1242,10 +1210,10 @@ function SCORING:_EventOnDeadOrCrash( Event ) -- What is the player destroying? if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] and Player.Hit[TargetCategory][TargetUnitName].TimeStamp ~= 0 then -- Was there a hit for this unit for this player before registered??? - + 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 {} @@ -1253,7 +1221,7 @@ function SCORING:_EventOnDeadOrCrash( Event ) 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.Penalty = TargetDestroy.Penalty or 0 TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy or 0 if TargetCoalition then @@ -1261,85 +1229,73 @@ function SCORING:_EventOnDeadOrCrash( Event ) local ThreatLevelTarget = TargetThreatLevel local ThreatTypeTarget = TargetThreatType local ThreatLevelPlayer = Player.ThreatLevel / 10 + 1 - local ThreatPenalty = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.ScaleDestroyPenalty / 10 ) - self:F( { ThreatLevel = ThreatPenalty, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } ) - + local ThreatPenalty = math.ceil( (ThreatLevelTarget / ThreatLevelPlayer) * self.ScaleDestroyPenalty / 10 ) + self:F( { 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 -- A player destroyed another player - 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() ) + 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() ) + 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 ) + Destroyed = true else local ThreatLevelTarget = TargetThreatLevel local ThreatTypeTarget = TargetThreatType local ThreatLevelPlayer = Player.ThreatLevel / 10 + 1 - local ThreatScore = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.ScaleDestroyScore / 10 ) - - self:F( { ThreatLevel = ThreatScore, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } ) - + local ThreatScore = math.ceil( (ThreatLevelTarget / ThreatLevelPlayer) * self.ScaleDestroyScore / 10 ) + + self:F( { 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 -- A player destroyed another player - 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() ) + 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() ) + 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 ) - + Destroyed = true + 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() ) + 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 - + -- Check if there are Zones where the destruction happened. for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do self:F( { ScoringZone = ScoreZoneData } ) @@ -1348,42 +1304,39 @@ function SCORING:_EventOnDeadOrCrash( Event ) 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() ) + 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 -- Check if there are Zones where the destruction happened. for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do - self:F( { ScoringZone = ScoreZoneData } ) + self:F( { ScoringZone = ScoreZoneData } ) local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE 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 + 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() ) + self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType ) + Destroyed = true end end end - + -- Delete now the hit cache if the target was destroyed. -- Otherwise points will be granted every time a target gets killed by the players that hit that target. -- This is only relevant for player to player destroys. @@ -1395,7 +1348,6 @@ function SCORING:_EventOnDeadOrCrash( Event ) end end - --- Produce detailed report of player hit scores. -- @param #SCORING self -- @param #string PlayerName The name of the player. @@ -1437,18 +1389,17 @@ function SCORING:ReportDetailedPlayerHits( PlayerName ) PlayerScore = PlayerScore + Score PlayerPenalty = PlayerPenalty + Penalty else - --ScoreMessageHits = ScoreMessageHits .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 ) + -- ScoreMessageHits = ScoreMessageHits .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 ) end end if ScoreMessageHits ~= "" then ScoreMessage = "Hits: " .. ScoreMessageHits end end - + return ScoreMessage, PlayerScore, PlayerPenalty end - --- Produce detailed report of player destroy scores. -- @param #SCORING self -- @param #string PlayerName The name of the player. @@ -1495,7 +1446,7 @@ function SCORING:ReportDetailedPlayerDestroys( PlayerName ) PlayerScore = PlayerScore + Score PlayerPenalty = PlayerPenalty + Penalty else - --ScoreMessageDestroys = ScoreMessageDestroys .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 ) + -- ScoreMessageDestroys = ScoreMessageDestroys .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 ) end end if ScoreMessageDestroys ~= "" then @@ -1535,7 +1486,7 @@ function SCORING:ReportDetailedPlayerCoalitionChanges( PlayerName ) ScoreMessage = ScoreMessage .. "Coalition Penalties: " .. ScoreMessageCoalitionChangePenalties end end - + return ScoreMessage, PlayerScore, PlayerPenalty end @@ -1572,7 +1523,7 @@ function SCORING:ReportDetailedPlayerGoals( PlayerName ) ScoreMessage = "Goals: " .. ScoreMessageGoal end end - + return ScoreMessage, PlayerScore, PlayerPenalty end @@ -1610,11 +1561,10 @@ function SCORING:ReportDetailedPlayerMissions( PlayerName ) ScoreMessage = "Tasks: " .. ScoreTask .. " Mission: " .. ScoreMission .. " ( " .. ScoreMessageMission .. ")" end end - + return ScoreMessage, PlayerScore, PlayerPenalty end - --- Report Group Score Summary -- @param #SCORING self -- @param Wrapper.Group#GROUP PlayerGroup The player group. @@ -1628,11 +1578,11 @@ function SCORING:ReportScoreGroupSummary( PlayerGroup ) for UnitID, PlayerUnit in pairs( PlayerUnits ) do local PlayerUnit = PlayerUnit -- Wrapper.Unit#UNIT local PlayerName = PlayerUnit:GetPlayerName() - + if PlayerName then - + local ReportHits, ScoreHits, PenaltyHits = self:ReportDetailedPlayerHits( PlayerName ) - ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits + ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits self:F( { ReportHits, ScoreHits, PenaltyHits } ) local ReportDestroys, ScoreDestroys, PenaltyDestroys = self:ReportDetailedPlayerDestroys( PlayerName ) @@ -1650,17 +1600,16 @@ function SCORING:ReportScoreGroupSummary( PlayerGroup ) local ReportMissions, ScoreMissions, PenaltyMissions = self:ReportDetailedPlayerMissions( PlayerName ) ReportMissions = ReportMissions ~= "" and "\n- " .. ReportMissions or ReportMissions self:F( { ReportMissions, ScoreMissions, PenaltyMissions } ) - + local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions - - PlayerMessage = - string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )", - PlayerName, - PlayerScore - PlayerPenalty, - PlayerScore, - PlayerPenalty - ) + + 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 @@ -1680,11 +1629,11 @@ function SCORING:ReportScoreGroupDetailed( PlayerGroup ) for UnitID, PlayerUnit in pairs( PlayerUnits ) do local PlayerUnit = PlayerUnit -- Wrapper.Unit#UNIT local PlayerName = PlayerUnit:GetPlayerName() - + if PlayerName then - + local ReportHits, ScoreHits, PenaltyHits = self:ReportDetailedPlayerHits( PlayerName ) - ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits + ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits self:F( { ReportHits, ScoreHits, PenaltyHits } ) local ReportDestroys, ScoreDestroys, PenaltyDestroys = self:ReportDetailedPlayerDestroys( PlayerName ) @@ -1694,7 +1643,7 @@ function SCORING:ReportScoreGroupDetailed( PlayerGroup ) local ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges = self:ReportDetailedPlayerCoalitionChanges( PlayerName ) ReportCoalitionChanges = ReportCoalitionChanges ~= "" and "\n- " .. ReportCoalitionChanges or ReportCoalitionChanges self:F( { ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges } ) - + local ReportGoals, ScoreGoals, PenaltyGoals = self:ReportDetailedPlayerGoals( PlayerName ) ReportGoals = ReportGoals ~= "" and "\n- " .. ReportGoals or ReportGoals self:F( { ReportGoals, ScoreGoals, PenaltyGoals } ) @@ -1702,7 +1651,7 @@ function SCORING:ReportScoreGroupDetailed( PlayerGroup ) local ReportMissions, ScoreMissions, PenaltyMissions = self:ReportDetailedPlayerMissions( PlayerName ) ReportMissions = ReportMissions ~= "" and "\n- " .. ReportMissions or ReportMissions self:F( { ReportMissions, ScoreMissions, PenaltyMissions } ) - + local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions @@ -1734,13 +1683,13 @@ function SCORING:ReportScoreAllSummary( PlayerGroup ) self:T( { "Summary Score Report of All Players", Players = self.Players } ) for PlayerName, PlayerData in pairs( self.Players ) do - + self:T( { PlayerName = PlayerName, PlayerGroup = PlayerGroup } ) - + if PlayerName then - + local ReportHits, ScoreHits, PenaltyHits = self:ReportDetailedPlayerHits( PlayerName ) - ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits + ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits self:F( { ReportHits, ScoreHits, PenaltyHits } ) local ReportDestroys, ScoreDestroys, PenaltyDestroys = self:ReportDetailedPlayerDestroys( PlayerName ) @@ -1758,7 +1707,7 @@ function SCORING:ReportScoreAllSummary( PlayerGroup ) local ReportMissions, ScoreMissions, PenaltyMissions = self:ReportDetailedPlayerMissions( PlayerName ) ReportMissions = ReportMissions ~= "" and "\n- " .. ReportMissions or ReportMissions self:F( { ReportMissions, ScoreMissions, PenaltyMissions } ) - + local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions @@ -1775,17 +1724,16 @@ function SCORING:ReportScoreAllSummary( PlayerGroup ) end - -function SCORING:SecondsToClock(sSeconds) +function SCORING:SecondsToClock( sSeconds ) local nSeconds = sSeconds if nSeconds == 0 then - --return nil; + -- return nil; 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 + 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 @@ -1799,7 +1747,7 @@ end -- ScoringObject:OpenCSV( "Player Scores" ) function SCORING:OpenCSV( ScoringCSV ) self:F( ScoringCSV ) - + if lfs and io and os then if ScoringCSV then self.ScoringCSV = ScoringCSV @@ -1810,9 +1758,9 @@ function SCORING:OpenCSV( ScoringCSV ) 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") + self.CSVFile:write( '"GameName","RunTime","Time","PlayerName","TargetPlayerName","ScoreType","PlayerUnitCoalition","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 @@ -1822,7 +1770,6 @@ function SCORING:OpenCSV( ScoringCSV ) return self end - --- Registers a score for a player. -- @param #SCORING self -- @param #string PlayerName The name of the player. @@ -1840,10 +1787,10 @@ end -- @param #string TargetUnitType The type of the target unit. -- @return #SCORING self function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes, ScoreAmount, PlayerUnitName, PlayerUnitCoalition, PlayerUnitCategory, PlayerUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - --write statistic information to file + -- write statistic information to file local ScoreTime = self:SecondsToClock( timer.getTime() ) PlayerName = PlayerName:gsub( '"', '_' ) - + TargetPlayerName = TargetPlayerName or "" TargetPlayerName = TargetPlayerName:gsub( '"', '_' ) @@ -1852,7 +1799,7 @@ function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes, if PlayerUnit then if not PlayerUnitCategory then - --PlayerUnitCategory = SCORINGCategory[PlayerUnit:getCategory()] + -- PlayerUnitCategory = SCORINGCategory[PlayerUnit:getCategory()] PlayerUnitCategory = _SCORINGCategory[PlayerUnit:getDesc().category] end @@ -1905,7 +1852,6 @@ function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes, end end - function SCORING:CloseCSV() if lfs and io and os then self.CSVFile:close() diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 7b51d8da1..c753a7b0a 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -51,7 +51,7 @@ -- @field #boolean Report If true, send status messages to coalition. -- @field Wrapper.Static#STATIC warehouse The phyical warehouse structure. -- @field #string alias Alias of the warehouse. Name its called when sending messages. --- @field Core.Zone#ZONE zone Zone around the warehouse. If this zone is captured, the warehouse and all its assets goes to the capturing coaliton. +-- @field Core.Zone#ZONE zone Zone around the warehouse. If this zone is captured, the warehouse and all its assets goes to the capturing coalition. -- @field Wrapper.Airbase#AIRBASE airbase Airbase the warehouse belongs to. -- @field #string airbasename Name of the airbase associated to the warehouse. -- @field Core.Point#COORDINATE road Closest point to warehouse on road. @@ -773,7 +773,7 @@ -- warehouseBatumi:Load("D:\\My Warehouse Data\\") -- warehouseBatumi:Start() -- --- This sequence loads all assets from file. If a warehouse was captured in the last mission, it also respawns the static warehouse structure with the right coaliton. +-- This sequence loads all assets from file. If a warehouse was captured in the last mission, it also respawns the static warehouse structure with the right coalition. -- However, it due to DCS limitations it is not possible to set the airbase coalition. This has to be done manually in the mission editor. Or alternatively, one could -- spawn some ground units via a self request and let them capture the airbase. -- @@ -1613,7 +1613,7 @@ WAREHOUSE = { -- @field #number range Range of the unit in meters. -- @field #number speedmax Maximum speed in km/h the group can do. -- @field #number size Maximum size in length and with of the asset in meters. --- @field #number weight The weight of the whole asset group in kilo gramms. +-- @field #number weight The weight of the whole asset group in kilograms. -- @field DCS#Object.Desc DCSdesc All DCS descriptors. -- @field #WAREHOUSE.Attribute attribute Generalized attribute of the group. -- @field #table cargobay Array of cargo bays of all units in an asset group. @@ -1835,7 +1835,7 @@ WAREHOUSE.version="1.0.2" -- DONE: Add shipping lanes between warehouses. -- DONE: Handle cases with immobile units <== should be handled by dispatcher classes. -- DONE: Handle cases for aircraft carriers and other ships. Place warehouse on carrier possible? On others probably not - exclude them? --- DONE: Add general message function for sending to coaliton or debug. +-- DONE: Add general message function for sending to coalition or debug. -- DONE: Fine tune event handlers. -- DONE: Improve generalized attributes. -- DONE: If warehouse is destroyed, all asssets are gone. @@ -3245,7 +3245,7 @@ end -- @param MinAssets (Optional) Minimum number of assets the warehouse should have. Default 0. -- @param #string Descriptor (Optional) Descriptor describing the selected assets which should be in stock. See @{#WAREHOUSE.Descriptor} for possible values. -- @param DescriptorValue (Optional) Descriptor value selecting the type of assets which should be in stock. --- @param DCS#Coalition.side Coalition (Optional) Coalition side of the warehouse. Default is the same coaliton as the present warehouse. Set to false for any coalition. +-- @param DCS#Coalition.side Coalition (Optional) Coalition side of the warehouse. Default is the same coalition as the present warehouse. Set to false for any coalition. -- @param Core.Point#COORDINATE RefCoordinate (Optional) Coordinate to which the closest warehouse is searched. Default is the warehouse calling this function. -- @return #WAREHOUSE The the nearest warehouse object. Or nil if no warehouse is found. -- @return #number The distance to the nearest warehouse in meters. Or nil if no warehouse is found. @@ -3416,7 +3416,7 @@ function WAREHOUSE:onafterStart(From, Event, To) -- Short info. local text=string.format("Starting warehouse %s alias %s:\n",self.warehouse:GetName(), self.alias) - text=text..string.format("Coaliton = %s\n", self:GetCoalitionName()) + text=text..string.format("Coalition = %s\n", self:GetCoalitionName()) text=text..string.format("Country = %s\n", self:GetCountryName()) text=text..string.format("Airbase = %s (category=%d)\n", self:GetAirbaseName(), self:GetAirbaseCategory()) env.info(text) @@ -8736,7 +8736,7 @@ function WAREHOUSE:_GetStockAssetsText(messagetoall) end --- Create or update mark text at warehouse, which is displayed in F10 map showing how many assets of each type are in stock. --- Only the coaliton of the warehouse owner is able to see it. +-- Only the coalition of the warehouse owner is able to see it. -- @param #WAREHOUSE self -- @return #string Text about warehouse stock function WAREHOUSE:_UpdateWarehouseMarkText() diff --git a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua index d90dfcca7..9be2c35bf 100644 --- a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua +++ b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua @@ -804,7 +804,7 @@ do -- ZONE_CAPTURE_COALITION return IsEmpty end - --- Check if zone is "Guarded", i.e. only one (the defending) coaliton is present inside the zone. + --- Check if zone is "Guarded", i.e. only one (the defending) coalition is present inside the zone. -- @param #ZONE_CAPTURE_COALITION self -- @return #boolean self:IsAllInZoneOfCoalition( self.Coalition ) function ZONE_CAPTURE_COALITION:IsGuarded() @@ -826,7 +826,7 @@ do -- ZONE_CAPTURE_COALITION return IsCaptured end - --- Check if zone is "Attacked", i.e. another coaliton entered the zone. + --- Check if zone is "Attacked", i.e. another coalition entered the zone. -- @param #ZONE_CAPTURE_COALITION self -- @return #boolean self:IsSomeInZoneOfCoalition( self.Coalition ) function ZONE_CAPTURE_COALITION:IsAttacked() @@ -899,24 +899,23 @@ do -- ZONE_CAPTURE_COALITION end self:I(text) end - + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- Update Mark on F10 map. -- @param #ZONE_CAPTURE_COALITION self function ZONE_CAPTURE_COALITION:Mark() - + if self.MarkOn then - + local Coord = self:GetCoordinate() local ZoneName = self:GetZoneName() local State = self:GetState() - + -- Remove marks. if self.MarkRed then Coord:RemoveMark(self.MarkRed) @@ -924,21 +923,21 @@ do -- ZONE_CAPTURE_COALITION if self.MarkBlue then Coord:RemoveMark(self.MarkBlue) end - - -- Create new marks for each coaliton. + + -- Create new marks for each coalition. if self.Coalition == coalition.side.BLUE then - self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Blue\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State ) + self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Blue\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State ) self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Blue\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State ) elseif self.Coalition == coalition.side.RED then - self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Red\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State ) + self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Red\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State ) self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Red\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State ) else - self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State ) + self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State ) self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State ) end - + end - + end end diff --git a/Moose Development/Moose/Functional/ZoneGoalCoalition.lua b/Moose Development/Moose/Functional/ZoneGoalCoalition.lua index 6296b4835..d2086f1ad 100644 --- a/Moose Development/Moose/Functional/ZoneGoalCoalition.lua +++ b/Moose Development/Moose/Functional/ZoneGoalCoalition.lua @@ -1,16 +1,16 @@ --- **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. +-- +-- 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: **FlightControl** --- +-- -- === --- +-- -- @module Functional.ZoneGoalCoalition -- @image MOOSE.JPG @@ -24,34 +24,33 @@ do -- ZoneGoal -- @field #table ObjectCategories Table of object categories that are able to hold a zone. Default is UNITS and STATICS. -- @extends Functional.ZoneGoal#ZONE_GOAL - - --- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement 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. - -- + -- -- ## 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 - -- + -- -- ### 2.2 ZONE_GOAL_COALITION Events - -- + -- -- ### 2.3 ZONE_GOAL_COALITION State Machine - -- + -- -- @field #ZONE_GOAL_COALITION ZONE_GOAL_COALITION = { - ClassName = "ZONE_GOAL_COALITION", - Coalition = nil, - PreviousCoaliton = nil, - UnitCategories = nil, + ClassName = "ZONE_GOAL_COALITION", + Coalition = nil, + PreviousCoalition = nil, + UnitCategories = nil, ObjectCategories = nil, } - + --- @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. @@ -59,33 +58,32 @@ do -- ZoneGoal -- @param #table UnitCategories Table of unit categories. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). Default {Unit.Category.GROUND_UNIT}. -- @return #ZONE_GOAL_COALITION function ZONE_GOAL_COALITION:New( Zone, Coalition, UnitCategories ) - + if not Zone then - BASE:E("ERROR: No Zone specified in ZONE_GOAL_COALITON!") + BASE:E( "ERROR: No Zone specified in ZONE_GOAL_COALITION!" ) return nil end - + -- Inherit ZONE_GOAL. local self = BASE:Inherit( self, ZONE_GOAL:New( Zone ) ) -- #ZONE_GOAL_COALITION - self:F( { Zone = Zone, Coalition = Coalition } ) + self:F( { Zone = Zone, Coalition = Coalition } ) -- Set initial owner. - self:SetCoalition( Coalition or coalition.side.NEUTRAL) - + self:SetCoalition( Coalition or coalition.side.NEUTRAL ) + -- Set default unit and object categories for the zone scan. - self:SetUnitCategories(UnitCategories) + self:SetUnitCategories( UnitCategories ) self:SetObjectCategories() - + return self end - --- Set the owning coalition of the zone. -- @param #ZONE_GOAL_COALITION self -- @param DCSCoalition.DCSCoalition#coalition Coalition The coalition ID, e.g. *coalition.side.RED*. -- @return #ZONE_GOAL_COALITION function ZONE_GOAL_COALITION:SetCoalition( Coalition ) - self.PreviousCoalition=self.Coalition or Coalition + self.PreviousCoalition = self.Coalition or Coalition self.Coalition = Coalition return self end @@ -95,31 +93,31 @@ do -- ZoneGoal -- @param #table UnitCategories Table of unit categories. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). Default {Unit.Category.GROUND_UNIT}. -- @return #ZONE_GOAL_COALITION function ZONE_GOAL_COALITION:SetUnitCategories( UnitCategories ) - - if UnitCategories and type(UnitCategories)~="table" then - UnitCategories={UnitCategories} + + if UnitCategories and type( UnitCategories ) ~= "table" then + UnitCategories = { UnitCategories } end - - self.UnitCategories=UnitCategories or {Unit.Category.GROUND_UNIT} - + + self.UnitCategories = UnitCategories or { Unit.Category.GROUND_UNIT } + return self end - + --- Set the owning coalition of the zone. -- @param #ZONE_GOAL_COALITION self -- @param #table ObjectCategories Table of unit categories. See [DCS Class Object](https://wiki.hoggitworld.com/view/DCS_Class_Object). Default {Object.Category.UNIT, Object.Category.STATIC}, i.e. all UNITS and STATICS. -- @return #ZONE_GOAL_COALITION function ZONE_GOAL_COALITION:SetObjectCategories( ObjectCategories ) - - if ObjectCategories and type(ObjectCategories)~="table" then - ObjectCategories={ObjectCategories} + + if ObjectCategories and type( ObjectCategories ) ~= "table" then + ObjectCategories = { ObjectCategories } end - - self.ObjectCategories=ObjectCategories or {Object.Category.UNIT, Object.Category.STATIC} - + + self.ObjectCategories = ObjectCategories or { Object.Category.UNIT, Object.Category.STATIC } + return self - end - + end + --- Get the owning coalition of the zone. -- @param #ZONE_GOAL_COALITION self -- @return DCSCoalition.DCSCoalition#coalition Coalition. @@ -127,39 +125,37 @@ do -- ZoneGoal return self.Coalition end - --- Get the previous coaliton, i.e. the one owning the zone before the current one. + --- Get the previous coalition, i.e. the one owning the zone before the current one. -- @param #ZONE_GOAL_COALITION self -- @return DCSCoalition.DCSCoalition#coalition Coalition. function ZONE_GOAL_COALITION:GetPreviousCoalition() return self.PreviousCoalition end - --- Get the owning coalition name of the zone. -- @param #ZONE_GOAL_COALITION self -- @return #string Coalition name. function ZONE_GOAL_COALITION:GetCoalitionName() - return UTILS.GetCoalitionName(self.Coalition) + return UTILS.GetCoalitionName( self.Coalition ) end - --- Check status Coalition ownership. -- @param #ZONE_GOAL_COALITION self -- @return #ZONE_GOAL_COALITION function ZONE_GOAL_COALITION:StatusZone() - + -- Get current state. local State = self:GetState() - + -- Debug text. - local text=string.format("Zone state=%s, Owner=%s, Scanning...", State, self:GetCoalitionName()) - self:F(text) - + local text = string.format( "Zone state=%s, Owner=%s, Scanning...", State, self:GetCoalitionName() ) + self:F( text ) + -- Scan zone. self:Scan( self.ObjectCategories, self.UnitCategories ) - + return self end - + end diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 94b3774ce..88e650d99 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -45,10 +45,10 @@ -- === -- -- ### Author: **funkyfranky** +-- -- @module Ops.Atis -- @image OPS_ATIS.png - --- ATIS class. -- @type ATIS -- @field #string ClassName Name of the class. @@ -66,7 +66,7 @@ -- @field #string activerunway The active runway specified by the user. -- @field #number subduration Duration how long subtitles are displayed in seconds. -- @field #boolean metric If true, use metric units. If false, use imperial (default). --- @field #boolean PmmHg If true, give pressure in millimeters of Mercury. Default is inHg for imperial and hecto Pascal (=mili Bars) for metric units. +-- @field #boolean PmmHg If true, give pressure in millimeters of Mercury. Default is inHg for imperial and hectopascal (hPa, which is the same as millibar - mbar) for metric units. -- @field #boolean qnhonly If true, suppresses reporting QFE. Default is to report both QNH and QFE. -- @field #boolean TDegF If true, give temperature in degrees Fahrenheit. Default is in degrees Celsius independent of chosen unit system. -- @field #number zuludiff Time difference local vs. zulu in hours. @@ -125,7 +125,7 @@ -- ## Subtitles -- -- Currently, DCS allows for displaying subtitles of radio transmissions only from airborne units, *i.e.* airplanes and helicopters. Therefore, if you want to have subtitles, it is necessary to place an --- additonal aircraft on the ATIS airport and set it to uncontrolled. This unit can then function as a radio relay to transmit messages with subtitles. These subtitles will only be displayed, if the +-- additional aircraft on the ATIS airport and set it to uncontrolled. This unit can then function as a radio relay to transmit messages with subtitles. These subtitles will only be displayed, if the -- player has tuned in the correct ATIS frequency. -- -- Radio transmissions via an airborne unit can be set via the @{#ATIS.SetRadioRelayUnitName}(*unitname*) function, where the parameter *unitname* is the name of the unit passed as string, *e.g.* @@ -141,7 +141,7 @@ -- -- ## Active Runway -- --- By default, the currently active runway is determined automatically by analysing the wind direction. Therefore, you should obviously set the wind speed to be greater zero in your mission. +-- By default, the currently active runway is determined automatically by analyzing the wind direction. Therefore, you should obviously set the wind speed to be greater zero in your mission. -- -- Note however, there are a few special cases, where automatic detection does not yield the correct or desired result. -- For example, there are airports with more than one runway facing in the same direction (usually denoted left and right). In this case, there is obviously no *unique* result depending on the wind vector. @@ -169,7 +169,7 @@ -- -- ## Nav Aids -- --- Frequencies or channels of navigation aids can be specified by the user and are then provided as additional information. Unfortunately, it is **not possible** to aquire this information via the DCS API +-- Frequencies or channels of navigation aids can be specified by the user and are then provided as additional information. Unfortunately, it is **not possible** to acquire this information via the DCS API -- we have access to. -- -- As they say, all road lead to Rome but (for me) the easiest way to obtain the available nav aids data of an airport, is to start a mission and click on an airport symbol. @@ -238,7 +238,7 @@ -- -- atisBatumi:SetMetricUnits() -- --- With this, wind speed is given in meters per second, pressure in hecto Pascal (mbar), visibility in kilometers etc. +-- With this, wind speed is given in meters per second, pressure in hectopascal (hPa, which is the same as millibar - mbar), visibility in kilometers etc. -- -- # Sound Files -- @@ -255,14 +255,14 @@ -- # Marks on the F10 Map -- -- You can place marks on the F10 map via the @{#ATIS.SetMapMarks}() function. These will contain info about the ATIS frequency, the currently active runway and some basic info about the weather (wind, pressure and temperature). --- +-- -- # Text-To-Speech --- --- You can enable text-to-speech ATIS information with the @{#ATIS.SetSRS}() function. This uses [SRS](http://dcssimpleradio.com/) (Version >= 1.9.6.0) for broadcasing. +-- +-- You can enable text-to-speech ATIS information with the @{#ATIS.SetSRS}() function. This uses [SRS](http://dcssimpleradio.com/) (Version >= 1.9.6.0) for broadcasting. -- Advantages are that **no sound files** or radio relay units are necessary. Also the issue that FC3 aircraft hear all transmissions will be circumvented. --- +-- -- The @{#ATIS.SetSRS}() requires you to specify the path to the SRS install directory or more specifically the path to the DCS-SR-ExternalAudio.exe file. --- +-- -- Unfortunately, it is not possible to determine the duration of the complete transmission. So once the transmission is finished, there might be some radio silence before -- the next iteration begins. You can fine tune the time interval between transmissions with the @{#ATIS.SetQueueUpdateTime}() function. The default interval is 90 seconds. -- @@ -296,9 +296,9 @@ -- atisAbuDhabi:SetTowerFrequencies({250.5, 119.2}) -- atisAbuDhabi:SetVOR(114.25) -- atisAbuDhabi:Start() --- +-- -- ## SRS --- +-- -- atis=ATIS:New("Batumi", 305, radio.modulation.AM) -- atis:SetSRS("D:\\DCS\\_SRS\\", "male", "en-US") -- atis:Start() @@ -388,14 +388,14 @@ ATIS.Alphabet = { -- @field #number TheChannel -10° (West). -- @field #number Syria +5° (East). -- @field #number MarianaIslands +2° (East). -ATIS.RunwayM2T={ - Caucasus=0, - Nevada=12, - Normandy=-10, - PersianGulf=2, - TheChannel=-10, - Syria=5, - MarianaIslands=2, +ATIS.RunwayM2T = { + Caucasus = 0, + Nevada = 12, + Normandy = -10, + PersianGulf = 2, + TheChannel = -10, + Syria = 5, + MarianaIslands = 2, } --- Whether ICAO phraseology is used for ATIS broadcasts. @@ -407,14 +407,14 @@ ATIS.RunwayM2T={ -- @field #boolean TheChannel true. -- @field #boolean Syria true. -- @field #boolean MarianaIslands true. -ATIS.ICAOPhraseology={ - Caucasus=true, - Nevada=false, - Normandy=true, - PersianGulf=true, - TheChannel=true, - Syria=true, - MarianaIslands=true, +ATIS.ICAOPhraseology = { + Caucasus = true, + Nevada = false, + Normandy = true, + PersianGulf = true, + TheChannel = true, + Syria = true, + MarianaIslands = true } --- Nav point data. @@ -503,91 +503,90 @@ ATIS.ICAOPhraseology={ -- @field #ATIS.Soundfile TACANChannel -- @field #ATIS.Soundfile VORFrequency ATIS.Sound = { - ActiveRunway={filename="ActiveRunway.ogg", duration=0.99}, - AdviceOnInitial={filename="AdviceOnInitial.ogg", duration=3.00}, - Airport={filename="Airport.ogg", duration=0.66}, - Altimeter={filename="Altimeter.ogg", duration=0.68}, - At={filename="At.ogg", duration=0.41}, - CloudBase={filename="CloudBase.ogg", duration=0.82}, - CloudCeiling={filename="CloudCeiling.ogg", duration=0.61}, - CloudsBroken={filename="CloudsBroken.ogg", duration=1.07}, - CloudsFew={filename="CloudsFew.ogg", duration=0.99}, - CloudsNo={filename="CloudsNo.ogg", duration=1.01}, - CloudsNotAvailable={filename="CloudsNotAvailable.ogg", duration=2.35}, - CloudsOvercast={filename="CloudsOvercast.ogg", duration=0.83}, - CloudsScattered={filename="CloudsScattered.ogg", duration=1.18}, - Decimal={filename="Decimal.ogg", duration=0.54}, - DegreesCelsius={filename="DegreesCelsius.ogg", duration=1.27}, - DegreesFahrenheit={filename="DegreesFahrenheit.ogg", duration=1.23}, - DewPoint={filename="DewPoint.ogg", duration=0.65}, - Dust={filename="Dust.ogg", duration=0.54}, - Elevation={filename="Elevation.ogg", duration=0.78}, - EndOfInformation={filename="EndOfInformation.ogg", duration=1.15}, - Feet={filename="Feet.ogg", duration=0.45}, - Fog={filename="Fog.ogg", duration=0.47}, - Gusting={filename="Gusting.ogg", duration=0.55}, - HectoPascal={filename="HectoPascal.ogg", duration=1.15}, - Hundred={filename="Hundred.ogg", duration=0.47}, - InchesOfMercury={filename="InchesOfMercury.ogg", duration=1.16}, - Information={filename="Information.ogg", duration=0.85}, - Kilometers={filename="Kilometers.ogg", duration=0.78}, - Knots={filename="Knots.ogg", duration=0.59}, - Left={filename="Left.ogg", duration=0.54}, - MegaHertz={filename="MegaHertz.ogg", duration=0.87}, - Meters={filename="Meters.ogg", duration=0.59}, - MetersPerSecond={filename="MetersPerSecond.ogg", duration=1.14}, - Miles={filename="Miles.ogg", duration=0.60}, - MillimetersOfMercury={filename="MillimetersOfMercury.ogg", duration=1.53}, - Minus={filename="Minus.ogg", duration=0.64}, - N0={filename="N-0.ogg", duration=0.55}, - N1={filename="N-1.ogg", duration=0.41}, - N2={filename="N-2.ogg", duration=0.37}, - N3={filename="N-3.ogg", duration=0.41}, - N4={filename="N-4.ogg", duration=0.37}, - N5={filename="N-5.ogg", duration=0.43}, - N6={filename="N-6.ogg", duration=0.55}, - N7={filename="N-7.ogg", duration=0.43}, - N8={filename="N-8.ogg", duration=0.38}, - N9={filename="N-9.ogg", duration=0.55}, - NauticalMiles={filename="NauticalMiles.ogg", duration=1.04}, - None={filename="None.ogg", duration=0.43}, - QFE={filename="QFE.ogg", duration=0.63}, - QNH={filename="QNH.ogg", duration=0.71}, - Rain={filename="Rain.ogg", duration=0.41}, - Right={filename="Right.ogg", duration=0.44}, - Snow={filename="Snow.ogg", duration=0.48}, - SnowStorm={filename="SnowStorm.ogg", duration=0.82}, - StatuteMiles={filename="StatuteMiles.ogg", duration=1.15}, - SunriseAt={filename="SunriseAt.ogg", duration=0.92}, - SunsetAt={filename="SunsetAt.ogg", duration=0.95}, - Temperature={filename="Temperature.ogg", duration=0.64}, - Thousand={filename="Thousand.ogg", duration=0.55}, - ThunderStorm={filename="ThunderStorm.ogg", duration=0.81}, - TimeLocal={filename="TimeLocal.ogg", duration=0.90}, - TimeZulu={filename="TimeZulu.ogg", duration=0.86}, - TowerFrequency={filename="TowerFrequency.ogg", duration=1.19}, - Visibilty={filename="Visibility.ogg", duration=0.79}, - WeatherPhenomena={filename="WeatherPhenomena.ogg", duration=1.07}, - WindFrom={filename="WindFrom.ogg", duration=0.60}, - ILSFrequency={filename="ILSFrequency.ogg", duration=1.30}, - InnerNDBFrequency={filename="InnerNDBFrequency.ogg", duration=1.56}, - OuterNDBFrequency={filename="OuterNDBFrequency.ogg", duration=1.59}, - RunwayLength={filename="RunwayLength.ogg", duration=0.91}, - VORFrequency={filename="VORFrequency.ogg", duration=1.38}, - TACANChannel={filename="TACANChannel.ogg", duration=0.88}, - PRMGChannel={filename="PRMGChannel.ogg", duration=1.18}, - RSBNChannel={filename="RSBNChannel.ogg", duration=1.14}, - Zulu={filename="Zulu.ogg", duration=0.62}, + ActiveRunway = { filename = "ActiveRunway.ogg", duration = 0.99 }, + AdviceOnInitial = { filename = "AdviceOnInitial.ogg", duration = 3.00 }, + Airport = { filename = "Airport.ogg", duration = 0.66 }, + Altimeter = { filename = "Altimeter.ogg", duration = 0.68 }, + At = { filename = "At.ogg", duration = 0.41 }, + CloudBase = { filename = "CloudBase.ogg", duration = 0.82 }, + CloudCeiling = { filename = "CloudCeiling.ogg", duration = 0.61 }, + CloudsBroken = { filename = "CloudsBroken.ogg", duration = 1.07 }, + CloudsFew = { filename = "CloudsFew.ogg", duration = 0.99 }, + CloudsNo = { filename = "CloudsNo.ogg", duration = 1.01 }, + CloudsNotAvailable = { filename = "CloudsNotAvailable.ogg", duration = 2.35 }, + CloudsOvercast = { filename = "CloudsOvercast.ogg", duration = 0.83 }, + CloudsScattered = { filename = "CloudsScattered.ogg", duration = 1.18 }, + Decimal = { filename = "Decimal.ogg", duration = 0.54 }, + DegreesCelsius = { filename = "DegreesCelsius.ogg", duration = 1.27 }, + DegreesFahrenheit = { filename = "DegreesFahrenheit.ogg", duration = 1.23 }, + DewPoint = { filename = "DewPoint.ogg", duration = 0.65 }, + Dust = { filename = "Dust.ogg", duration = 0.54 }, + Elevation = { filename = "Elevation.ogg", duration = 0.78 }, + EndOfInformation = { filename = "EndOfInformation.ogg", duration = 1.15 }, + Feet = { filename = "Feet.ogg", duration = 0.45 }, + Fog = { filename = "Fog.ogg", duration = 0.47 }, + Gusting = { filename = "Gusting.ogg", duration = 0.55 }, + HectoPascal = { filename = "HectoPascal.ogg", duration = 1.15 }, + Hundred = { filename = "Hundred.ogg", duration = 0.47 }, + InchesOfMercury = { filename = "InchesOfMercury.ogg", duration = 1.16 }, + Information = { filename = "Information.ogg", duration = 0.85 }, + Kilometers = { filename = "Kilometers.ogg", duration = 0.78 }, + Knots = { filename = "Knots.ogg", duration = 0.59 }, + Left = { filename = "Left.ogg", duration = 0.54 }, + MegaHertz = { filename = "MegaHertz.ogg", duration = 0.87 }, + Meters = { filename = "Meters.ogg", duration = 0.59 }, + MetersPerSecond = { filename = "MetersPerSecond.ogg", duration = 1.14 }, + Miles = { filename = "Miles.ogg", duration = 0.60 }, + MillimetersOfMercury = { filename = "MillimetersOfMercury.ogg", duration = 1.53 }, + Minus = { filename = "Minus.ogg", duration = 0.64 }, + N0 = { filename = "N-0.ogg", duration = 0.55 }, + N1 = { filename = "N-1.ogg", duration = 0.41 }, + N2 = { filename = "N-2.ogg", duration = 0.37 }, + N3 = { filename = "N-3.ogg", duration = 0.41 }, + N4 = { filename = "N-4.ogg", duration = 0.37 }, + N5 = { filename = "N-5.ogg", duration = 0.43 }, + N6 = { filename = "N-6.ogg", duration = 0.55 }, + N7 = { filename = "N-7.ogg", duration = 0.43 }, + N8 = { filename = "N-8.ogg", duration = 0.38 }, + N9 = { filename = "N-9.ogg", duration = 0.55 }, + NauticalMiles = { filename = "NauticalMiles.ogg", duration = 1.04 }, + None = { filename = "None.ogg", duration = 0.43 }, + QFE = { filename = "QFE.ogg", duration = 0.63 }, + QNH = { filename = "QNH.ogg", duration = 0.71 }, + Rain = { filename = "Rain.ogg", duration = 0.41 }, + Right = { filename = "Right.ogg", duration = 0.44 }, + Snow = { filename = "Snow.ogg", duration = 0.48 }, + SnowStorm = { filename = "SnowStorm.ogg", duration = 0.82 }, + StatuteMiles = { filename = "StatuteMiles.ogg", duration = 1.15 }, + SunriseAt = { filename = "SunriseAt.ogg", duration = 0.92 }, + SunsetAt = { filename = "SunsetAt.ogg", duration = 0.95 }, + Temperature = { filename = "Temperature.ogg", duration = 0.64 }, + Thousand = { filename = "Thousand.ogg", duration = 0.55 }, + ThunderStorm = { filename = "ThunderStorm.ogg", duration = 0.81 }, + TimeLocal = { filename = "TimeLocal.ogg", duration = 0.90 }, + TimeZulu = { filename = "TimeZulu.ogg", duration = 0.86 }, + TowerFrequency = { filename = "TowerFrequency.ogg", duration = 1.19 }, + Visibilty = { filename = "Visibility.ogg", duration = 0.79 }, + WeatherPhenomena = { filename = "WeatherPhenomena.ogg", duration = 1.07 }, + WindFrom = { filename = "WindFrom.ogg", duration = 0.60 }, + ILSFrequency = { filename = "ILSFrequency.ogg", duration = 1.30 }, + InnerNDBFrequency = { filename = "InnerNDBFrequency.ogg", duration = 1.56 }, + OuterNDBFrequency = { filename = "OuterNDBFrequency.ogg", duration = 1.59 }, + RunwayLength = { filename = "RunwayLength.ogg", duration = 0.91 }, + VORFrequency = { filename = "VORFrequency.ogg", duration = 1.38 }, + TACANChannel = { filename = "TACANChannel.ogg", duration = 0.88 }, + PRMGChannel = { filename = "PRMGChannel.ogg", duration = 1.18 }, + RSBNChannel = { filename = "RSBNChannel.ogg", duration = 1.14 }, + Zulu = { filename = "Zulu.ogg", duration = 0.62 }, } - --- ATIS table containing all defined ATISes. -- @field #table _ATIS -_ATIS={} +_ATIS = {} --- ATIS class version. -- @field #string version -ATIS.version="0.9.6" +ATIS.version = "0.9.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -619,6 +618,8 @@ ATIS.version="0.9.6" function ATIS:New(AirbaseName, Frequency, Modulation) -- Inherit everything from FSM class. + local self = BASE:Inherit( self, FSM:New() ) -- #ATIS + local self=BASE:Inherit(self, FSM:New()) -- #ATIS self.airbasename=AirbaseName @@ -634,13 +635,13 @@ function ATIS:New(AirbaseName, Frequency, Modulation) self.modulation=Modulation or 0 -- Get map. - self.theatre=env.mission.theatre + self.theatre = env.mission.theatre -- Set some string id for output to DCS.log file. - self.lid=string.format("ATIS %s | ", self.airbasename) + self.lid = string.format( "ATIS %s | ", self.airbasename ) -- This is just to hinder the garbage collector deallocating the ATIS object. - _ATIS[#_ATIS+1]=self + _ATIS[#_ATIS + 1] = self -- Defaults: self:SetSoundfilesPath() @@ -648,13 +649,13 @@ function ATIS:New(AirbaseName, Frequency, Modulation) self:SetMagneticDeclination() self:SetRunwayCorrectionMagnetic2True() self:SetRadioPower() - self:SetAltimeterQNH(true) - self:SetMapMarks(false) + self:SetAltimeterQNH( true ) + self:SetMapMarks( false ) self:SetRelativeHumidity() self:SetQueueUpdateTime() -- Start State. - self:SetStartState("Stopped") + self:SetStartState( "Stopped" ) -- Add FSM transitions. -- From State --> Event --> To State @@ -678,7 +679,6 @@ function ATIS:New(AirbaseName, Frequency, Modulation) -- @param #ATIS self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "Stop". Stops the ATIS. -- @function [parent=#ATIS] Stop -- @param #ATIS self @@ -688,7 +688,6 @@ function ATIS:New(AirbaseName, Frequency, Modulation) -- @param #ATIS self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "Status". -- @function [parent=#ATIS] Status -- @param #ATIS self @@ -698,7 +697,6 @@ function ATIS:New(AirbaseName, Frequency, Modulation) -- @param #ATIS self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "Broadcast". -- @function [parent=#ATIS] Broadcast -- @param #ATIS self @@ -708,7 +706,6 @@ function ATIS:New(AirbaseName, Frequency, Modulation) -- @param #ATIS self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "CheckQueue". -- @function [parent=#ATIS] CheckQueue -- @param #ATIS self @@ -718,7 +715,6 @@ function ATIS:New(AirbaseName, Frequency, Modulation) -- @param #ATIS self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "Report". -- @function [parent=#ATIS] Report -- @param #ATIS self @@ -749,9 +745,9 @@ end -- @param #ATIS self -- @param #string path Path for sound files. Default "ATIS Soundfiles/". Mind the slash "/" at the end! -- @return #ATIS self -function ATIS:SetSoundfilesPath(path) - self.soundpath=tostring(path or "ATIS Soundfiles/") - self:I(self.lid..string.format("Setting sound files path to %s", self.soundpath)) +function ATIS:SetSoundfilesPath( path ) + self.soundpath = tostring( path or "ATIS Soundfiles/" ) + self:I( self.lid .. string.format( "Setting sound files path to %s", self.soundpath ) ) return self end @@ -760,9 +756,9 @@ end -- @param #ATIS self -- @param #string unitname Name of the unit. -- @return #ATIS self -function ATIS:SetRadioRelayUnitName(unitname) - self.relayunitname=unitname - self:I(self.lid..string.format("Setting radio relay unit to %s", self.relayunitname)) +function ATIS:SetRadioRelayUnitName( unitname ) + self.relayunitname = unitname + self:I( self.lid .. string.format( "Setting radio relay unit to %s", self.relayunitname ) ) return self end @@ -770,13 +766,13 @@ end -- @param #ATIS self -- @param #table freqs Table of frequencies in MHz. A single frequency can be given as a plain number (*i.e.* must not be table). -- @return #ATIS self -function ATIS:SetTowerFrequencies(freqs) - if type(freqs)=="table" then +function ATIS:SetTowerFrequencies( freqs ) + if type( freqs ) == "table" then -- nothing to do else - freqs={freqs} + freqs = { freqs } end - self.towerfrequency=freqs + self.towerfrequency = freqs return self end @@ -785,8 +781,8 @@ end -- @param #ATIS self -- @param #string runway Active runway, *e.g.* "31L". -- @return #ATIS self -function ATIS:SetActiveRunway(runway) - self.activerunway=tostring(runway) +function ATIS:SetActiveRunway( runway ) + self.activerunway = tostring( runway ) return self end @@ -794,7 +790,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:SetRunwayLength() - self.rwylength=true + self.rwylength = true return self end @@ -811,7 +807,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:SetElevation() - self.elevation=true + self.elevation = true return self end @@ -819,8 +815,8 @@ end -- @param #ATIS self -- @param #number power Radio power in Watts. Default 100 W. -- @return #ATIS self -function ATIS:SetRadioPower(power) - self.power=power or 100 +function ATIS:SetRadioPower( power ) + self.power = power or 100 return self end @@ -828,11 +824,11 @@ end -- @param #ATIS self -- @param #boolean switch If *true* or *nil*, marks are placed on F10 map. If *false* this feature is set to off (default). -- @return #ATIS self -function ATIS:SetMapMarks(switch) - if switch==nil or switch==true then - self.usemarker=true +function ATIS:SetMapMarks( switch ) + if switch == nil or switch == true then + self.usemarker = true else - self.usemarker=false + self.usemarker = false end return self end @@ -841,46 +837,46 @@ end -- @param #ATIS self -- @param #table headings Magnetic headings. Inverse (-180°) headings are added automatically. You only need to specify one heading per runway direction. "L"eft and "R" right can also be appended. -- @return #ATIS self -function ATIS:SetRunwayHeadingsMagnetic(headings) +function ATIS:SetRunwayHeadingsMagnetic( headings ) -- First make sure, we have a table. - if type(headings)=="table" then + if type( headings ) == "table" then -- nothing to do else - headings={headings} + headings = { headings } end - for _,heading in pairs(headings) do + for _, heading in pairs( headings ) do - if type(heading)=="number" then - heading=string.format("%02d", heading) + if type( heading ) == "number" then + heading = string.format( "%02d", heading ) end -- Add runway heading to table. - self:I(self.lid..string.format("Adding user specified magnetic runway heading %s", heading)) - table.insert(self.runwaymag, heading) + self:I( self.lid .. string.format( "Adding user specified magnetic runway heading %s", heading ) ) + table.insert( self.runwaymag, heading ) - local h=self:GetRunwayWithoutLR(heading) + local h = self:GetRunwayWithoutLR( heading ) - local head2=tonumber(h)-18 - if head2<0 then - head2=head2+36 + local head2 = tonumber( h ) - 18 + if head2 < 0 then + head2 = head2 + 36 end -- Convert to string. - head2=string.format("%02d", head2) + head2 = string.format( "%02d", head2 ) -- Append "L" or "R" if necessary. - local left=self:GetRunwayLR(heading) - if left==true then - head2=head2.."L" - elseif left==false then - head2=head2.."R" + local left = self:GetRunwayLR( heading ) + if left == true then + head2 = head2 .. "L" + elseif left == false then + head2 = head2 .. "R" end -- Add inverse runway heading to table. - self:I(self.lid..string.format("Adding user specified magnetic runway heading %s (inverse)", head2)) - table.insert(self.runwaymag, head2) + self:I( self.lid .. string.format( "Adding user specified magnetic runway heading %s (inverse)", head2 ) ) + table.insert( self.runwaymag, head2 ) end return self @@ -890,8 +886,8 @@ end -- @param #ATIS self -- @param #number duration Duration in seconds. Default 10 seconds. -- @return #ATIS self -function ATIS:SetSubtitleDuration(duration) - self.subduration=tonumber(duration or 10) +function ATIS:SetSubtitleDuration( duration ) + self.subduration = tonumber( duration or 10 ) return self end @@ -899,7 +895,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:SetMetricUnits() - self.metric=true + self.metric = true return self end @@ -907,7 +903,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:SetImperialUnits() - self.metric=false + self.metric = false return self end @@ -916,7 +912,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:SetPressureMillimetersMercury() - self.PmmHg=true + self.PmmHg = true return self end @@ -924,7 +920,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:SetTemperatureFahrenheit() - self.TDegF=true + self.TDegF = true return self end @@ -933,8 +929,8 @@ end -- @param #ATIS self -- @param #number Humidity Relative Humidity, i.e. a number between 0 and 100 %. Default is 50 %. -- @return #ATIS self -function ATIS:SetRelativeHumidity(Humidity) - self.relHumidity=Humidity or 50 +function ATIS:SetRelativeHumidity( Humidity ) + self.relHumidity = Humidity or 50 return self end @@ -942,12 +938,12 @@ end -- @param #ATIS self -- @param #boolean switch If true or nil, report altimeter QHN. If false, report QFF. -- @return #ATIS self -function ATIS:SetAltimeterQNH(switch) +function ATIS:SetAltimeterQNH( switch ) - if switch==true or switch==nil then - self.altimeterQNH=true + if switch == true or switch == nil then + self.altimeterQNH = true else - self.altimeterQNH=false + self.altimeterQNH = false end return self @@ -957,7 +953,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:ReportQNHOnly() - self.qnhonly=true + self.qnhonly = true return self end @@ -986,8 +982,8 @@ end -- @param #ATIS self -- @param #number magvar Magnetic variation in degrees. Positive for easterly and negative for westerly variation. Default is magnatic declinaton of the used map, c.f. @{Utilities.UTils#UTILS.GetMagneticDeclination}. -- @return #ATIS self -function ATIS:SetMagneticDeclination(magvar) - self.magvar=magvar or UTILS.GetMagneticDeclination() +function ATIS:SetMagneticDeclination( magvar ) + self.magvar = magvar or UTILS.GetMagneticDeclination() return self end @@ -995,8 +991,8 @@ end -- @param #ATIS self -- @param #number correction Correction of magnetic to true heading for runways in degrees. -- @return #ATIS self -function ATIS:SetRunwayCorrectionMagnetic2True(correction) - self.runwaym2t=correction or ATIS.RunwayM2T[UTILS.GetDCSMap()] +function ATIS:SetRunwayCorrectionMagnetic2True( correction ) + self.runwaym2t = correction or ATIS.RunwayM2T[UTILS.GetDCSMap()] return self end @@ -1004,7 +1000,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:SetReportWindTrue() - self.windtrue=true + self.windtrue = true return self end @@ -1020,8 +1016,8 @@ end -- @param #ATIS self -- @param #number delta Time difference in hours. -- @return #ATIS self -function ATIS:SetZuluTimeDifference(delta) - self.zuludiff=delta +function ATIS:SetZuluTimeDifference( delta ) + self.zuludiff = delta return self end @@ -1029,7 +1025,7 @@ end -- @param #ATIS self -- @return #ATIS self function ATIS:ReportZuluTimeOnly() - self.zulutimeonly=true + self.zulutimeonly = true return self end @@ -1038,11 +1034,11 @@ end -- @param #number frequency ILS frequency in MHz. -- @param #string runway (Optional) Runway for which the given ILS frequency applies. Default all (*nil*). -- @return #ATIS self -function ATIS:AddILS(frequency, runway) - local ils={} --#ATIS.NavPoint - ils.frequency=tonumber(frequency) - ils.runway=runway and tostring(runway) or nil - table.insert(self.ils, ils) +function ATIS:AddILS( frequency, runway ) + local ils = {} -- #ATIS.NavPoint + ils.frequency = tonumber( frequency ) + ils.runway = runway and tostring( runway ) or nil + table.insert( self.ils, ils ) return self end @@ -1050,8 +1046,8 @@ end -- @param #ATIS self -- @param #number frequency VOR frequency. -- @return #ATIS self -function ATIS:SetVOR(frequency) - self.vor=frequency +function ATIS:SetVOR( frequency ) + self.vor = frequency return self end @@ -1060,11 +1056,11 @@ end -- @param #number frequency NDB frequency in MHz. -- @param #string runway (Optional) Runway for which the given NDB frequency applies. Default all (*nil*). -- @return #ATIS self -function ATIS:AddNDBouter(frequency, runway) - local ndb={} --#ATIS.NavPoint - ndb.frequency=tonumber(frequency) - ndb.runway=runway and tostring(runway) or nil - table.insert(self.ndbouter, ndb) +function ATIS:AddNDBouter( frequency, runway ) + local ndb = {} -- #ATIS.NavPoint + ndb.frequency = tonumber( frequency ) + ndb.runway = runway and tostring( runway ) or nil + table.insert( self.ndbouter, ndb ) return self end @@ -1073,11 +1069,11 @@ end -- @param #number frequency NDB frequency in MHz. -- @param #string runway (Optional) Runway for which the given NDB frequency applies. Default all (*nil*). -- @return #ATIS self -function ATIS:AddNDBinner(frequency, runway) - local ndb={} --#ATIS.NavPoint - ndb.frequency=tonumber(frequency) - ndb.runway=runway and tostring(runway) or nil - table.insert(self.ndbinner, ndb) +function ATIS:AddNDBinner( frequency, runway ) + local ndb = {} -- #ATIS.NavPoint + ndb.frequency = tonumber( frequency ) + ndb.runway = runway and tostring( runway ) or nil + table.insert( self.ndbinner, ndb ) return self end @@ -1085,8 +1081,8 @@ end -- @param #ATIS self -- @param #number channel TACAN channel. -- @return #ATIS self -function ATIS:SetTACAN(channel) - self.tacan=channel +function ATIS:SetTACAN( channel ) + self.tacan = channel return self end @@ -1094,8 +1090,8 @@ end -- @param #ATIS self -- @param #number channel RSBN channel. -- @return #ATIS self -function ATIS:SetRSBN(channel) - self.rsbn=channel +function ATIS:SetRSBN( channel ) + self.rsbn = channel return self end @@ -1104,24 +1100,23 @@ end -- @param #number channel PRMG channel. -- @param #string runway (Optional) Runway for which the given PRMG channel applies. Default all (*nil*). -- @return #ATIS self -function ATIS:AddPRMG(channel, runway) - local ndb={} --#ATIS.NavPoint - ndb.frequency=tonumber(channel) - ndb.runway=runway and tostring(runway) or nil - table.insert(self.prmg, ndb) +function ATIS:AddPRMG( channel, runway ) + local ndb = {} -- #ATIS.NavPoint + ndb.frequency = tonumber( channel ) + ndb.runway = runway and tostring( runway ) or nil + table.insert( self.prmg, ndb ) return self end - --- Place marks with runway data on the F10 map. -- @param #ATIS self -- @param #boolean markall If true, mark all runways of the map. By default only the current ATIS runways are marked. -function ATIS:MarkRunways(markall) - local airbases=AIRBASE.GetAllAirbases() - for _,_airbase in pairs(airbases) do - local airbase=_airbase --Wrapper.Airbase#AIRBASE - if (not markall and airbase:GetName()==self.airbasename) or markall==true then - airbase:GetRunwayData(self.runwaym2t, true) +function ATIS:MarkRunways( markall ) + local airbases = AIRBASE.GetAllAirbases() + for _, _airbase in pairs( airbases ) do + local airbase = _airbase -- Wrapper.Airbase#AIRBASE + if (not markall and airbase:GetName() == self.airbasename) or markall == true then + airbase:GetRunwayData( self.runwaym2t, true ) end end end @@ -1157,15 +1152,15 @@ end -- @param #ATIS self -- @param #number TimeInterval Interval in seconds. Default 5 sec. -- @return #ATIS self -function ATIS:SetQueueUpdateTime(TimeInterval) - self.dTQueueCheck=TimeInterval or 5 +function ATIS:SetQueueUpdateTime( TimeInterval ) + self.dTQueueCheck = TimeInterval or 5 end --- Get the coalition of the associated airbase. -- @param #ATIS self -- @return #number Coalition of the associcated airbase. function ATIS:GetCoalition() - local coal=self.airbase and self.airbase:GetCoalition() or nil + local coal = self.airbase and self.airbase:GetCoalition() or nil return coal end @@ -1178,51 +1173,51 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function ATIS:onafterStart(From, Event, To) +function ATIS:onafterStart( From, Event, To ) -- Check that this is an airdrome. - if self.airbase:GetAirbaseCategory()~=Airbase.Category.AIRDROME then - self:E(self.lid..string.format("ERROR: Cannot start ATIS for airbase %s! Only AIRDROMES are supported but NOT FARPS or SHIPS.", self.airbasename)) + if self.airbase:GetAirbaseCategory() ~= Airbase.Category.AIRDROME then + self:E( self.lid .. string.format( "ERROR: Cannot start ATIS for airbase %s! Only AIRDROMES are supported but NOT FARPS or SHIPS.", self.airbasename ) ) return end -- Info. - self:I(self.lid..string.format("Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation)) + self:I( self.lid .. string.format( "Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation ) ) -- Start radio queue. - self.radioqueue=RADIOQUEUE:New(self.frequency, self.modulation, string.format("ATIS %s", self.airbasename)) + self.radioqueue = RADIOQUEUE:New( self.frequency, self.modulation, string.format( "ATIS %s", self.airbasename ) ) -- Send coordinate is airbase coord. - self.radioqueue:SetSenderCoordinate(self.airbase:GetCoordinate()) + self.radioqueue:SetSenderCoordinate( self.airbase:GetCoordinate() ) -- Set relay unit if we have one. - self.radioqueue:SetSenderUnitName(self.relayunitname) + self.radioqueue:SetSenderUnitName( self.relayunitname ) -- Set radio power. - self.radioqueue:SetRadioPower(self.power) + self.radioqueue:SetRadioPower( self.power ) -- Init numbers. - self.radioqueue:SetDigit(0, ATIS.Sound.N0.filename, ATIS.Sound.N0.duration, self.soundpath) - self.radioqueue:SetDigit(1, ATIS.Sound.N1.filename, ATIS.Sound.N1.duration, self.soundpath) - self.radioqueue:SetDigit(2, ATIS.Sound.N2.filename, ATIS.Sound.N2.duration, self.soundpath) - self.radioqueue:SetDigit(3, ATIS.Sound.N3.filename, ATIS.Sound.N3.duration, self.soundpath) - self.radioqueue:SetDigit(4, ATIS.Sound.N4.filename, ATIS.Sound.N4.duration, self.soundpath) - self.radioqueue:SetDigit(5, ATIS.Sound.N5.filename, ATIS.Sound.N5.duration, self.soundpath) - self.radioqueue:SetDigit(6, ATIS.Sound.N6.filename, ATIS.Sound.N6.duration, self.soundpath) - self.radioqueue:SetDigit(7, ATIS.Sound.N7.filename, ATIS.Sound.N7.duration, self.soundpath) - self.radioqueue:SetDigit(8, ATIS.Sound.N8.filename, ATIS.Sound.N8.duration, self.soundpath) - self.radioqueue:SetDigit(9, ATIS.Sound.N9.filename, ATIS.Sound.N9.duration, self.soundpath) + self.radioqueue:SetDigit( 0, ATIS.Sound.N0.filename, ATIS.Sound.N0.duration, self.soundpath ) + self.radioqueue:SetDigit( 1, ATIS.Sound.N1.filename, ATIS.Sound.N1.duration, self.soundpath ) + self.radioqueue:SetDigit( 2, ATIS.Sound.N2.filename, ATIS.Sound.N2.duration, self.soundpath ) + self.radioqueue:SetDigit( 3, ATIS.Sound.N3.filename, ATIS.Sound.N3.duration, self.soundpath ) + self.radioqueue:SetDigit( 4, ATIS.Sound.N4.filename, ATIS.Sound.N4.duration, self.soundpath ) + self.radioqueue:SetDigit( 5, ATIS.Sound.N5.filename, ATIS.Sound.N5.duration, self.soundpath ) + self.radioqueue:SetDigit( 6, ATIS.Sound.N6.filename, ATIS.Sound.N6.duration, self.soundpath ) + self.radioqueue:SetDigit( 7, ATIS.Sound.N7.filename, ATIS.Sound.N7.duration, self.soundpath ) + self.radioqueue:SetDigit( 8, ATIS.Sound.N8.filename, ATIS.Sound.N8.duration, self.soundpath ) + self.radioqueue:SetDigit( 9, ATIS.Sound.N9.filename, ATIS.Sound.N9.duration, self.soundpath ) -- Start radio queue. - self.radioqueue:Start(1, 0.1) - + self.radioqueue:Start( 1, 0.1 ) + -- Handle airbase capture -- Handle events. - self:HandleEvent(EVENTS.BaseCaptured) + self:HandleEvent( EVENTS.BaseCaptured ) -- Init status updates. - self:__Status(-2) - self:__CheckQueue(-3) + self:__Status( -2 ) + self:__CheckQueue( -3 ) end --- Update status. @@ -1230,30 +1225,29 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function ATIS:onafterStatus(From, Event, To) +function ATIS:onafterStatus( From, Event, To ) -- Get FSM state. - local fsmstate=self:GetState() + local fsmstate = self:GetState() - local relayunitstatus="N/A" + local relayunitstatus = "N/A" if self.relayunitname then - local ru=UNIT:FindByName(self.relayunitname) + local ru = UNIT:FindByName( self.relayunitname ) if ru then - relayunitstatus=tostring(ru:IsAlive()) + relayunitstatus = tostring( ru:IsAlive() ) end end - -- Info text. - local text=string.format("State %s: Freq=%.3f MHz %s", fsmstate, self.frequency, UTILS.GetModulationName(self.modulation)) + -- Info text. + local text = string.format( "State %s: Freq=%.3f MHz %s", fsmstate, self.frequency, UTILS.GetModulationName( self.modulation ) ) if self.useSRS then - text=text..string.format(", SRS path=%s (%s), gender=%s, culture=%s, voice=%s", - tostring(self.msrs.path), tostring(self.msrs.port), tostring(self.msrs.gender), tostring(self.msrs.culture), tostring(self.msrs.voice)) + text = text .. string.format( ", SRS path=%s (%s), gender=%s, culture=%s, voice=%s", tostring( self.msrs.path ), tostring( self.msrs.port ), tostring( self.msrs.gender ), tostring( self.msrs.culture ), tostring( self.msrs.voice ) ) else - text=text..string.format(", Relay unit=%s (alive=%s)", tostring(self.relayunitname), relayunitstatus) + text = text .. string.format( ", Relay unit=%s (alive=%s)", tostring( self.relayunitname ), relayunitstatus ) end - self:I(self.lid..text) + self:I( self.lid .. text ) - self:__Status(-60) + self:__Status( -60 ) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1265,27 +1259,25 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function ATIS:onafterCheckQueue(From, Event, To) +function ATIS:onafterCheckQueue( From, Event, To ) if self.useSRS then - + self:Broadcast() - + else - - if #self.radioqueue.queue==0 then - self:T(self.lid..string.format("Radio queue empty. Repeating message.")) + + if #self.radioqueue.queue == 0 then + self:T( self.lid .. string.format( "Radio queue empty. Repeating message." ) ) self:Broadcast() else - self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue)) + self:T2( self.lid .. string.format( "Radio queue %d transmissions queued.", #self.radioqueue.queue ) ) end - - - + end -- Check back in 5 seconds. - self:__CheckQueue(-math.abs(self.dTQueueCheck)) + self:__CheckQueue( -math.abs( self.dTQueueCheck ) ) end --- Broadcast ATIS radio message. @@ -1293,69 +1285,67 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function ATIS:onafterBroadcast(From, Event, To) +function ATIS:onafterBroadcast( From, Event, To ) -- Get current coordinate. - local coord=self.airbase:GetCoordinate() + local coord = self.airbase:GetCoordinate() -- Get elevation. - local height=coord:GetLandHeight() + local height = coord:GetLandHeight() ---------------- --- Pressure --- ---------------- -- Pressure in hPa. - local qfe=coord:GetPressure(height) - local qnh=coord:GetPressure(0) + local qfe = coord:GetPressure( height ) + local qnh = coord:GetPressure( 0 ) if self.altimeterQNH then -- Some constants. - local L=-0.0065 --[K/m] - local R= 8.31446 --[J/mol/K] - local g= 9.80665 --[m/s^2] - local M= 0.0289644 --[kg/mol] - local T0=coord:GetTemperature(0)+273.15 --[K] Temp at sea level. - local TS=288.15 -- Standard Temperature assumed by Altimeter is 15°C - local q=qnh*100 + local L = -0.0065 -- [K/m] + local R = 8.31446 -- [J/mol/K] + local g = 9.80665 -- [m/s^2] + local M = 0.0289644 -- [kg/mol] + local T0 = coord:GetTemperature( 0 ) + 273.15 -- [K] Temp at sea level. + local TS = 288.15 -- Standard Temperature assumed by Altimeter is 15°C + local q = qnh * 100 -- Calculate Pressure. - local P=q*(1+L*height/T0)^(-g*M/(R*L)) -- Pressure at sea level - local Q=P/(1+L*height/TS)^(-g*M/(R*L)) -- Altimeter QNH - local A=(T0/L)*((P/q)^(((-R*L)/(g*M)))-1) -- Altitude check - + local P = q * (1 + L * height / T0) ^ (-g * M / (R * L)) -- Pressure at sea level + local Q = P / (1 + L * height / TS) ^ (-g * M / (R * L)) -- Altimeter QNH + local A = (T0 / L) * ((P / q) ^ (((-R * L) / (g * M))) - 1) -- Altitude check -- Debug aoutput - self:T2(self.lid..string.format("height=%.1f, A=%.1f, T0=%.1f, QFE=%.1f, QNH=%.1f, P=%.1f, Q=%.1f hPa = %.2f", height, A, T0-273.15, qfe, qnh, P/100, Q/100, UTILS.hPa2inHg(Q/100))) + self:T2( self.lid .. string.format( "height=%.1f, A=%.1f, T0=%.1f, QFE=%.1f, QNH=%.1f, P=%.1f, Q=%.1f hPa = %.2f", height, A, T0 - 273.15, qfe, qnh, P / 100, Q / 100, UTILS.hPa2inHg( Q / 100 ) ) ) -- Set QNH value in hPa. - qnh=Q/100 + qnh = Q / 100 end - -- Convert to inHg. if self.PmmHg then - qfe=UTILS.hPa2mmHg(qfe) - qnh=UTILS.hPa2mmHg(qnh) + qfe = UTILS.hPa2mmHg( qfe ) + qnh = UTILS.hPa2mmHg( qnh ) else if not self.metric then - qfe=UTILS.hPa2inHg(qfe) - qnh=UTILS.hPa2inHg(qnh) + qfe = UTILS.hPa2inHg( qfe ) + qnh = UTILS.hPa2inHg( qnh ) end end - local QFE=UTILS.Split(string.format("%.2f", qfe), ".") - local QNH=UTILS.Split(string.format("%.2f", qnh), ".") + local QFE = UTILS.Split( string.format( "%.2f", qfe ), "." ) + local QNH = UTILS.Split( string.format( "%.2f", qnh ), "." ) if self.PmmHg then - QFE=UTILS.Split(string.format("%.1f", qfe), ".") - QNH=UTILS.Split(string.format("%.1f", qnh), ".") + QFE = UTILS.Split( string.format( "%.1f", qfe ), "." ) + QNH = UTILS.Split( string.format( "%.1f", qnh ), "." ) else if self.metric then - QFE=UTILS.Split(string.format("%.1f", qfe), ".") - QNH=UTILS.Split(string.format("%.1f", qnh), ".") + QFE = UTILS.Split( string.format( "%.1f", qfe ), "." ) + QNH = UTILS.Split( string.format( "%.1f", qnh ), "." ) end end @@ -1364,30 +1354,30 @@ function ATIS:onafterBroadcast(From, Event, To) ------------ -- Get wind direction and speed in m/s. - local windFrom, windSpeed=coord:GetWind(height+10) + local windFrom, windSpeed = coord:GetWind( height + 10 ) -- Wind in magnetic or true. - local magvar=self.magvar + local magvar = self.magvar if self.windtrue then - magvar=0 + magvar = 0 end - windFrom=windFrom-magvar - + windFrom = windFrom - magvar + -- Correct negative values. - if windFrom<0 then - windFrom=windFrom+360 + if windFrom < 0 then + windFrom = windFrom + 360 end - local WINDFROM=string.format("%03d", windFrom) - local WINDSPEED=string.format("%d", UTILS.MpsToKnots(windSpeed)) - + local WINDFROM = string.format( "%03d", windFrom ) + local WINDSPEED = string.format( "%d", UTILS.MpsToKnots( windSpeed ) ) + -- Report North as 0. - if WINDFROM=="000" then - WINDFROM="360" + if WINDFROM == "000" then + WINDFROM = "360" end if self.metric then - WINDSPEED=string.format("%d", windSpeed) + WINDSPEED = string.format( "%d", windSpeed ) end -------------- @@ -1400,66 +1390,64 @@ function ATIS:onafterBroadcast(From, Event, To) ------------ --- Time --- ------------ - local time=timer.getAbsTime() + local time = timer.getAbsTime() -- Conversion to Zulu time. if self.zuludiff then -- User specified. - time=time-self.zuludiff*60*60 + time = time - self.zuludiff * 60 * 60 else - time=time-UTILS.GMTToLocalTimeDifference()*60*60 + time = time - UTILS.GMTToLocalTimeDifference() * 60 * 60 end if time < 0 then - time = 24*60*60 + time --avoid negative time around midnight - end - - local clock=UTILS.SecondsToClock(time) - local zulu=UTILS.Split(clock, ":") - local ZULU=string.format("%s%s", zulu[1], zulu[2]) - if self.useSRS then - ZULU=string.format("%s hours", zulu[1]) + time = 24 * 60 * 60 + time -- avoid negative time around midnight end + local clock = UTILS.SecondsToClock( time ) + local zulu = UTILS.Split( clock, ":" ) + local ZULU = string.format( "%s%s", zulu[1], zulu[2] ) + if self.useSRS then + ZULU = string.format( "%s hours", zulu[1] ) + end -- NATO time stamp. 0=Alfa, 1=Bravo, 2=Charlie, etc. - local NATO=ATIS.Alphabet[tonumber(zulu[1])+1] + local NATO = ATIS.Alphabet[tonumber( zulu[1] ) + 1] -- Debug. - self:T3(string.format("clock=%s", tostring(clock))) - self:T3(string.format("zulu1=%s", tostring(zulu[1]))) - self:T3(string.format("zulu2=%s", tostring(zulu[2]))) - self:T3(string.format("ZULU =%s", tostring(ZULU))) - self:T3(string.format("NATO =%s", tostring(NATO))) + self:T3( string.format( "clock=%s", tostring( clock ) ) ) + self:T3( string.format( "zulu1=%s", tostring( zulu[1] ) ) ) + self:T3( string.format( "zulu2=%s", tostring( zulu[2] ) ) ) + self:T3( string.format( "ZULU =%s", tostring( ZULU ) ) ) + self:T3( string.format( "NATO =%s", tostring( NATO ) ) ) -------------------------- --- Sunrise and Sunset --- -------------------------- - local sunrise=coord:GetSunrise() - sunrise=UTILS.Split(sunrise, ":") - local SUNRISE=string.format("%s%s", sunrise[1], sunrise[2]) + local sunrise = coord:GetSunrise() + sunrise = UTILS.Split( sunrise, ":" ) + local SUNRISE = string.format( "%s%s", sunrise[1], sunrise[2] ) if self.useSRS then - SUNRISE=string.format("%s %s hours", sunrise[1], sunrise[2]) - end + SUNRISE = string.format( "%s %s hours", sunrise[1], sunrise[2] ) + end - local sunset=coord:GetSunset() - sunset=UTILS.Split(sunset, ":") - local SUNSET=string.format("%s%s", sunset[1], sunset[2]) + local sunset = coord:GetSunset() + sunset = UTILS.Split( sunset, ":" ) + local SUNSET = string.format( "%s%s", sunset[1], sunset[2] ) if self.useSRS then - SUNSET=string.format("%s %s hours", sunset[1], sunset[2]) - end - + SUNSET = string.format( "%s %s hours", sunset[1], sunset[2] ) + end --------------------------------- --- Temperature and Dew Point --- --------------------------------- -- Temperature in °C. - local temperature=coord:GetTemperature(height+5) - + local temperature = coord:GetTemperature( height + 5 ) + -- Dew point in °C. - local dewpoint=temperature-(100-self.relHumidity)/5 + local dewpoint = temperature - (100 - self.relHumidity) / 5 -- Convert to °F. if self.TDegF then @@ -1467,24 +1455,24 @@ function ATIS:onafterBroadcast(From, Event, To) dewpoint=UTILS.CelsiusToFahrenheit(dewpoint) end - local TEMPERATURE=string.format("%d", math.abs(temperature)) - local DEWPOINT=string.format("%d", math.abs(dewpoint)) + local TEMPERATURE = string.format( "%d", math.abs( temperature ) ) + local DEWPOINT = string.format( "%d", math.abs( dewpoint ) ) --------------- --- Weather --- --------------- -- Get mission weather info. Most of this is static. - local clouds, visibility, turbulence, fog, dust, static=self:GetMissionWeather() + local clouds, visibility, turbulence, fog, dust, static = self:GetMissionWeather() -- Check that fog is actually "thick" enough to reach the airport. If an airport is in the mountains, fog might not affect it as it is measured from sea level. - if fog and fog.thicknessUTILS.FeetToMeters(1500) then - dust=nil + if dust and height + 25 > UTILS.FeetToMeters( 1500 ) then + dust = nil end ------------------ @@ -1492,214 +1480,214 @@ function ATIS:onafterBroadcast(From, Event, To) ------------------ -- Get min visibility. - local visibilitymin=visibility + local visibilitymin = visibility if fog then - if fog.visibility 10 then - reportedviz=10 + reportedviz = 10 end - VISIBILITY=string.format("%d", reportedviz) + VISIBILITY = string.format( "%d", reportedviz ) else -- max reported visibility 10 NM - local reportedviz=UTILS.Round(UTILS.MetersToSM(visibilitymin)) + local reportedviz = UTILS.Round( UTILS.MetersToSM( visibilitymin ) ) if reportedviz > 10 then - reportedviz=10 + reportedviz = 10 end - VISIBILITY=string.format("%d", reportedviz) + VISIBILITY = string.format( "%d", reportedviz ) end -------------- --- Clouds --- -------------- - local cloudbase=clouds.base - local cloudceil=clouds.base+clouds.thickness - local clouddens=clouds.density + local cloudbase = clouds.base + local cloudceil = clouds.base + clouds.thickness + local clouddens = clouds.density -- Cloud preset (DCS 2.7) - local cloudspreset=clouds.preset or "Nothing" - - -- Precepitation: 0=None, 1=Rain, 2=Thunderstorm, 3=Snow, 4=Snowstorm. - local precepitation=0 + local cloudspreset = clouds.preset or "Nothing" - if cloudspreset:find("Preset10") then + -- Precepitation: 0=None, 1=Rain, 2=Thunderstorm, 3=Snow, 4=Snowstorm. + local precepitation = 0 + + if cloudspreset:find( "Preset10" ) then -- Scattered 5 - clouddens=4 - elseif cloudspreset:find("Preset11") then + clouddens = 4 + elseif cloudspreset:find( "Preset11" ) then -- Scattered 6 - clouddens=4 - elseif cloudspreset:find("Preset12") then + clouddens = 4 + elseif cloudspreset:find( "Preset12" ) then -- Scattered 7 - clouddens=4 - elseif cloudspreset:find("Preset13") then + clouddens = 4 + elseif cloudspreset:find( "Preset13" ) then -- Broken 1 - clouddens=7 - elseif cloudspreset:find("Preset14") then + clouddens = 7 + elseif cloudspreset:find( "Preset14" ) then -- Broken 2 - clouddens=7 - elseif cloudspreset:find("Preset15") then + clouddens = 7 + elseif cloudspreset:find( "Preset15" ) then -- Broken 3 - clouddens=7 - elseif cloudspreset:find("Preset16") then + clouddens = 7 + elseif cloudspreset:find( "Preset16" ) then -- Broken 4 - clouddens=7 - elseif cloudspreset:find("Preset17") then + clouddens = 7 + elseif cloudspreset:find( "Preset17" ) then -- Broken 5 - clouddens=7 - elseif cloudspreset:find("Preset18") then + clouddens = 7 + elseif cloudspreset:find( "Preset18" ) then -- Broken 6 - clouddens=7 - elseif cloudspreset:find("Preset19") then + clouddens = 7 + elseif cloudspreset:find( "Preset19" ) then -- Broken 7 - clouddens=7 - elseif cloudspreset:find("Preset20") then + clouddens = 7 + elseif cloudspreset:find( "Preset20" ) then -- Broken 8 - clouddens=7 - elseif cloudspreset:find("Preset21") then + clouddens = 7 + elseif cloudspreset:find( "Preset21" ) then -- Overcast 1 - clouddens=9 - elseif cloudspreset:find("Preset22") then + clouddens = 9 + elseif cloudspreset:find( "Preset22" ) then -- Overcast 2 - clouddens=9 - elseif cloudspreset:find("Preset23") then + clouddens = 9 + elseif cloudspreset:find( "Preset23" ) then -- Overcast 3 - clouddens=9 - elseif cloudspreset:find("Preset24") then + clouddens = 9 + elseif cloudspreset:find( "Preset24" ) then -- Overcast 4 - clouddens=9 - elseif cloudspreset:find("Preset25") then + clouddens = 9 + elseif cloudspreset:find( "Preset25" ) then -- Overcast 5 - clouddens=9 - elseif cloudspreset:find("Preset26") then + clouddens = 9 + elseif cloudspreset:find( "Preset26" ) then -- Overcast 6 - clouddens=9 - elseif cloudspreset:find("Preset27") then + clouddens = 9 + elseif cloudspreset:find( "Preset27" ) then -- Overcast 7 - clouddens=9 - elseif cloudspreset:find("Preset1") then + clouddens = 9 + elseif cloudspreset:find( "Preset1" ) then -- Light Scattered 1 - clouddens=1 - elseif cloudspreset:find("Preset2") then + clouddens = 1 + elseif cloudspreset:find( "Preset2" ) then -- Light Scattered 2 - clouddens=1 - elseif cloudspreset:find("Preset3") then + clouddens = 1 + elseif cloudspreset:find( "Preset3" ) then -- High Scattered 1 - clouddens=4 - elseif cloudspreset:find("Preset4") then + clouddens = 4 + elseif cloudspreset:find( "Preset4" ) then -- High Scattered 2 - clouddens=4 - elseif cloudspreset:find("Preset5") then + clouddens = 4 + elseif cloudspreset:find( "Preset5" ) then -- Scattered 1 - clouddens=4 - elseif cloudspreset:find("Preset6") then + clouddens = 4 + elseif cloudspreset:find( "Preset6" ) then -- Scattered 2 - clouddens=4 - elseif cloudspreset:find("Preset7") then + clouddens = 4 + elseif cloudspreset:find( "Preset7" ) then -- Scattered 3 - clouddens=4 - elseif cloudspreset:find("Preset8") then + clouddens = 4 + elseif cloudspreset:find( "Preset8" ) then -- High Scattered 3 - clouddens=4 - elseif cloudspreset:find("Preset9") then + clouddens = 4 + elseif cloudspreset:find( "Preset9" ) then -- Scattered 4 - clouddens=4 - elseif cloudspreset:find("RainyPreset") then - -- Overcast + Rain - clouddens=9 - if temperature>5 then - precepitation=1 -- rain - else - precepitation=3 -- snow - end - elseif cloudspreset:find("RainyPreset1") then - -- Overcast + Rain - clouddens=9 - if temperature>5 then - precepitation=1 -- rain - else - precepitation=3 -- snow - end - elseif cloudspreset:find("RainyPreset2") then - -- Overcast + Rain - clouddens=9 - if temperature>5 then - precepitation=1 -- rain - else - precepitation=3 -- snow - end - elseif cloudspreset:find("RainyPreset3") then + clouddens = 4 + elseif cloudspreset:find( "RainyPreset" ) then -- Overcast + Rain - clouddens=9 - if temperature>5 then - precepitation=1 -- rain + clouddens = 9 + if temperature > 5 then + precepitation = 1 -- rain else - precepitation=3 -- snow + precepitation = 3 -- snow + end + elseif cloudspreset:find( "RainyPreset1" ) then + -- Overcast + Rain + clouddens = 9 + if temperature > 5 then + precepitation = 1 -- rain + else + precepitation = 3 -- snow + end + elseif cloudspreset:find( "RainyPreset2" ) then + -- Overcast + Rain + clouddens = 9 + if temperature > 5 then + precepitation = 1 -- rain + else + precepitation = 3 -- snow + end + elseif cloudspreset:find( "RainyPreset3" ) then + -- Overcast + Rain + clouddens = 9 + if temperature > 5 then + precepitation = 1 -- rain + else + precepitation = 3 -- snow end end - - local CLOUDBASE=string.format("%d", UTILS.MetersToFeet(cloudbase)) - local CLOUDCEIL=string.format("%d", UTILS.MetersToFeet(cloudceil)) + + local CLOUDBASE = string.format( "%d", UTILS.MetersToFeet( cloudbase ) ) + local CLOUDCEIL = string.format( "%d", UTILS.MetersToFeet( cloudceil ) ) if self.metric then - CLOUDBASE=string.format("%d", cloudbase) - CLOUDCEIL=string.format("%d", cloudceil) + CLOUDBASE = string.format( "%d", cloudbase ) + CLOUDCEIL = string.format( "%d", cloudceil ) end -- Cloud base/ceiling in thousands and hundrets of ft/meters. - local CLOUDBASE1000, CLOUDBASE0100=self:_GetThousandsAndHundreds(UTILS.MetersToFeet(cloudbase)) - local CLOUDCEIL1000, CLOUDCEIL0100=self:_GetThousandsAndHundreds(UTILS.MetersToFeet(cloudceil)) + local CLOUDBASE1000, CLOUDBASE0100 = self:_GetThousandsAndHundreds( UTILS.MetersToFeet( cloudbase ) ) + local CLOUDCEIL1000, CLOUDCEIL0100 = self:_GetThousandsAndHundreds( UTILS.MetersToFeet( cloudceil ) ) if self.metric then - CLOUDBASE1000, CLOUDBASE0100=self:_GetThousandsAndHundreds(cloudbase) - CLOUDCEIL1000, CLOUDCEIL0100=self:_GetThousandsAndHundreds(cloudceil) + CLOUDBASE1000, CLOUDBASE0100 = self:_GetThousandsAndHundreds( cloudbase ) + CLOUDCEIL1000, CLOUDCEIL0100 = self:_GetThousandsAndHundreds( cloudceil ) end -- No cloud info for dynamic weather. - local CloudCover={} --#ATIS.Soundfile - CloudCover=ATIS.Sound.CloudsNotAvailable - local CLOUDSsub="Cloud coverage information not available" + local CloudCover = {} -- #ATIS.Soundfile + CloudCover = ATIS.Sound.CloudsNotAvailable + local CLOUDSsub = "Cloud coverage information not available" -- Only valid for static weather. if static then - if clouddens>=9 then + if clouddens >= 9 then -- Overcast 9,10 - CloudCover=ATIS.Sound.CloudsOvercast - CLOUDSsub="Overcast" - elseif clouddens>=7 then + CloudCover = ATIS.Sound.CloudsOvercast + CLOUDSsub = "Overcast" + elseif clouddens >= 7 then -- Broken 7,8 - CloudCover=ATIS.Sound.CloudsBroken - CLOUDSsub="Broken clouds" - elseif clouddens>=4 then + CloudCover = ATIS.Sound.CloudsBroken + CLOUDSsub = "Broken clouds" + elseif clouddens >= 4 then -- Scattered 4,5,6 - CloudCover=ATIS.Sound.CloudsScattered - CLOUDSsub="Scattered clouds" - elseif clouddens>=1 then + CloudCover = ATIS.Sound.CloudsScattered + CLOUDSsub = "Scattered clouds" + elseif clouddens >= 1 then -- Few 1,2,3 - CloudCover=ATIS.Sound.CloudsFew - CLOUDSsub="Few clouds" + CloudCover = ATIS.Sound.CloudsFew + CLOUDSsub = "Few clouds" else -- No clouds - CLOUDBASE=nil - CLOUDCEIL=nil - CloudCover=ATIS.Sound.CloudsNo - CLOUDSsub="No clouds" + CLOUDBASE = nil + CLOUDCEIL = nil + CloudCover = ATIS.Sound.CloudsNo + CLOUDSsub = "No clouds" end end @@ -1708,317 +1696,317 @@ function ATIS:onafterBroadcast(From, Event, To) -------------------- -- Subtitle - local subtitle="" + local subtitle = "" - --Airbase name - subtitle=string.format("%s", self.airbasename) - if self.airbasename:find("AFB")==nil and self.airbasename:find("Airport")==nil and self.airbasename:find("Airstrip")==nil and self.airbasename:find("airfield")==nil and self.airbasename:find("AB")==nil then - subtitle=subtitle.." Airport" + -- Airbase name + subtitle = string.format( "%s", self.airbasename ) + if self.airbasename:find( "AFB" ) == nil and self.airbasename:find( "Airport" ) == nil and self.airbasename:find( "Airstrip" ) == nil and self.airbasename:find( "airfield" ) == nil and self.airbasename:find( "AB" ) == nil then + subtitle = subtitle .. " Airport" end if not self.useSRS then - self.radioqueue:NewTransmission(string.format("%s/%s.ogg", self.theatre, self.airbasename), 3.0, self.soundpath, nil, nil, subtitle, self.subduration) + self.radioqueue:NewTransmission( string.format( "%s/%s.ogg", self.theatre, self.airbasename ), 3.0, self.soundpath, nil, nil, subtitle, self.subduration ) end - local alltext=subtitle + local alltext = subtitle -- Information tag - subtitle=string.format("Information %s", NATO) - local _INFORMATION=subtitle + subtitle = string.format( "Information %s", NATO ) + local _INFORMATION = subtitle if not self.useSRS then - self:Transmission(ATIS.Sound.Information, 0.5, subtitle) - self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) + self:Transmission( ATIS.Sound.Information, 0.5, subtitle ) + self.radioqueue:NewTransmission( string.format( "NATO Alphabet/%s.ogg", NATO ), 0.75, self.soundpath ) end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle -- Zulu Time - subtitle=string.format("%s Zulu", ZULU) + subtitle = string.format( "%s Zulu", ZULU ) if not self.useSRS then - self.radioqueue:Number2Transmission(ZULU, nil, 0.5) - self:Transmission(ATIS.Sound.Zulu, 0.2, subtitle) + self.radioqueue:Number2Transmission( ZULU, nil, 0.5 ) + self:Transmission( ATIS.Sound.Zulu, 0.2, subtitle ) end - alltext=alltext..";\n"..subtitle - + alltext = alltext .. ";\n" .. subtitle + if not self.zulutimeonly then -- Sunrise Time - subtitle=string.format("Sunrise at %s local time", SUNRISE) + subtitle = string.format( "Sunrise at %s local time", SUNRISE ) if not self.useSRS then - self:Transmission(ATIS.Sound.SunriseAt, 0.5, subtitle) - self.radioqueue:Number2Transmission(SUNRISE, nil, 0.2) - self:Transmission(ATIS.Sound.TimeLocal, 0.2) + self:Transmission( ATIS.Sound.SunriseAt, 0.5, subtitle ) + self.radioqueue:Number2Transmission( SUNRISE, nil, 0.2 ) + self:Transmission( ATIS.Sound.TimeLocal, 0.2 ) end - alltext=alltext..";\n"..subtitle - + alltext = alltext .. ";\n" .. subtitle + -- Sunset Time - subtitle=string.format("Sunset at %s local time", SUNSET) + subtitle = string.format( "Sunset at %s local time", SUNSET ) if not self.useSRS then - self:Transmission(ATIS.Sound.SunsetAt, 0.5, subtitle) - self.radioqueue:Number2Transmission(SUNSET, nil, 0.5) - self:Transmission(ATIS.Sound.TimeLocal, 0.2) + self:Transmission( ATIS.Sound.SunsetAt, 0.5, subtitle ) + self.radioqueue:Number2Transmission( SUNSET, nil, 0.5 ) + self:Transmission( ATIS.Sound.TimeLocal, 0.2 ) end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end - + -- Wind -- Adding a space after each digit of WINDFROM to convert this to aviation-speak for TTS via SRS if self.useSRS then WINDFROM = string.gsub(WINDFROM,".", "%1 ") end if self.metric then - subtitle=string.format("Wind from %s at %s m/s", WINDFROM, WINDSPEED) + subtitle = string.format( "Wind from %s at %s m/s", WINDFROM, WINDSPEED ) else - subtitle=string.format("Wind from %s at %s knots", WINDFROM, WINDSPEED) + subtitle = string.format( "Wind from %s at %s knots", WINDFROM, WINDSPEED ) end - if turbulence>0 then - subtitle=subtitle..", gusting" + if turbulence > 0 then + subtitle = subtitle .. ", gusting" end - local _WIND=subtitle + local _WIND = subtitle if not self.useSRS then - self:Transmission(ATIS.Sound.WindFrom, 1.0, subtitle) - self.radioqueue:Number2Transmission(WINDFROM) - self:Transmission(ATIS.Sound.At, 0.2) - self.radioqueue:Number2Transmission(WINDSPEED) + self:Transmission( ATIS.Sound.WindFrom, 1.0, subtitle ) + self.radioqueue:Number2Transmission( WINDFROM ) + self:Transmission( ATIS.Sound.At, 0.2 ) + self.radioqueue:Number2Transmission( WINDSPEED ) if self.metric then - self:Transmission(ATIS.Sound.MetersPerSecond, 0.2) + self:Transmission( ATIS.Sound.MetersPerSecond, 0.2 ) else - self:Transmission(ATIS.Sound.Knots, 0.2) + self:Transmission( ATIS.Sound.Knots, 0.2 ) end - if turbulence>0 then - self:Transmission(ATIS.Sound.Gusting, 0.2) + if turbulence > 0 then + self:Transmission( ATIS.Sound.Gusting, 0.2 ) end end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle -- Visibility if self.metric then - subtitle=string.format("Visibility %s km", VISIBILITY) + subtitle = string.format( "Visibility %s km", VISIBILITY ) else - subtitle=string.format("Visibility %s SM", VISIBILITY) + subtitle = string.format( "Visibility %s SM", VISIBILITY ) end if not self.useSRS then - self:Transmission(ATIS.Sound.Visibilty, 1.0, subtitle) - self.radioqueue:Number2Transmission(VISIBILITY) + self:Transmission( ATIS.Sound.Visibilty, 1.0, subtitle ) + self.radioqueue:Number2Transmission( VISIBILITY ) if self.metric then - self:Transmission(ATIS.Sound.Kilometers, 0.2) + self:Transmission( ATIS.Sound.Kilometers, 0.2 ) else - self:Transmission(ATIS.Sound.StatuteMiles, 0.2) + self:Transmission( ATIS.Sound.StatuteMiles, 0.2 ) end end - alltext=alltext..";\n"..subtitle - + alltext = alltext .. ";\n" .. subtitle + -- Weather phenomena - local wp=false - local wpsub="" - if precepitation==1 then - wp=true - wpsub=wpsub.." rain" - elseif precepitation==2 then + local wp = false + local wpsub = "" + if precepitation == 1 then + wp = true + wpsub = wpsub .. " rain" + elseif precepitation == 2 then if wp then - wpsub=wpsub.."," + wpsub = wpsub .. "," end - wpsub=wpsub.." thunderstorm" - wp=true - elseif precepitation==3 then - wpsub=wpsub.." snow" - wp=true - elseif precepitation==4 then - wpsub=wpsub.." snowstorm" - wp=true + wpsub = wpsub .. " thunderstorm" + wp = true + elseif precepitation == 3 then + wpsub = wpsub .. " snow" + wp = true + elseif precepitation == 4 then + wpsub = wpsub .. " snowstorm" + wp = true end if fog then if wp then - wpsub=wpsub.."," + wpsub = wpsub .. "," end - wpsub=wpsub.." fog" - wp=true + wpsub = wpsub .. " fog" + wp = true end if dust then if wp then - wpsub=wpsub.."," + wpsub = wpsub .. "," end - wpsub=wpsub.." dust" - wp=true + wpsub = wpsub .. " dust" + wp = true end -- Actual output if wp then - subtitle=string.format("Weather phenomena:%s", wpsub) + subtitle = string.format( "Weather phenomena:%s", wpsub ) if not self.useSRS then - self:Transmission(ATIS.Sound.WeatherPhenomena, 1.0, subtitle) - if precepitation==1 then - self:Transmission(ATIS.Sound.Rain, 0.5) - elseif precepitation==2 then - self:Transmission(ATIS.Sound.ThunderStorm, 0.5) - elseif precepitation==3 then - self:Transmission(ATIS.Sound.Snow, 0.5) - elseif precepitation==4 then - self:Transmission(ATIS.Sound.SnowStorm, 0.5) + self:Transmission( ATIS.Sound.WeatherPhenomena, 1.0, subtitle ) + if precepitation == 1 then + self:Transmission( ATIS.Sound.Rain, 0.5 ) + elseif precepitation == 2 then + self:Transmission( ATIS.Sound.ThunderStorm, 0.5 ) + elseif precepitation == 3 then + self:Transmission( ATIS.Sound.Snow, 0.5 ) + elseif precepitation == 4 then + self:Transmission( ATIS.Sound.SnowStorm, 0.5 ) end if fog then - self:Transmission(ATIS.Sound.Fog, 0.5) + self:Transmission( ATIS.Sound.Fog, 0.5 ) end if dust then - self:Transmission(ATIS.Sound.Dust, 0.5) + self:Transmission( ATIS.Sound.Dust, 0.5 ) end end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end -- Cloud base if not self.useSRS then - self:Transmission(CloudCover, 1.0, CLOUDSsub) + self:Transmission( CloudCover, 1.0, CLOUDSsub ) end if CLOUDBASE and static then -- Base - local cbase=tostring(tonumber(CLOUDBASE1000)*1000+tonumber(CLOUDBASE0100)*100) - local cceil=tostring(tonumber(CLOUDCEIL1000)*1000+tonumber(CLOUDCEIL0100)*100) + local cbase = tostring( tonumber( CLOUDBASE1000 ) * 1000 + tonumber( CLOUDBASE0100 ) * 100 ) + local cceil = tostring( tonumber( CLOUDCEIL1000 ) * 1000 + tonumber( CLOUDCEIL0100 ) * 100 ) if self.metric then - --subtitle=string.format("Cloud base %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL) - subtitle=string.format("Cloud base %s, ceiling %s meters", cbase, cceil) + -- subtitle=string.format("Cloud base %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL) + subtitle = string.format( "Cloud base %s, ceiling %s meters", cbase, cceil ) else - --subtitle=string.format("Cloud base %s, ceiling %s feet", CLOUDBASE, CLOUDCEIL) - subtitle=string.format("Cloud base %s, ceiling %s feet", cbase, cceil) + -- subtitle=string.format("Cloud base %s, ceiling %s feet", CLOUDBASE, CLOUDCEIL) + subtitle = string.format( "Cloud base %s, ceiling %s feet", cbase, cceil ) end if not self.useSRS then - self:Transmission(ATIS.Sound.CloudBase, 1.0, subtitle) - if tonumber(CLOUDBASE1000)>0 then - self.radioqueue:Number2Transmission(CLOUDBASE1000) - self:Transmission(ATIS.Sound.Thousand, 0.1) + self:Transmission( ATIS.Sound.CloudBase, 1.0, subtitle ) + if tonumber( CLOUDBASE1000 ) > 0 then + self.radioqueue:Number2Transmission( CLOUDBASE1000 ) + self:Transmission( ATIS.Sound.Thousand, 0.1 ) end - if tonumber(CLOUDBASE0100)>0 then - self.radioqueue:Number2Transmission(CLOUDBASE0100) - self:Transmission(ATIS.Sound.Hundred, 0.1) + if tonumber( CLOUDBASE0100 ) > 0 then + self.radioqueue:Number2Transmission( CLOUDBASE0100 ) + self:Transmission( ATIS.Sound.Hundred, 0.1 ) end -- Ceiling - self:Transmission(ATIS.Sound.CloudCeiling, 0.5) - if tonumber(CLOUDCEIL1000)>0 then - self.radioqueue:Number2Transmission(CLOUDCEIL1000) - self:Transmission(ATIS.Sound.Thousand, 0.1) + self:Transmission( ATIS.Sound.CloudCeiling, 0.5 ) + if tonumber( CLOUDCEIL1000 ) > 0 then + self.radioqueue:Number2Transmission( CLOUDCEIL1000 ) + self:Transmission( ATIS.Sound.Thousand, 0.1 ) end - if tonumber(CLOUDCEIL0100)>0 then - self.radioqueue:Number2Transmission(CLOUDCEIL0100) - self:Transmission(ATIS.Sound.Hundred, 0.1) + if tonumber( CLOUDCEIL0100 ) > 0 then + self.radioqueue:Number2Transmission( CLOUDCEIL0100 ) + self:Transmission( ATIS.Sound.Hundred, 0.1 ) end if self.metric then - self:Transmission(ATIS.Sound.Meters, 0.1) + self:Transmission( ATIS.Sound.Meters, 0.1 ) else - self:Transmission(ATIS.Sound.Feet, 0.1) + self:Transmission( ATIS.Sound.Feet, 0.1 ) end end end - alltext=alltext..";\n"..subtitle - + alltext = alltext .. ";\n" .. subtitle + -- Temperature if self.TDegF then - if temperature<0 then - subtitle=string.format("Temperature -%s °F", TEMPERATURE) + if temperature < 0 then + subtitle = string.format( "Temperature -%s °F", TEMPERATURE ) else - subtitle=string.format("Temperature %s °F", TEMPERATURE) + subtitle = string.format( "Temperature %s °F", TEMPERATURE ) end else - if temperature<0 then - subtitle=string.format("Temperature -%s °C", TEMPERATURE) + if temperature < 0 then + subtitle = string.format( "Temperature -%s °C", TEMPERATURE ) else - subtitle=string.format("Temperature %s °C", TEMPERATURE) + subtitle = string.format( "Temperature %s °C", TEMPERATURE ) end end - local _TEMPERATURE=subtitle + local _TEMPERATURE = subtitle if not self.useSRS then - self:Transmission(ATIS.Sound.Temperature, 1.0, subtitle) - if temperature<0 then - self:Transmission(ATIS.Sound.Minus, 0.2) + self:Transmission( ATIS.Sound.Temperature, 1.0, subtitle ) + if temperature < 0 then + self:Transmission( ATIS.Sound.Minus, 0.2 ) end - self.radioqueue:Number2Transmission(TEMPERATURE) + self.radioqueue:Number2Transmission( TEMPERATURE ) if self.TDegF then - self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2) + self:Transmission( ATIS.Sound.DegreesFahrenheit, 0.2 ) else - self:Transmission(ATIS.Sound.DegreesCelsius, 0.2) + self:Transmission( ATIS.Sound.DegreesCelsius, 0.2 ) end end - alltext=alltext..";\n"..subtitle - + alltext = alltext .. ";\n" .. subtitle + -- Dew point if self.TDegF then - if dewpoint<0 then - subtitle=string.format("Dew point -%s °F", DEWPOINT) + if dewpoint < 0 then + subtitle = string.format( "Dew point -%s °F", DEWPOINT ) else - subtitle=string.format("Dew point %s °F", DEWPOINT) + subtitle = string.format( "Dew point %s °F", DEWPOINT ) end else - if dewpoint<0 then - subtitle=string.format("Dew point -%s °C", DEWPOINT) + if dewpoint < 0 then + subtitle = string.format( "Dew point -%s °C", DEWPOINT ) else - subtitle=string.format("Dew point %s °C", DEWPOINT) + subtitle = string.format( "Dew point %s °C", DEWPOINT ) end end - local _DEWPOINT=subtitle + local _DEWPOINT = subtitle if not self.useSRS then - self:Transmission(ATIS.Sound.DewPoint, 1.0, subtitle) - if dewpoint<0 then - self:Transmission(ATIS.Sound.Minus, 0.2) + self:Transmission( ATIS.Sound.DewPoint, 1.0, subtitle ) + if dewpoint < 0 then + self:Transmission( ATIS.Sound.Minus, 0.2 ) end - self.radioqueue:Number2Transmission(DEWPOINT) + self.radioqueue:Number2Transmission( DEWPOINT ) if self.TDegF then - self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2) + self:Transmission( ATIS.Sound.DegreesFahrenheit, 0.2 ) else - self:Transmission(ATIS.Sound.DegreesCelsius, 0.2) + self:Transmission( ATIS.Sound.DegreesCelsius, 0.2 ) end end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle -- Altimeter QNH/QFE. if self.PmmHg then if self.qnhonly then - subtitle=string.format("Altimeter %s.%s mmHg", QNH[1], QNH[2]) + subtitle = string.format( "Altimeter %s.%s mmHg", QNH[1], QNH[2] ) else - subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s mmHg", QNH[1], QNH[2], QFE[1], QFE[2]) + subtitle = string.format( "Altimeter: QNH %s.%s, QFE %s.%s mmHg", QNH[1], QNH[2], QFE[1], QFE[2] ) end else if self.metric then if self.qnhonly then - subtitle=string.format("Altimeter %s.%s hPa", QNH[1], QNH[2]) + subtitle = string.format( "Altimeter %s.%s hPa", QNH[1], QNH[2] ) else - subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s hPa", QNH[1], QNH[2], QFE[1], QFE[2]) + subtitle = string.format( "Altimeter: QNH %s.%s, QFE %s.%s hPa", QNH[1], QNH[2], QFE[1], QFE[2] ) end else if self.qnhonly then - subtitle=string.format("Altimeter %s.%s inHg", QNH[1], QNH[2]) + subtitle = string.format( "Altimeter %s.%s inHg", QNH[1], QNH[2] ) else - subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s inHg", QNH[1], QNH[2], QFE[1], QFE[2]) + subtitle = string.format( "Altimeter: QNH %s.%s, QFE %s.%s inHg", QNH[1], QNH[2], QFE[1], QFE[2] ) end end end - local _ALTIMETER=subtitle + local _ALTIMETER = subtitle if not self.useSRS then - self:Transmission(ATIS.Sound.Altimeter, 1.0, subtitle) + self:Transmission( ATIS.Sound.Altimeter, 1.0, subtitle ) if not self.qnhonly then - self:Transmission(ATIS.Sound.QNH, 0.5) + self:Transmission( ATIS.Sound.QNH, 0.5 ) end - self.radioqueue:Number2Transmission(QNH[1]) + self.radioqueue:Number2Transmission( QNH[1] ) if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then - self:Transmission(ATIS.Sound.Decimal, 0.2) + self:Transmission( ATIS.Sound.Decimal, 0.2 ) end - self.radioqueue:Number2Transmission(QNH[2]) - + self.radioqueue:Number2Transmission( QNH[2] ) + if not self.qnhonly then - self:Transmission(ATIS.Sound.QFE, 0.75) - self.radioqueue:Number2Transmission(QFE[1]) - if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then - self:Transmission(ATIS.Sound.Decimal, 0.2) - end - self.radioqueue:Number2Transmission(QFE[2]) - end - + self:Transmission( ATIS.Sound.QFE, 0.75 ) + self.radioqueue:Number2Transmission( QFE[1] ) + if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then + self:Transmission( ATIS.Sound.Decimal, 0.2 ) + end + self.radioqueue:Number2Transmission( QFE[2] ) + end + if self.PmmHg then - self:Transmission(ATIS.Sound.MillimetersOfMercury, 0.1) + self:Transmission( ATIS.Sound.MillimetersOfMercury, 0.1 ) else if self.metric then - self:Transmission(ATIS.Sound.HectoPascal, 0.1) + self:Transmission( ATIS.Sound.HectoPascal, 0.1 ) else - self:Transmission(ATIS.Sound.InchesOfMercury, 0.1) - end + self:Transmission( ATIS.Sound.InchesOfMercury, 0.1 ) + end end end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle -- Active runway. local subtitle=string.format("Active runway %s", runwayLanding) @@ -2027,7 +2015,7 @@ function ATIS:onafterBroadcast(From, Event, To) elseif rwyLandingLeft==false then subtitle=subtitle.." Right" end - local _RUNACT=subtitle + local _RUNACT = subtitle if not self.useSRS then self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle) self.radioqueue:Number2Transmission(runwayLanding) @@ -2037,233 +2025,233 @@ function ATIS:onafterBroadcast(From, Event, To) self:Transmission(ATIS.Sound.Right, 0.2) end end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle -- Runway length. if self.rwylength then - local runact=self.airbase:GetActiveRunway(self.runwaym2t) - local length=runact.length + local runact = self.airbase:GetActiveRunway( self.runwaym2t ) + local length = runact.length if not self.metric then - length=UTILS.MetersToFeet(length) + length = UTILS.MetersToFeet( length ) end -- Length in thousands and hundrets of ft/meters. - local L1000, L0100=self:_GetThousandsAndHundreds(length) + local L1000, L0100 = self:_GetThousandsAndHundreds( length ) -- Subtitle. - local subtitle=string.format("Runway length %d", length) + local subtitle = string.format( "Runway length %d", length ) if self.metric then - subtitle=subtitle.." meters" + subtitle = subtitle .. " meters" else - subtitle=subtitle.." feet" + subtitle = subtitle .. " feet" end -- Transmit. if not self.useSRS then - self:Transmission(ATIS.Sound.RunwayLength, 1.0, subtitle) - if tonumber(L1000)>0 then - self.radioqueue:Number2Transmission(L1000) - self:Transmission(ATIS.Sound.Thousand, 0.1) + self:Transmission( ATIS.Sound.RunwayLength, 1.0, subtitle ) + if tonumber( L1000 ) > 0 then + self.radioqueue:Number2Transmission( L1000 ) + self:Transmission( ATIS.Sound.Thousand, 0.1 ) end - if tonumber(L0100)>0 then - self.radioqueue:Number2Transmission(L0100) - self:Transmission(ATIS.Sound.Hundred, 0.1) + if tonumber( L0100 ) > 0 then + self.radioqueue:Number2Transmission( L0100 ) + self:Transmission( ATIS.Sound.Hundred, 0.1 ) end if self.metric then - self:Transmission(ATIS.Sound.Meters, 0.1) + self:Transmission( ATIS.Sound.Meters, 0.1 ) else - self:Transmission(ATIS.Sound.Feet, 0.1) + self:Transmission( ATIS.Sound.Feet, 0.1 ) end end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end -- Airfield elevation if self.elevation then - local elevation=self.airbase:GetHeight() + local elevation = self.airbase:GetHeight() if not self.metric then - elevation=UTILS.MetersToFeet(elevation) + elevation = UTILS.MetersToFeet( elevation ) end -- Length in thousands and hundrets of ft/meters. - local L1000, L0100=self:_GetThousandsAndHundreds(elevation) + local L1000, L0100 = self:_GetThousandsAndHundreds( elevation ) -- Subtitle. - local subtitle=string.format("Elevation %d", elevation) + local subtitle = string.format( "Elevation %d", elevation ) if self.metric then - subtitle=subtitle.." meters" + subtitle = subtitle .. " meters" else - subtitle=subtitle.." feet" + subtitle = subtitle .. " feet" end -- Transmit. if not self.useSRS then - self:Transmission(ATIS.Sound.Elevation, 1.0, subtitle) - if tonumber(L1000)>0 then - self.radioqueue:Number2Transmission(L1000) - self:Transmission(ATIS.Sound.Thousand, 0.1) + self:Transmission( ATIS.Sound.Elevation, 1.0, subtitle ) + if tonumber( L1000 ) > 0 then + self.radioqueue:Number2Transmission( L1000 ) + self:Transmission( ATIS.Sound.Thousand, 0.1 ) end - if tonumber(L0100)>0 then - self.radioqueue:Number2Transmission(L0100) - self:Transmission(ATIS.Sound.Hundred, 0.1) + if tonumber( L0100 ) > 0 then + self.radioqueue:Number2Transmission( L0100 ) + self:Transmission( ATIS.Sound.Hundred, 0.1 ) end if self.metric then - self:Transmission(ATIS.Sound.Meters, 0.1) + self:Transmission( ATIS.Sound.Meters, 0.1 ) else - self:Transmission(ATIS.Sound.Feet, 0.1) + self:Transmission( ATIS.Sound.Feet, 0.1 ) end end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end -- Tower frequency. if self.towerfrequency then - local freqs="" - for i,freq in pairs(self.towerfrequency) do - freqs=freqs..string.format("%.3f MHz", freq) - if i<#self.towerfrequency then - freqs=freqs..", " + local freqs = "" + for i, freq in pairs( self.towerfrequency ) do + freqs = freqs .. string.format( "%.3f MHz", freq ) + if i < #self.towerfrequency then + freqs = freqs .. ", " end end - subtitle=string.format("Tower frequency %s", freqs) + subtitle = string.format( "Tower frequency %s", freqs ) if not self.useSRS then - self:Transmission(ATIS.Sound.TowerFrequency, 1.0, subtitle) - for _,freq in pairs(self.towerfrequency) do - local f=string.format("%.3f", freq) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) + self:Transmission( ATIS.Sound.TowerFrequency, 1.0, subtitle ) + for _, freq in pairs( self.towerfrequency ) do + local f = string.format( "%.3f", freq ) + f = UTILS.Split( f, "." ) + self.radioqueue:Number2Transmission( f[1], nil, 0.5 ) + if tonumber( f[2] ) > 0 then + self:Transmission( ATIS.Sound.Decimal, 0.2 ) + self.radioqueue:Number2Transmission( f[2] ) end - self:Transmission(ATIS.Sound.MegaHertz, 0.2) + self:Transmission( ATIS.Sound.MegaHertz, 0.2 ) end end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end -- ILS local ils=self:GetNavPoint(self.ils, runwayLanding, rwyLandingLeft) if ils then - subtitle=string.format("ILS frequency %.2f MHz", ils.frequency) - if not self.useSRS then - self:Transmission(ATIS.Sound.ILSFrequency, 1.0, subtitle) - local f=string.format("%.2f", ils.frequency) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) + subtitle = string.format( "ILS frequency %.2f MHz", ils.frequency ) + if not self.useSRS then + self:Transmission( ATIS.Sound.ILSFrequency, 1.0, subtitle ) + local f = string.format( "%.2f", ils.frequency ) + f = UTILS.Split( f, "." ) + self.radioqueue:Number2Transmission( f[1], nil, 0.5 ) + if tonumber( f[2] ) > 0 then + self:Transmission( ATIS.Sound.Decimal, 0.2 ) + self.radioqueue:Number2Transmission( f[2] ) end - self:Transmission(ATIS.Sound.MegaHertz, 0.2) - end - alltext=alltext..";\n"..subtitle + self:Transmission( ATIS.Sound.MegaHertz, 0.2 ) + end + alltext = alltext .. ";\n" .. subtitle end -- Outer NDB local ndb=self:GetNavPoint(self.ndbouter, runwayLanding, rwyLandingLeft) if ndb then - subtitle=string.format("Outer NDB frequency %.2f MHz", ndb.frequency) + subtitle = string.format( "Outer NDB frequency %.2f MHz", ndb.frequency ) if not self.useSRS then - self:Transmission(ATIS.Sound.OuterNDBFrequency, 1.0, subtitle) - local f=string.format("%.2f", ndb.frequency) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) + self:Transmission( ATIS.Sound.OuterNDBFrequency, 1.0, subtitle ) + local f = string.format( "%.2f", ndb.frequency ) + f = UTILS.Split( f, "." ) + self.radioqueue:Number2Transmission( f[1], nil, 0.5 ) + if tonumber( f[2] ) > 0 then + self:Transmission( ATIS.Sound.Decimal, 0.2 ) + self.radioqueue:Number2Transmission( f[2] ) end - self:Transmission(ATIS.Sound.MegaHertz, 0.2) + self:Transmission( ATIS.Sound.MegaHertz, 0.2 ) end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end -- Inner NDB local ndb=self:GetNavPoint(self.ndbinner, runwayLanding, rwyLandingLeft) if ndb then - subtitle=string.format("Inner NDB frequency %.2f MHz", ndb.frequency) + subtitle = string.format( "Inner NDB frequency %.2f MHz", ndb.frequency ) if not self.useSRS then - self:Transmission(ATIS.Sound.InnerNDBFrequency, 1.0, subtitle) - local f=string.format("%.2f", ndb.frequency) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) + self:Transmission( ATIS.Sound.InnerNDBFrequency, 1.0, subtitle ) + local f = string.format( "%.2f", ndb.frequency ) + f = UTILS.Split( f, "." ) + self.radioqueue:Number2Transmission( f[1], nil, 0.5 ) + if tonumber( f[2] ) > 0 then + self:Transmission( ATIS.Sound.Decimal, 0.2 ) + self.radioqueue:Number2Transmission( f[2] ) end - self:Transmission(ATIS.Sound.MegaHertz, 0.2) - end - alltext=alltext..";\n"..subtitle + self:Transmission( ATIS.Sound.MegaHertz, 0.2 ) + end + alltext = alltext .. ";\n" .. subtitle end -- VOR if self.vor then - subtitle=string.format("VOR frequency %.2f MHz", self.vor) + subtitle = string.format( "VOR frequency %.2f MHz", self.vor ) if self.useSRS then - subtitle=string.format("V O R frequency %.2f MHz", self.vor) + subtitle = string.format( "V O R frequency %.2f MHz", self.vor ) end if not self.useSRS then - self:Transmission(ATIS.Sound.VORFrequency, 1.0, subtitle) - local f=string.format("%.2f", self.vor) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) + self:Transmission( ATIS.Sound.VORFrequency, 1.0, subtitle ) + local f = string.format( "%.2f", self.vor ) + f = UTILS.Split( f, "." ) + self.radioqueue:Number2Transmission( f[1], nil, 0.5 ) + if tonumber( f[2] ) > 0 then + self:Transmission( ATIS.Sound.Decimal, 0.2 ) + self.radioqueue:Number2Transmission( f[2] ) end - self:Transmission(ATIS.Sound.MegaHertz, 0.2) + self:Transmission( ATIS.Sound.MegaHertz, 0.2 ) end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end -- TACAN if self.tacan then subtitle=string.format("TACAN channel %dX Ray", self.tacan) if not self.useSRS then - self:Transmission(ATIS.Sound.TACANChannel, 1.0, subtitle) - self.radioqueue:Number2Transmission(tostring(self.tacan), nil, 0.2) - self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2) + self:Transmission( ATIS.Sound.TACANChannel, 1.0, subtitle ) + self.radioqueue:Number2Transmission( tostring( self.tacan ), nil, 0.2 ) + self.radioqueue:NewTransmission( "NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2 ) end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end -- RSBN if self.rsbn then - subtitle=string.format("RSBN channel %d", self.rsbn) + subtitle = string.format( "RSBN channel %d", self.rsbn ) if not self.useSRS then - self:Transmission(ATIS.Sound.RSBNChannel, 1.0, subtitle) - self.radioqueue:Number2Transmission(tostring(self.rsbn), nil, 0.2) + self:Transmission( ATIS.Sound.RSBNChannel, 1.0, subtitle ) + self.radioqueue:Number2Transmission( tostring( self.rsbn ), nil, 0.2 ) end - alltext=alltext..";\n"..subtitle + alltext = alltext .. ";\n" .. subtitle end -- PRMG local ndb=self:GetNavPoint(self.prmg, runwayLanding, rwyLandingLeft) if ndb then - subtitle=string.format("PRMG channel %d", ndb.frequency) + subtitle = string.format( "PRMG channel %d", ndb.frequency ) if not self.useSRS then - self:Transmission(ATIS.Sound.PRMGChannel, 1.0, subtitle) - self.radioqueue:Number2Transmission(tostring(ndb.frequency), nil, 0.5) - end - alltext=alltext..";\n"..subtitle + self:Transmission( ATIS.Sound.PRMGChannel, 1.0, subtitle ) + self.radioqueue:Number2Transmission( tostring( ndb.frequency ), nil, 0.5 ) + end + alltext = alltext .. ";\n" .. subtitle end - + -- Advice on initial... - subtitle=string.format("Advise on initial contact, you have information %s", NATO) + subtitle = string.format( "Advise on initial contact, you have information %s", NATO ) if not self.useSRS then - self:Transmission(ATIS.Sound.AdviceOnInitial, 0.5, subtitle) - self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) - end - alltext=alltext..";\n"..subtitle - + self:Transmission( ATIS.Sound.AdviceOnInitial, 0.5, subtitle ) + self.radioqueue:NewTransmission( string.format( "NATO Alphabet/%s.ogg", NATO ), 0.75, self.soundpath ) + end + alltext = alltext .. ";\n" .. subtitle + -- Report ATIS text. - self:Report(alltext) + self:Report( alltext ) -- Update F10 marker. if self.usemarker then - self:UpdateMarker(_INFORMATION, _RUNACT, _WIND, _ALTIMETER, _TEMPERATURE) + self:UpdateMarker( _INFORMATION, _RUNACT, _WIND, _ALTIMETER, _TEMPERATURE ) end end @@ -2274,34 +2262,34 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #string Text Report text. -function ATIS:onafterReport(From, Event, To, Text) - self:T(self.lid..string.format("Report:\n%s", Text)) - +function ATIS:onafterReport( From, Event, To, Text ) + self:T( self.lid .. string.format( "Report:\n%s", Text ) ) + if self.useSRS and self.msrs then - + -- Remove line breaks - local text=string.gsub(Text, "[\r\n]", "") - + local text = string.gsub( Text, "[\r\n]", "" ) + -- Replace other stuff. - local text=string.gsub(text, "SM", "statute miles") - local text=string.gsub(text, "°C", "degrees Celsius") - local text=string.gsub(text, "°F", "degrees Fahrenheit") - local text=string.gsub(text, "inHg", "inches of Mercury") - local text=string.gsub(text, "mmHg", "millimeters of Mercury") - local text=string.gsub(text, "hPa", "hecto Pascals") - local text=string.gsub(text, "m/s", "meters per second") - + local text = string.gsub( text, "SM", "statute miles" ) + local text = string.gsub( text, "°C", "degrees Celsius" ) + local text = string.gsub( text, "°F", "degrees Fahrenheit" ) + local text = string.gsub( text, "inHg", "inches of Mercury" ) + local text = string.gsub( text, "mmHg", "millimeters of Mercury" ) + local text = string.gsub( text, "hPa", "hectopascals" ) + local text = string.gsub( text, "m/s", "meters per second" ) + -- Replace ";" by "." - local text=string.gsub(text, ";", " . ") - - --Debug output. - self:T("SRS TTS: "..text) - + local text = string.gsub( text, ";", " . " ) + + -- Debug output. + self:T( "SRS TTS: " .. text ) + -- Play text-to-speech report. - self.msrs:PlayText(text) - + self.msrs:PlayText( text ) + end - + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2311,25 +2299,25 @@ end --- Base captured -- @param #ATIS self -- @param Core.Event#EVENTDATA EventData Event data. -function ATIS:OnEventBaseCaptured(EventData) - +function ATIS:OnEventBaseCaptured( EventData ) + if EventData and EventData.Place then -- Place is the airbase that was captured. - local airbase=EventData.Place --Wrapper.Airbase#AIRBASE + local airbase = EventData.Place -- Wrapper.Airbase#AIRBASE -- Check that this airbase belongs or did belong to this warehouse. - if EventData.PlaceName==self.airbasename then + if EventData.PlaceName == self.airbasename then -- New coalition of airbase after it was captured. - local NewCoalitionAirbase=airbase:GetCoalition() - - if self.useSRS and self.msrs and self.msrs.coalition~=NewCoalitionAirbase then - self.msrs:SetCoalition(NewCoalitionAirbase) + local NewCoalitionAirbase = airbase:GetCoalition() + + if self.useSRS and self.msrs and self.msrs.coalition ~= NewCoalitionAirbase then + self.msrs:SetCoalition( NewCoalitionAirbase ) end - + end - + end end @@ -2346,21 +2334,21 @@ end -- @param #string altimeter Altimeter text. -- @param #string temperature Temperature text. -- @return #number Marker ID. -function ATIS:UpdateMarker(information, runact, wind, altimeter, temperature) +function ATIS:UpdateMarker( information, runact, wind, altimeter, temperature ) if self.markerid then - self.airbase:GetCoordinate():RemoveMark(self.markerid) + self.airbase:GetCoordinate():RemoveMark( self.markerid ) end - local text=string.format("ATIS on %.3f %s, %s:\n", self.frequency, UTILS.GetModulationName(self.modulation), tostring(information)) - text=text..string.format("%s\n", tostring(runact)) - text=text..string.format("%s\n", tostring(wind)) - text=text..string.format("%s\n", tostring(altimeter)) - text=text..string.format("%s", tostring(temperature)) + local text = string.format( "ATIS on %.3f %s, %s:\n", self.frequency, UTILS.GetModulationName( self.modulation ), tostring( information ) ) + text = text .. string.format( "%s\n", tostring( runact ) ) + text = text .. string.format( "%s\n", tostring( wind ) ) + text = text .. string.format( "%s\n", tostring( altimeter ) ) + text = text .. string.format( "%s", tostring( temperature ) ) -- More info is not displayed on the marker! -- Place new mark - self.markerid=self.airbase:GetCoordinate():MarkToAll(text, true) + self.markerid = self.airbase:GetCoordinate():MarkToAll( text, true ) return self.markerid end @@ -2386,18 +2374,18 @@ end -- @param #ATIS self -- @param #number windfrom Wind direction (from) in degrees. -- @return #string Runway magnetic heading divided by ten (and rounded). Eg, "13" for 130°. -function ATIS:GetMagneticRunway(windfrom) +function ATIS:GetMagneticRunway( windfrom ) - local diffmin=nil - local runway=nil - for _,heading in pairs(self.runwaymag) do + local diffmin = nil + local runway = nil + for _, heading in pairs( self.runwaymag ) do - local hdg=self:GetRunwayWithoutLR(heading) + local hdg = self:GetRunwayWithoutLR( heading ) - local diff=UTILS.HdgDiff(windfrom, tonumber(hdg)*10) - if diffmin==nil or diff data is valid for all runways. return nav else - local navy=tonumber(self:GetRunwayWithoutLR(nav.runway))*10 - local rwyy=tonumber(self:GetRunwayWithoutLR(runway))*10 + local navy = tonumber( self:GetRunwayWithoutLR( nav.runway ) ) * 10 + local rwyy = tonumber( self:GetRunwayWithoutLR( runway ) ) * 10 - local navL=self:GetRunwayLR(nav.runway) - local hdgD=UTILS.HdgDiff(navy,rwyy) + local navL = self:GetRunwayLR( nav.runway ) + local hdgD = UTILS.HdgDiff( navy, rwyy ) - if hdgD<=15 then --We allow an error of +-15° here. - if navL==nil or (navL==true and left==true) or (navL==false and left==false) then + if hdgD <= 15 then -- We allow an error of +-15° here. + if navL == nil or (navL == true and left == true) or (navL == false and left == false) then return nav end end @@ -2443,9 +2431,9 @@ end -- @param #ATIS self -- @param #string runway Runway heading, *e.g.* "31L". -- @return #string Runway heading without left or right, *e.g.* "31". -function ATIS:GetRunwayWithoutLR(runway) - local rwywo=runway:gsub("%D+", "") - --self:I(string.format("FF runway=%s ==> rwywo=%s", runway, rwywo)) +function ATIS:GetRunwayWithoutLR( runway ) + local rwywo = runway:gsub( "%D+", "" ) + -- self:I(string.format("FF runway=%s ==> rwywo=%s", runway, rwywo)) return rwywo end @@ -2453,11 +2441,11 @@ end -- @param #ATIS self -- @param #string runway Runway heading, *e.g.* "31L". -- @return #boolean If *true*, left runway is active. If *false*, right runway. If *nil*, neither applies. -function ATIS:GetRunwayLR(runway) +function ATIS:GetRunwayLR( runway ) -- Get left/right if specified. - local rwyL=runway:lower():find("l") - local rwyR=runway:lower():find("r") + local rwyL = runway:lower():find( "l" ) + local rwyR = runway:lower():find( "r" ) if rwyL then return true @@ -2475,19 +2463,19 @@ end -- @param #number interval Interval in seconds after the last transmission finished. -- @param #string subtitle Subtitle of the transmission. -- @param #string path Path to sound file. Default self.soundpath. -function ATIS:Transmission(sound, interval, subtitle, path) - self.radioqueue:NewTransmission(sound.filename, sound.duration, path or self.soundpath, nil, interval, subtitle, self.subduration) +function ATIS:Transmission( sound, interval, subtitle, path ) + self.radioqueue:NewTransmission( sound.filename, sound.duration, path or self.soundpath, nil, interval, subtitle, self.subduration ) end --- Play all audio files. -- @param #ATIS self function ATIS:SoundCheck() - for _,_sound in pairs(ATIS.Sound) do - local sound=_sound --#ATIS.Soundfile - local subtitle=string.format("Playing sound file %s, duration %.2f sec", sound.filename, sound.duration) - self:Transmission(sound, nil, subtitle) - MESSAGE:New(subtitle, 5, "ATIS"):ToAll() + for _, _sound in pairs( ATIS.Sound ) do + local sound = _sound -- #ATIS.Soundfile + local subtitle = string.format( "Playing sound file %s, duration %.2f sec", sound.filename, sound.duration ) + self:Transmission( sound, nil, subtitle ) + MESSAGE:New( subtitle, 5, "ATIS" ):ToAll() end end @@ -2503,7 +2491,7 @@ end function ATIS:GetMissionWeather() -- Weather data from mission file. - local weather=env.mission.weather + local weather = env.mission.weather -- Clouds --[[ @@ -2515,25 +2503,25 @@ function ATIS:GetMissionWeather() ["iprecptns"] = 1, }, -- end of ["clouds"] ]] - local clouds=weather.clouds + local clouds = weather.clouds -- 0=static, 1=dynamic - local static=weather.atmosphere_type==0 + local static = weather.atmosphere_type == 0 -- Visibilty distance in meters. - local visibility=weather.visibility.distance + local visibility = weather.visibility.distance -- Ground turbulence. - local turbulence=weather.groundTurbulence + local turbulence = weather.groundTurbulence -- Dust --[[ ["enable_dust"] = false, ["dust_density"] = 0, ]] - local dust=nil - if weather.enable_dust==true then - dust=weather.dust_density + local dust = nil + if weather.enable_dust == true then + dust = weather.dust_density end -- Fog @@ -2545,35 +2533,34 @@ function ATIS:GetMissionWeather() ["visibility"] = 25, }, -- end of ["fog"] ]] - local fog=nil - if weather.enable_fog==true then - fog=weather.fog + local fog = nil + if weather.enable_fog == true then + fog = weather.fog end - self:T("FF weather:") - self:T({clouds=clouds}) - self:T({visibility=visibility}) - self:T({turbulence=turbulence}) - self:T({fog=fog}) - self:T({dust=dust}) - self:T({static=static}) + self:T( "FF weather:" ) + self:T( { clouds = clouds } ) + self:T( { visibility = visibility } ) + self:T( { turbulence = turbulence } ) + self:T( { fog = fog } ) + self:T( { dust = dust } ) + self:T( { static = static } ) return clouds, visibility, turbulence, fog, dust, static end - --- Get thousands of a number. -- @param #ATIS self -- @param #number n Number, *e.g.* 4359. -- @return #string Thousands of n, *e.g.* "4" for 4359. -- @return #string Hundreds of n, *e.g.* "4" for 4359 because its rounded. -function ATIS:_GetThousandsAndHundreds(n) +function ATIS:_GetThousandsAndHundreds( n ) - local N=UTILS.Round(n/1000, 1) + local N = UTILS.Round( n / 1000, 1 ) - local S=UTILS.Split(string.format("%.1f", N), ".") + local S = UTILS.Split( string.format( "%.1f", N ), "." ) - local t=S[1] - local h=S[2] + local t = S[1] + local h = S[2] return t, h end diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index a92ba908f..7afe36983 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -53,7 +53,7 @@ -- -- At the moment, optimized parameters are available for the F/A-18C Hornet (Lot 20) and A-4E community mod as aircraft and the USS John C. Stennis as carrier. -- --- The AV-8B Harrier, HMS Hermes, the USS Tarawa, USS America, HMAS Canberra, and Juan Carlos I are WIP. The AV-8B harrier and the LHA's and LHD can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and +-- The AV-8B Harrier, HMS Hermes, HMS Invincible, the USS Tarawa, USS America, HMAS Canberra, and Juan Carlos I are WIP. The AV-8B harrier and the LHA's and LHD can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and -- no other fixed wing aircraft (human or AI controlled) are supposed to land on these ships. Currently only Case I is supported. Case II/III take slightly different steps from the CVN carrier. -- However, if no offset is used for the holding radial this provides a very close representation of the V/STOL Case III, allowing for an approach to over the deck and a vertical landing. -- diff --git a/Moose Development/Moose/Tasking/Mission.lua b/Moose Development/Moose/Tasking/Mission.lua index f057c9648..e8da4f102 100644 --- a/Moose Development/Moose/Tasking/Mission.lua +++ b/Moose Development/Moose/Tasking/Mission.lua @@ -132,7 +132,7 @@ MISSION = { -- @param #string MissionName Name of the mission. This name will be used to reference the status of each mission by the players. -- @param #string MissionPriority String indicating the "priority" of the Mission. e.g. "Primary", "Secondary". It is free format and up to the Mission designer to choose. There are no rules behind this field. -- @param #string MissionBriefing String indicating the mission briefing to be shown when a player joins a @{CLIENT}. --- @param DCS#coaliton.side MissionCoalition Side of the coalition, i.e. and enumerator @{#DCS.coalition.side} corresponding to RED, BLUE or NEUTRAL. +-- @param DCS#coalition.side MissionCoalition Side of the coalition, i.e. and enumerator @{#DCS.coalition.side} corresponding to RED, BLUE or NEUTRAL. -- @return #MISSION self function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefing, MissionCoalition ) diff --git a/Moose Development/Moose/Tasking/Task.lua b/Moose Development/Moose/Tasking/Task.lua index 7cd79f453..f553cfc71 100644 --- a/Moose Development/Moose/Tasking/Task.lua +++ b/Moose Development/Moose/Tasking/Task.lua @@ -82,7 +82,7 @@ -- -- ![Mission](../Tasking/Report_Statistics_Progress.JPG) -- --- A statistic report on the progress of the mission. Each task achievement will increase the %-tage to 100% as a goal to complete the task. +-- A statistic report on the progress of the mission. Each task achievement will increase the % to 100% as a goal to complete the task. -- -- ## 1.3) Join a Task. -- diff --git a/Moose Development/Moose/Tasking/Task_A2A.lua b/Moose Development/Moose/Tasking/Task_A2A.lua index 2da9be005..389be7dd7 100644 --- a/Moose Development/Moose/Tasking/Task_A2A.lua +++ b/Moose Development/Moose/Tasking/Task_A2A.lua @@ -1,13 +1,13 @@ --- **Tasking** - The TASK_A2A models tasks for players in Air to Air engagements. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- --- ### Contributions: --- +-- +-- ### Contributions: +-- -- === --- +-- -- @module Tasking.Task_A2A -- @image MOOSE.JPG @@ -35,12 +35,12 @@ do -- TASK_A2A -- * @{#TASK_A2A.SetScoreOnDestroy}(): Set a score when a target in scope of the A2A attack, has been destroyed. -- * @{#TASK_A2A.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2A attack, have been destroyed. -- * @{#TASK_A2A.SetPenaltyOnFailed}(): Set a penalty when the A2A attack has failed. - -- + -- -- @field #TASK_A2A TASK_A2A = { - ClassName = "TASK_A2A", + ClassName = "TASK_A2A" } - + --- Instantiates a new TASK_A2A. -- @param #TASK_A2A self -- @param Tasking.Mission#MISSION Mission @@ -54,51 +54,49 @@ do -- TASK_A2A function TASK_A2A:New( Mission, SetAttack, TaskName, TargetSetUnit, TaskType, TaskBriefing ) local self = BASE:Inherit( self, TASK:New( Mission, SetAttack, TaskName, TaskType, TaskBriefing ) ) -- Tasking.Task#TASK_A2A self:F() - + self.TargetSetUnit = TargetSetUnit self.TaskType = TaskType local Fsm = self:GetUnitProcess() - 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: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: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( "Accounted", "DestroyedAll", "Accounted" ) --- Fsm:AddTransition( "Accounted", "Success", "Success" ) + + -- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" ) + -- Fsm:AddTransition( "Accounted", "Success", "Success" ) Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - ---- @param #FSM_PROCESS self + -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param #TASK_CARGO Task function Fsm:OnLeaveAssigned( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - + self:SelectAction() end - - --- Test + + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_A2A#TASK_A2A Task function Fsm:onafterRouteToRendezVous( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) -- Determine the first Unit from the self.RendezVousSetUnit - + if Task:GetRendezVousZone( TaskUnit ) then self:__RouteToRendezVousZone( 0.1 ) else @@ -110,36 +108,36 @@ do -- TASK_A2A end end - --- Test + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task#TASK_A2A Task function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) -- Determine the first Unit from the self.TargetSetUnit - - self:__Engage( 0.1 ) + + self:__Engage( 0.1 ) end - - --- Test + + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task#TASK_A2A Task function Fsm:onafterEngage( TaskUnit, Task ) self:F( { self } ) self:__Account( 0.1 ) - self:__RouteToTarget(0.1 ) + self:__RouteToTarget( 0.1 ) self:__RouteToTargets( -10 ) end - - --- Test + + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_A2A#TASK_A2A Task function Fsm:onafterRouteToTarget( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) -- Determine the first Unit from the self.TargetSetUnit - + if Task:GetTargetZone( TaskUnit ) then self:__RouteToTargetZone( 0.1 ) else @@ -152,8 +150,8 @@ do -- TASK_A2A self:__RouteToTargetPoint( 0.1 ) end end - - --- Test + + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_A2A#TASK_A2A Task @@ -165,20 +163,18 @@ do -- TASK_A2A end self:__RouteToTargets( -10 ) end - + return self - + end - + --- @param #TASK_A2A self -- @param Core.Set#SET_UNIT TargetSetUnit The set of targets. function TASK_A2A:SetTargetSetUnit( TargetSetUnit ) - + self.TargetSetUnit = TargetSetUnit end - - --- @param #TASK_A2A self function TASK_A2A:GetPlannedMenuText() return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" @@ -188,34 +184,32 @@ do -- TASK_A2A -- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. -- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2A:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit ) - + function TASK_A2A:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit ) + local ProcessUnit = self:GetUnitProcess( TaskUnit ) - + local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT ActRouteRendezVous:SetCoordinate( RendezVousCoordinate ) ActRouteRendezVous:SetRange( RendezVousRange ) end - + --- @param #TASK_A2A self -- @param Wrapper.Unit#UNIT TaskUnit -- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. -- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. function TASK_A2A:GetRendezVousCoordinate( TaskUnit ) - + local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange() end - - - + --- @param #TASK_A2A self -- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map. -- @param Wrapper.Unit#UNIT TaskUnit function TASK_A2A:SetRendezVousZone( RendezVousZone, TaskUnit ) - + local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE @@ -232,18 +226,17 @@ do -- TASK_A2A local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE return ActRouteRendezVous:GetZone() end - + --- @param #TASK_A2A self -- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map. -- @param Wrapper.Unit#UNIT TaskUnit function TASK_A2A:SetTargetCoordinate( TargetCoordinate, TaskUnit ) - + local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT ActRouteTarget:SetCoordinate( TargetCoordinate ) end - --- @param #TASK_A2A self -- @param Wrapper.Unit#UNIT TaskUnit @@ -256,18 +249,16 @@ do -- TASK_A2A return ActRouteTarget:GetCoordinate() end - --- @param #TASK_A2A 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_A2A:SetTargetZone( TargetZone, Altitude, Heading, TaskUnit ) - + local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE ActRouteTarget:SetZone( TargetZone, Altitude, Heading ) end - --- @param #TASK_A2A self -- @param Wrapper.Unit#UNIT TaskUnit @@ -281,45 +272,43 @@ do -- TASK_A2A end function TASK_A2A:SetGoalTotal() - + self.GoalTotal = self.TargetSetUnit:Count() end function TASK_A2A:GetGoalTotal() - + return self.GoalTotal end --- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats. -- @param #TASK_A2A self function TASK_A2A:ReportOrder( ReportGroup ) - self:UpdateTaskInfo( self.DetectedItem ) - + self:UpdateTaskInfo( self.DetectedItem ) + local Coordinate = self.TaskInfo:GetData( "Coordinate" ) local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate ) - + return Distance end - - + --- This method checks every 10 seconds if the goal has been reached of the task. -- @param #TASK_A2A self function TASK_A2A:onafterGoal( TaskUnit, From, Event, To ) local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT - + if TargetSetUnit:Count() == 0 then self:Success() end - + self:__Goal( -10 ) end - --- @param #TASK_A2A self function TASK_A2A:UpdateTaskInfo( DetectedItem ) if self:IsStatePlanned() or self:IsStateAssigned() then - local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate() + local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate() self.TaskInfo:AddTaskName( 0, "MSOD" ) self.TaskInfo:AddCoordinate( TargetCoordinate, 1, "SOD" ) @@ -343,12 +332,12 @@ do -- TASK_A2A end end self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) - self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true ) + self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true ) else local DetectedItemsCount = self.TargetSetUnit:Count() local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames() self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) - self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true ) + self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true ) end end end @@ -359,8 +348,8 @@ do -- TASK_A2A -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. -- @param Wrapper.Group#GROUP TaskGroup The player group. function TASK_A2A:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup ) - - if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then + + if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then return math.random( 1, 9 ) elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then local Coordinate = self.TaskInfo:GetData( "Coordinate" ) @@ -373,8 +362,7 @@ do -- TASK_A2A return 0 end -end - +end do -- TASK_A2A_INTERCEPT @@ -384,44 +372,39 @@ do -- TASK_A2A_INTERCEPT -- @extends Tasking.Task#TASK --- Defines an intercept task for a human player to be executed. - -- When enemy planes need to be intercepted by human players, use this task type to urgen the players to get out there! - -- - -- The TASK_A2A_INTERCEPT is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create intercept tasks + -- When enemy planes need to be intercepted by human players, use this task type to urge the players to get out there! + -- + -- The TASK_A2A_INTERCEPT is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create intercept tasks -- based on detected airborne enemy targets intruding friendly airspace. - -- + -- -- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is intercepting the targets. -- The task is given a name and a briefing, that is used in the menu structure and in the reporting. - -- + -- -- @field #TASK_A2A_INTERCEPT TASK_A2A_INTERCEPT = { - ClassName = "TASK_A2A_INTERCEPT", + ClassName = "TASK_A2A_INTERCEPT" } - - --- Instantiates a new TASK_A2A_INTERCEPT. -- @param #TASK_A2A_INTERCEPT 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.Set#SET_UNIT TargetSetUnit + -- @param Core.Set#SET_UNIT TargetSetUnit -- @param #string TaskBriefing The briefing of the task. -- @return #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 ) ) -- #TASK_A2A_INTERCEPT self:F() - + Mission:AddTask( self ) - - self:SetBriefing( - TaskBriefing or - "Intercept incoming intruders.\n" - ) + + self:SetBriefing( TaskBriefing or "Intercept incoming intruders.\n" ) return self end - - --- Set a score when a target in scope of the A2A attack, has been destroyed . + + --- Set a score when a target in scope of the A2A attack, has been destroyed. -- @param #TASK_A2A_INTERCEPT self -- @param #string PlayerName The name of the player. -- @param #number Score The score in points to be granted when task process has been achieved. @@ -433,7 +416,7 @@ do -- TASK_A2A_INTERCEPT local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has intercepted a target.", Score ) - + return self end @@ -449,7 +432,7 @@ do -- TASK_A2A_INTERCEPT local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Success", "All targets have been successfully intercepted!", Score ) - + return self end @@ -465,14 +448,12 @@ do -- TASK_A2A_INTERCEPT local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Failed", "The intercept has failed!", Penalty ) - + return self end - end - do -- TASK_A2A_SWEEP --- The TASK_A2A_SWEEP class @@ -484,20 +465,18 @@ do -- TASK_A2A_SWEEP -- A sweep task needs to be given when targets were detected but somehow the detection was lost. -- Most likely, these enemy planes are hidden in the mountains or are flying under radar. -- These enemy planes need to be sweeped by human players, and use this task type to urge the players to get out there and find those enemy fighters. - -- - -- The TASK_A2A_SWEEP is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create sweep tasks + -- + -- The TASK_A2A_SWEEP is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create sweep tasks -- based on detected airborne enemy targets intruding friendly airspace, for which the detection has been lost for more than 60 seconds. - -- + -- -- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is sweeping the targets. -- The task is given a name and a briefing, that is used in the menu structure and in the reporting. - -- + -- -- @field #TASK_A2A_SWEEP TASK_A2A_SWEEP = { - ClassName = "TASK_A2A_SWEEP", + ClassName = "TASK_A2A_SWEEP" } - - --- Instantiates a new TASK_A2A_SWEEP. -- @param #TASK_A2A_SWEEP self -- @param Tasking.Mission#MISSION Mission @@ -509,29 +488,26 @@ do -- 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 ) ) -- #TASK_A2A_SWEEP 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:SetBriefing( TaskBriefing or "Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n" ) return self - end + end --- @param #TASK_A2A_SWEEP self function TASK_A2A_SWEEP:onafterGoal( TaskUnit, From, Event, To ) local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT - + if TargetSetUnit:Count() == 0 then self:Success() end - + self:__Goal( -10 ) end - --- Set a score when a target in scope of the A2A attack, has been destroyed . + --- Set a score when a target in scope of the A2A attack, has been destroyed. -- @param #TASK_A2A_SWEEP self -- @param #string PlayerName The name of the player. -- @param #number Score The score in points to be granted when task process has been achieved. @@ -543,7 +519,7 @@ do -- TASK_A2A_SWEEP local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has sweeped a target.", Score ) - + return self end @@ -559,7 +535,7 @@ do -- TASK_A2A_SWEEP local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Success", "All targets have been successfully sweeped!", Score ) - + return self end @@ -575,13 +551,12 @@ do -- TASK_A2A_SWEEP local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Failed", "The sweep has failed!", Penalty ) - + return self end end - do -- TASK_A2A_ENGAGE --- The TASK_A2A_ENGAGE class @@ -591,42 +566,37 @@ do -- TASK_A2A_ENGAGE --- Defines an engage task for a human player to be executed. -- When enemy planes are close to human players, use this task type is used urge the players to get out there! - -- - -- The TASK_A2A_ENGAGE is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create engage tasks + -- + -- The TASK_A2A_ENGAGE is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create engage tasks -- based on detected airborne enemy targets intruding friendly airspace. - -- + -- -- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is engaging the targets. -- The task is given a name and a briefing, that is used in the menu structure and in the reporting. - -- + -- -- @field #TASK_A2A_ENGAGE TASK_A2A_ENGAGE = { - ClassName = "TASK_A2A_ENGAGE", + ClassName = "TASK_A2A_ENGAGE" } - - --- Instantiates a new TASK_A2A_ENGAGE. -- @param #TASK_A2A_ENGAGE 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.Set#SET_UNIT TargetSetUnit + -- @param Core.Set#SET_UNIT TargetSetUnit -- @param #string TaskBriefing The briefing of the task. -- @return #TASK_A2A_ENGAGE self function TASK_A2A_ENGAGE:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "ENGAGE", TaskBriefing ) ) -- #TASK_A2A_ENGAGE self:F() - + Mission:AddTask( self ) - - self:SetBriefing( - TaskBriefing or - "Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n" - ) + + self:SetBriefing( TaskBriefing or "Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n" ) return self - end - + end + --- Set a score when a target in scope of the A2A attack, has been destroyed . -- @param #TASK_A2A_ENGAGE self -- @param #string PlayerName The name of the player. @@ -639,7 +609,7 @@ do -- TASK_A2A_ENGAGE local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has engaged and destroyed a target.", Score ) - + return self end @@ -655,7 +625,7 @@ do -- TASK_A2A_ENGAGE local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Success", "All targets have been successfully engaged!", Score ) - + return self end @@ -671,7 +641,7 @@ do -- TASK_A2A_ENGAGE local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Failed", "The target engagement has failed!", Penalty ) - + return self end diff --git a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua index 76c7225a7..12f0446a7 100644 --- a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua @@ -1,7 +1,7 @@ --- **Tasking** - Dynamically allocates A2A tasks to human players, based on detected airborne targets through an EWR network. --- +-- -- **Features:** --- +-- -- * Dynamically assign tasks to human players based on detected targets. -- * Dynamically change the tasks as the tactical situation evolves during the mission. -- * Dynamically assign (CAP) Control Air Patrols tasks for human players to perform CAP. @@ -11,15 +11,15 @@ -- * Define different ranges to engage upon intruders. -- * Keep task achievements. -- * Score task achievements. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- --- ### Contributions: --- +-- +-- ### Contributions: +-- -- === --- +-- -- @module Tasking.Task_A2A_Dispatcher -- @image Task_A2A_Dispatcher.JPG @@ -30,72 +30,72 @@ do -- TASK_A2A_DISPATCHER -- @extends Tasking.DetectionManager#DETECTION_MANAGER --- Orchestrates the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of EWR installation groups. - -- + -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia3.JPG) - -- + -- -- The EWR will detect units, will group them, and will dispatch @{Task}s to groups. Depending on the type of target detected, different tasks will be dispatched. -- Find a summary below describing for which situation a task type is created: - -- + -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia9.JPG) - -- + -- -- * **INTERCEPT Task**: Is created when the target is known, is detected and within a danger zone, and there is no friendly airborne in range. -- * **SWEEP Task**: Is created when the target is unknown, was detected and the last position is only known, and within a danger zone, and there is no friendly airborne in range. -- * **ENGAGE Task**: Is created when the target is known, is detected and within a danger zone, and there is a friendly airborne in range, that will receive this task. - -- + -- -- ## 1. TASK\_A2A\_DISPATCHER constructor: - -- + -- -- The @{#TASK_A2A_DISPATCHER.New}() method creates a new TASK\_A2A\_DISPATCHER instance. - -- + -- -- ### 1.1. Define or set the **Mission**: - -- + -- -- Tasking is executed to accomplish missions. Therefore, a MISSION object needs to be given as the first parameter. - -- + -- -- local HQ = GROUP:FindByName( "HQ", "Bravo" ) -- local CommandCenter = COMMANDCENTER:New( HQ, "Lima" ) -- local Mission = MISSION:New( CommandCenter, "A2A Mission", "High", "Watch the air enemy units being detected.", coalition.side.RED ) - -- + -- -- Missions are governed by COMMANDCENTERS, so, ensure you have a COMMANDCENTER object installed and setup within your mission. -- Create the MISSION object, and hook it under the command center. - -- + -- -- ### 1.2. Build a set of the groups seated by human players: - -- + -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia6.JPG) - -- + -- -- A set or collection of the groups wherein human players can be seated, these can be clients or units that can be joined as a slot or jumping into. - -- + -- -- local AttackGroups = SET_GROUP:New():FilterCoalitions( "red" ):FilterPrefixes( "Defender" ):FilterStart() - -- + -- -- The set is built using the SET_GROUP class. Apply any filter criteria to identify the correct groups for your mission. -- Only these slots or units will be able to execute the mission and will receive tasks for this mission, once available. - -- + -- -- ### 1.3. Define the **EWR network**: - -- + -- -- As part of the TASK\_A2A\_DISPATCHER constructor, an EWR network must be given as the third parameter. -- An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy. - -- + -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia5.JPG) - -- + -- -- Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units. -- These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US). -- Additionally, ANY other radar capable unit can be part of the EWR network! Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar. -- The position of these units is very important as they need to provide enough coverage -- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them. - -- + -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia7.JPG) - -- - -- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates. - -- For example if they are a long way forward and can detect enemy planes on the ground and taking off - -- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition. - -- Having the radars further back will mean a slower escalation because fewer targets will be detected and - -- therefore less CAP and GCI flights will spawn and this will tend to make just the border area active rather than a melee over the whole map. - -- It all depends on what the desired effect is. - -- + -- + -- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates. + -- For example if they are a long way forward and can detect enemy planes on the ground and taking off + -- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition. + -- Having the radars further back will mean a slower escalation because fewer targets will be detected and + -- therefore less CAP and GCI flights will spawn and this will tend to make just the border area active rather than a melee over the whole map. + -- It all depends on what the desired effect is. + -- -- EWR networks are **dynamically constructed**, that is, they form part of the @{Functional.Detection#DETECTION_BASE} object that is given as the input parameter of the TASK\_A2A\_DISPATCHER class. - -- By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network, + -- By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network, -- increasing or decreasing the radar coverage of the Early Warning System. - -- + -- -- See the following example to setup an EWR network containing EWR stations and AWACS. - -- + -- -- local EWRSet = SET_GROUP:New():FilterPrefixes( "EWR" ):FilterCoalitions("red"):FilterStart() -- -- local EWRDetection = DETECTION_AREAS:New( EWRSet, 6000 ) @@ -104,50 +104,50 @@ do -- TASK_A2A_DISPATCHER -- -- -- Setup the A2A dispatcher, and initialize it. -- A2ADispatcher = TASK_A2A_DISPATCHER:New( Mission, AttackGroups, EWRDetection ) - -- + -- -- The above example creates a SET_GROUP instance, and stores this in the variable (object) **EWRSet**. -- **EWRSet** is then being configured to filter all active groups with a group name starting with **EWR** to be included in the Set. -- **EWRSet** is then being ordered to start the dynamic filtering. Note that any destroy or new spawn of a group with the above names will be removed or added to the Set. - -- Then a new **EWRDetection** object is created from the class DETECTION_AREAS. A grouping radius of 6000 is choosen, which is 6km. + -- Then a new **EWRDetection** object is created from the class DETECTION_AREAS. A grouping radius of 6000 is chosen, which is 6 km. -- The **EWRDetection** object is then passed to the @{#TASK_A2A_DISPATCHER.New}() method to indicate the EWR network configuration and setup the A2A tasking and detection mechanism. - -- + -- -- ### 2. Define the detected **target grouping radius**: - -- + -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia8.JPG) - -- + -- -- The target grouping radius is a property of the Detection object, that was passed to the AI\_A2A\_DISPATCHER object, but can be changed. -- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation. - -- Fast planes like in the 80s, need a larger radius than WWII planes. + -- Fast planes like in the 80s, need a larger radius than WWII planes. -- Typically I suggest to use 30000 for new generation planes and 10000 for older era aircraft. - -- + -- -- Note that detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate -- group being detected. This may result in additional GCI being started by the dispatcher! So don't make this value too small! - -- + -- -- ## 3. Set the **Engage radius**: - -- + -- -- Define the radius to engage any target by airborne friendlies, which are executing cap or returning from an intercept mission. - -- + -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia11.JPG) - -- - -- So, if there is a target area detected and reported, - -- then any friendlies that are airborne near this target area, + -- + -- So, if there is a target area detected and reported, + -- then any friendlies that are airborne near this target area, -- will be commanded to (re-)engage that target when available (if no other tasks were commanded). - -- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, + -- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, -- will be considered to receive the command to engage that target area. -- You need to evaluate the value of this parameter carefully. -- If too small, more intercept missions may be triggered upon detected target areas. -- If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far. - -- + -- -- ## 4. Set **Scoring** and **Messages**: - -- + -- -- The TASK\_A2A\_DISPATCHER is a state machine. It triggers the event Assign when a new player joins a @{Task} dispatched by the TASK\_A2A\_DISPATCHER. -- An _event handler_ can be defined to catch the **Assign** event, and add **additional processing** to set _scoring_ and to _define messages_, -- when the player reaches certain achievements in the task. - -- + -- -- The prototype to handle the **Assign** event needs to be developed as follows: - -- + -- -- TaskDispatcher = TASK_A2A_DISPATCHER:New( ... ) - -- + -- -- --- @param #TaskDispatcher self -- -- @param #string From Contains the name of the state from where the Event was triggered. -- -- @param #string Event Contains the name of the event that was triggered. In this case Assign. @@ -160,22 +160,22 @@ do -- TASK_A2A_DISPATCHER -- Task:SetScoreOnSuccess( PlayerName, 200, TaskUnit ) -- Task:SetScoreOnFail( PlayerName, -100, TaskUnit ) -- end - -- + -- -- The **OnAfterAssign** method (function) is added to the TaskDispatcher object. -- This method will be called when a new player joins a unit in the set of groups in scope of the dispatcher. -- So, this method will be called only **ONCE** when a player joins a unit in scope of the task. - -- + -- -- The TASK class implements various methods to additional **set scoring** for player achievements: - -- + -- -- * @{Tasking.Task#TASK.SetScoreOnProgress}() will add additional scores when a player achieves **Progress** while executing the task. -- Examples of **task progress** can be destroying units, arriving at zones etc. - -- - -- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional scores when the task goes into **Success** state. + -- + -- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional scores when the task goes into **Success** state. -- This means the **task has been successfully completed**. - -- - -- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional (negative) scores when the task goes into **Failed** state. + -- + -- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional (negative) scores when the task goes into **Failed** state. -- This means the **task has not been successfully completed**, and the scores must be given with a negative value! - -- + -- -- @field #TASK_A2A_DISPATCHER TASK_A2A_DISPATCHER = { ClassName = "TASK_A2A_DISPATCHER", @@ -184,8 +184,7 @@ do -- TASK_A2A_DISPATCHER Tasks = {}, SweepZones = {}, } - - + --- TASK_A2A_DISPATCHER constructor. -- @param #TASK_A2A_DISPATCHER self -- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done. @@ -193,22 +192,21 @@ do -- TASK_A2A_DISPATCHER -- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players. -- @return #TASK_A2A_DISPATCHER self function TASK_A2A_DISPATCHER:New( Mission, SetGroup, Detection ) - + -- Inherits from DETECTION_MANAGER local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2A_DISPATCHER - + self.Detection = Detection self.Mission = Mission self.FlashNewTask = false - + -- TODO: Check detection through radar. self.Detection:FilterCategories( Unit.Category.AIRPLANE, Unit.Category.HELICOPTER ) self.Detection:InitDetectRadar( true ) self.Detection:SetRefreshTimeInterval( 30 ) - + self:AddTransition( "Started", "Assign", "Started" ) - --- OnAfter Transition Handler for Event Assign. -- @function [parent=#TASK_A2A_DISPATCHER] OnAfterAssign -- @param #TASK_A2A_DISPATCHER self @@ -220,14 +218,13 @@ do -- TASK_A2A_DISPATCHER -- @param #string PlayerName self:__Start( 5 ) - + return self end - --- Define the radius to when an ENGAGE task will be generated for any nearby by airborne friendlies, which are executing cap or returning from an intercept mission. - -- So, if there is a target area detected and reported, - -- then any friendlies that are airborne near this target area, + -- So, if there is a target area detected and reported, + -- then any friendlies that are airborne near this target area, -- will be commanded to (re-)engage that target when available (if no other tasks were commanded). -- An ENGAGE task will be created for those pilots. -- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, @@ -239,27 +236,27 @@ do -- TASK_A2A_DISPATCHER -- @param #number EngageRadius (Optional, Default = 100000) The radius to report friendlies near the target. -- @return #TASK_A2A_DISPATCHER -- @usage - -- + -- -- -- Set 50km as the radius to engage any target by airborne friendlies. -- TaskA2ADispatcher:SetEngageRadius( 50000 ) - -- + -- -- -- Set 100km as the radius to engage any target by airborne friendlies. -- TaskA2ADispatcher:SetEngageRadius() -- 100000 is the default value. - -- + -- function TASK_A2A_DISPATCHER:SetEngageRadius( EngageRadius ) self.Detection:SetFriendliesRange( EngageRadius or 100000 ) - + return self end - + --- Set flashing player messages on or off -- @param #TASK_A2A_DISPATCHER self -- @param #boolean onoff Set messages on (true) or off (false) function TASK_A2A_DISPATCHER:SetSendMessages( onoff ) - self.FlashNewTask = onoff + self.FlashNewTask = onoff end - + --- Creates an INTERCEPT task when there are targets for it. -- @param #TASK_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem @@ -267,26 +264,25 @@ do -- TASK_A2A_DISPATCHER -- @return #nil If there are no targets to be set. function TASK_A2A_DISPATCHER:EvaluateINTERCEPT( DetectedItem ) self:F( { DetectedItem.ItemID } ) - + local DetectedSet = DetectedItem.Set local DetectedZone = DetectedItem.Zone -- Check if there is at least one UNIT in the DetectedSet is visible. - + if DetectedItem.IsDetected == true then -- Here we're doing something advanced... We're copying the DetectedSet. local TargetSetUnit = SET_UNIT:New() TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - + return TargetSetUnit end - + return nil end - --- Creates an SWEEP task when there are targets for it. -- @param #TASK_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem @@ -294,10 +290,9 @@ do -- TASK_A2A_DISPATCHER -- @return #nil If there are no targets to be set. function TASK_A2A_DISPATCHER:EvaluateSWEEP( DetectedItem ) self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone + local DetectedSet = DetectedItem.Set + local DetectedZone = DetectedItem.Zone -- TODO: This seems unused, remove? if DetectedItem.IsDetected == false then @@ -305,14 +300,13 @@ do -- TASK_A2A_DISPATCHER local TargetSetUnit = SET_UNIT:New() TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - + return TargetSetUnit end - + return nil end - --- Creates an ENGAGE task when there are human friendlies airborne near the targets. -- @param #TASK_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem @@ -320,13 +314,12 @@ do -- TASK_A2A_DISPATCHER -- @return #nil If there are no targets to be set. function TASK_A2A_DISPATCHER:EvaluateENGAGE( DetectedItem ) self:F( { DetectedItem.ItemID } ) - + local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone + local DetectedZone = DetectedItem.Zone -- TODO: This seems unused, remove? local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem ) - -- Only allow ENGAGE when there are Players near the zone, and when the Area has detected items since the last run in a 60 seconds time zone. if PlayersCount > 0 and DetectedItem.IsDetected == true then @@ -334,16 +327,13 @@ do -- TASK_A2A_DISPATCHER local TargetSetUnit = SET_UNIT:New() TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - + return TargetSetUnit end - + return nil end - - - --- Evaluates the removal of the Task from the Mission. -- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned". -- @param #TASK_A2A_DISPATCHER self @@ -354,24 +344,24 @@ do -- TASK_A2A_DISPATCHER -- @param #boolean DetectedItemChange -- @return Tasking.Task#TASK 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 @@ -380,7 +370,7 @@ do -- TASK_A2A_DISPATCHER Remove = true end end - + if TaskType == "SWEEP" then if DetectedItem.IsDetected == true then Remove = true @@ -388,18 +378,18 @@ do -- TASK_A2A_DISPATCHER end local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT - --DetectedSet:Flush( self ) - --self:F( { DetectedSetCount = DetectedSet:Count() } ) + -- DetectedSet:Flush( self ) + -- self:F( { DetectedSetCount = DetectedSet:Count() } ) if DetectedSet:Count() == 0 then Remove = true end - + if DetectedItemChanged == true or Remove then Task = self:RemoveTask( DetectedItemIndex ) end end end - + return Task end @@ -408,10 +398,10 @@ do -- TASK_A2A_DISPATCHER -- @param DetectedItem -- @return #number, Core.CommandCenter#REPORT function TASK_A2A_DISPATCHER:GetFriendliesNearBy( DetectedItem ) - + local DetectedSet = DetectedItem.Set local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE ) - + local FriendlyTypes = {} local FriendliesCount = 0 @@ -423,27 +413,26 @@ do -- TASK_A2A_DISPATCHER local FriendlyUnitThreatLevel = FriendlyUnit:GetThreatLevel() FriendliesCount = FriendliesCount + 1 local FriendlyType = FriendlyUnit:GetTypeName() - FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and ( FriendlyTypes[FriendlyType] + 1 ) or 1 + FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and (FriendlyTypes[FriendlyType] + 1) or 1 if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then end end end - + end - --self:F( { FriendliesCount = FriendliesCount } ) - + -- self:F( { FriendliesCount = FriendliesCount } ) + local FriendlyTypesReport = REPORT:New() - + if FriendliesCount > 0 then for FriendlyType, FriendlyTypeCount in pairs( FriendlyTypes ) do - FriendlyTypesReport:Add( string.format("%d of %s", FriendlyTypeCount, FriendlyType ) ) + FriendlyTypesReport:Add( string.format( "%d of %s", FriendlyTypeCount, FriendlyType ) ) end else FriendlyTypesReport:Add( "-" ) end - - + return FriendliesCount, FriendlyTypesReport end @@ -452,10 +441,10 @@ do -- TASK_A2A_DISPATCHER -- @param DetectedItem -- @return #number, Core.CommandCenter#REPORT function TASK_A2A_DISPATCHER:GetPlayerFriendliesNearBy( DetectedItem ) - + local DetectedSet = DetectedItem.Set local PlayersNearBy = self.Detection:GetPlayersNearBy( DetectedItem ) - + local PlayerTypes = {} local PlayersCount = 0 @@ -464,7 +453,7 @@ do -- TASK_A2A_DISPATCHER for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT local PlayerName = PlayerUnit:GetPlayerName() - --self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } ) + -- self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } ) if PlayerUnit:IsAirPlane() and PlayerName ~= nil then local FriendlyUnitThreatLevel = PlayerUnit:GetThreatLevel() PlayersCount = PlayersCount + 1 @@ -474,20 +463,19 @@ do -- TASK_A2A_DISPATCHER end end end - + end local PlayerTypesReport = REPORT:New() - + if PlayersCount > 0 then for PlayerName, PlayerType in pairs( PlayerTypes ) do - PlayerTypesReport:Add( string.format('"%s" in %s', PlayerName, PlayerType ) ) + PlayerTypesReport:Add( string.format( '"%s" in %s', PlayerName, PlayerType ) ) end else PlayerTypesReport:Add( "-" ) end - - + return PlayersCount, PlayerTypesReport end @@ -496,24 +484,23 @@ do -- TASK_A2A_DISPATCHER self.Tasks[TaskIndex] = nil end - --- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}. -- @param #TASK_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. function TASK_A2A_DISPATCHER:ProcessDetected( Detection ) self:F() - + local AreaMsg = {} local TaskMsg = {} local ChangeMsg = {} - + local Mission = self.Mission - + if Mission:IsIDLE() or Mission:IsENGAGED() then - + local TaskReport = REPORT:New() - + -- Checking the task queue for the dispatcher, and removing any obsolete task! for TaskIndex, TaskData in pairs( self.Tasks ) do local Task = TaskData -- Tasking.Task#TASK @@ -531,18 +518,18 @@ do -- TASK_A2A_DISPATCHER -- Now that all obsolete tasks are removed, loop through the detected targets. for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - + local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT local DetectedCount = DetectedSet:Count() local DetectedZone = DetectedItem.Zone - --self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } ) - --DetectedSet:Flush( self ) - + -- self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } ) + -- DetectedSet:Flush( self ) + 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 ) -- Task will be removed if it is planned and changed. @@ -565,7 +552,7 @@ do -- TASK_A2A_DISPATCHER Task = TASK_A2A_SWEEP:New( Mission, self.SetGroup, string.format( "SWEEP.%03d", DetectedID ), TargetSetUnit ) Task:SetDetection( Detection, DetectedItem ) Task:UpdateTaskInfo( DetectedItem ) - end + end end end @@ -582,7 +569,7 @@ do -- TASK_A2A_DISPATCHER function Task.OnEnterCancelled( Task, From, Event, To ) self:Cancelled( Task ) end - + function Task.OnEnterFailed( Task, From, Event, To ) self:Failed( Task ) end @@ -590,38 +577,38 @@ do -- TASK_A2A_DISPATCHER function Task.OnEnterAborted( Task, From, Event, To ) self:Aborted( Task ) end - + TaskReport:Add( Task:GetName() ) else - self:F("This should not happen") + self:F( "This should not happen" ) end end if Task then local FriendliesCount, FriendliesReport = self:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE ) - Task.TaskInfo:AddText( "Friendlies", string.format( "%d ( %s )", FriendliesCount, FriendliesReport:Text( "," ) ), 40, "MOD" ) + Task.TaskInfo:AddText( "Friendlies", string.format( "%d ( %s )", FriendliesCount, FriendliesReport:Text( "," ) ), 40, "MOD" ) local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem ) - Task.TaskInfo:AddText( "Players", string.format( "%d ( %s )", PlayersCount, PlayersReport:Text( "," ) ), 40, "MOD" ) + Task.TaskInfo:AddText( "Players", string.format( "%d ( %s )", PlayersCount, PlayersReport:Text( "," ) ), 40, "MOD" ) end - + -- OK, so the tasking has been done, now delete the changes reported for the area. Detection:AcceptChanges( DetectedItem ) end - + -- TODO set menus using the HQ coordinator Mission:GetCommandCenter():SetMenu() - local TaskText = TaskReport:Text(", ") - + local TaskText = TaskReport:Text( ", " ) + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and (self.FlashNewTask) then + if (not Mission:IsGroupAssigned( TaskGroup )) and TaskText ~= "" and (self.FlashNewTask) then Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup ) end end - + end - + return true end diff --git a/Moose Development/Moose/Tasking/Task_A2G.lua b/Moose Development/Moose/Tasking/Task_A2G.lua index f4ccf40d6..20e5df555 100644 --- a/Moose Development/Moose/Tasking/Task_A2G.lua +++ b/Moose Development/Moose/Tasking/Task_A2G.lua @@ -1,13 +1,13 @@ --- **Tasking** - The TASK_A2G models tasks for players in Air to Ground engagements. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- --- ### Contributions: --- +-- +-- ### Contributions: +-- -- === --- +-- -- @module Tasking.Task_A2G -- @image MOOSE.JPG @@ -18,29 +18,29 @@ do -- TASK_A2G -- @field Core.Set#SET_UNIT TargetSetUnit -- @extends Tasking.Task#TASK - --- The TASK_A2G class defines Air To Ground tasks for a @{Set} of Target Units, + --- The TASK_A2G class defines Air To Ground tasks for a @{Set} of Target Units, -- based on the tasking capabilities defined in @{Tasking.Task#TASK}. -- The TASK_A2G is implemented using a @{Core.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 @{Wrapper.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. - -- + -- -- ## 1) Set the scoring of achievements in an A2G attack. - -- + -- -- Scoring or penalties can be given in the following circumstances: - -- + -- -- * @{#TASK_A2G.SetScoreOnDestroy}(): Set a score when a target in scope of the A2G attack, has been destroyed. -- * @{#TASK_A2G.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2G attack, have been destroyed. -- * @{#TASK_A2G.SetPenaltyOnFailed}(): Set a penalty when the A2G attack has failed. - -- + -- -- @field #TASK_A2G TASK_A2G = { - ClassName = "TASK_A2G", + ClassName = "TASK_A2G" } - + --- Instantiates a new TASK_A2G. -- @param #TASK_A2G self -- @param Tasking.Mission#MISSION Mission @@ -54,53 +54,51 @@ do -- TASK_A2G function TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskType, TaskBriefing ) local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType, TaskBriefing ) ) -- Tasking.Task#TASK_A2G self:F() - + self.TargetSetUnit = TargetSetUnit self.TaskType = TaskType - + local Fsm = self:GetUnitProcess() - + 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: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: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( "Accounted", "DestroyedAll", "Accounted" ) - --Fsm:AddTransition( "Accounted", "Success", "Success" ) + + -- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" ) + -- Fsm:AddTransition( "Accounted", "Success", "Success" ) Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - - --- Test + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_A2G#TASK_A2G Task function Fsm:onafterAssigned( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) -- Determine the first Unit from the self.RendezVousSetUnit - + self:RouteToRendezVous() end - - --- Test + + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_A2G#TASK_A2G Task function Fsm:onafterRouteToRendezVous( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) -- Determine the first Unit from the self.RendezVousSetUnit - + if Task:GetRendezVousZone( TaskUnit ) then self:__RouteToRendezVousZone( 0.1 ) else @@ -112,36 +110,36 @@ do -- TASK_A2G end end - --- Test + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task#TASK_A2G Task function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) -- Determine the first Unit from the self.TargetSetUnit - - self:__Engage( 0.1 ) + + self:__Engage( 0.1 ) end - - --- Test + + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task#TASK_A2G Task function Fsm:onafterEngage( TaskUnit, Task ) self:F( { self } ) self:__Account( 0.1 ) - self:__RouteToTarget(0.1 ) + self:__RouteToTarget( 0.1 ) self:__RouteToTargets( -10 ) end - - --- Test + + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_A2G#TASK_A2G Task function Fsm:onafterRouteToTarget( TaskUnit, Task ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) -- Determine the first Unit from the self.TargetSetUnit - + if Task:GetTargetZone( TaskUnit ) then self:__RouteToTargetZone( 0.1 ) else @@ -154,8 +152,8 @@ do -- TASK_A2G self:__RouteToTargetPoint( 0.1 ) end end - - --- Test + + --- Test -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_A2G#TASK_A2G Task @@ -167,20 +165,18 @@ do -- TASK_A2G end self:__RouteToTargets( -10 ) end - + return self - + end --- @param #TASK_A2G self -- @param Core.Set#SET_UNIT TargetSetUnit The set of targets. function TASK_A2G:SetTargetSetUnit( TargetSetUnit ) - + self.TargetSetUnit = TargetSetUnit end - - --- @param #TASK_A2G self function TASK_A2G:GetPlannedMenuText() return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" @@ -190,34 +186,32 @@ do -- TASK_A2G -- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. -- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit ) - + function TASK_A2G:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit ) + local ProcessUnit = self:GetUnitProcess( TaskUnit ) - + local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT ActRouteRendezVous:SetCoordinate( RendezVousCoordinate ) ActRouteRendezVous:SetRange( RendezVousRange ) end - + --- @param #TASK_A2G self -- @param Wrapper.Unit#UNIT TaskUnit -- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. -- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. function TASK_A2G:GetRendezVousCoordinate( TaskUnit ) - + local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange() end - - - + --- @param #TASK_A2G self -- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map. -- @param Wrapper.Unit#UNIT TaskUnit function TASK_A2G:SetRendezVousZone( RendezVousZone, TaskUnit ) - + local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE @@ -234,18 +228,17 @@ do -- TASK_A2G local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE return ActRouteRendezVous:GetZone() end - + --- @param #TASK_A2G self -- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map. -- @param Wrapper.Unit#UNIT TaskUnit function TASK_A2G:SetTargetCoordinate( TargetCoordinate, TaskUnit ) - + local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT ActRouteTarget:SetCoordinate( TargetCoordinate ) end - --- @param #TASK_A2G self -- @param Wrapper.Unit#UNIT TaskUnit @@ -258,18 +251,16 @@ do -- TASK_A2G return ActRouteTarget:GetCoordinate() end - --- @param #TASK_A2G 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_A2G:SetTargetZone( TargetZone, TaskUnit ) - + local ProcessUnit = self:GetUnitProcess( TaskUnit ) local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE ActRouteTarget:SetZone( TargetZone ) end - --- @param #TASK_A2G self -- @param Wrapper.Unit#UNIT TaskUnit @@ -283,47 +274,46 @@ do -- TASK_A2G end function TASK_A2G:SetGoalTotal() - + self.GoalTotal = self.TargetSetUnit:Count() end function TASK_A2G:GetGoalTotal() - + return self.GoalTotal end - + --- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats. -- @param #TASK_A2G self - function TASK_A2G:ReportOrder( ReportGroup ) - self:UpdateTaskInfo( self.DetectedItem ) - + function TASK_A2G:ReportOrder( ReportGroup ) + self:UpdateTaskInfo( self.DetectedItem ) + local Coordinate = self.TaskInfo:GetData( "Coordinate" ) local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate ) - + return Distance end - - + --- This method checks every 10 seconds if the goal has been reached of the task. -- @param #TASK_A2G self function TASK_A2G:onafterGoal( TaskUnit, From, Event, To ) local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT - + if TargetSetUnit:Count() == 0 then self:Success() end - + self:__Goal( -10 ) end --- @param #TASK_A2G self function TASK_A2G:UpdateTaskInfo( DetectedItem ) - + if self:IsStatePlanned() or self:IsStateAssigned() then - local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate() + local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate() self.TaskInfo:AddTaskName( 0, "MSOD" ) self.TaskInfo:AddCoordinate( TargetCoordinate, 1, "SOD" ) - + local ThreatLevel, ThreatText if DetectedItem then ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( DetectedItem ) @@ -331,7 +321,7 @@ do -- TASK_A2G ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G() end self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 10, "MOD", true ) - + if self.Detection then local DetectedItemsCount = self.TargetSetUnit:Count() local ReportTypes = REPORT:New() @@ -344,33 +334,33 @@ do -- TASK_A2G end end self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) - self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true ) + self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true ) else local DetectedItemsCount = self.TargetSetUnit:Count() local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames() self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) - self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true ) + self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true ) end self.TaskInfo:AddQFEAtCoordinate( TargetCoordinate, 30, "MOD" ) self.TaskInfo:AddTemperatureAtCoordinate( TargetCoordinate, 31, "MD" ) self.TaskInfo:AddWindAtCoordinate( TargetCoordinate, 32, "MD" ) end - + end - + --- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection. -- @param #TASK_A2G self -- @param #number AutoAssignMethod The method to be applied to the task. -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. -- @param Wrapper.Group#GROUP TaskGroup The player group. function TASK_A2G:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup ) - - if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then + + if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then return math.random( 1, 9 ) elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then local Coordinate = self.TaskInfo:GetData( "Coordinate" ) local Distance = Coordinate:Get2DDistance( CommandCenter:GetPositionable():GetCoordinate() ) - self:F({Distance=Distance}) + self:F( { Distance = Distance } ) return math.floor( Distance ) elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then return 1 @@ -379,8 +369,7 @@ do -- TASK_A2G return 0 end -end - +end do -- TASK_A2G_SEAD @@ -397,9 +386,9 @@ do -- TASK_A2G_SEAD -- -- @field #TASK_A2G_SEAD TASK_A2G_SEAD = { - ClassName = "TASK_A2G_SEAD", + ClassName = "TASK_A2G_SEAD" } - + --- Instantiates a new TASK_A2G_SEAD. -- @param #TASK_A2G_SEAD self -- @param Tasking.Mission#MISSION Mission @@ -408,19 +397,16 @@ do -- TASK_A2G_SEAD -- @param Core.Set#SET_UNIT TargetSetUnit -- @param #string TaskBriefing The briefing of the task. -- @return #TASK_A2G_SEAD self - function TASK_A2G_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing) + function TASK_A2G_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "SEAD", TaskBriefing ) ) -- #TASK_A2G_SEAD self:F() - + Mission:AddTask( self ) - - self:SetBriefing( - TaskBriefing or - "Execute a Suppression of Enemy Air Defenses." - ) + + self:SetBriefing( TaskBriefing or "Execute a Suppression of Enemy Air Defenses." ) return self - end + end --- Set a score when a target in scope of the A2G attack, has been destroyed . -- @param #TASK_A2G_SEAD self @@ -434,7 +420,7 @@ do -- TASK_A2G_SEAD local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has SEADed a target.", Score ) - + return self end @@ -450,7 +436,7 @@ do -- TASK_A2G_SEAD local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Success", "All radar emitting targets have been successfully SEADed!", Score ) - + return self end @@ -466,11 +452,10 @@ do -- TASK_A2G_SEAD local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Failed", "The SEADing has failed!", Penalty ) - + return self end - end do -- TASK_A2G_BAI @@ -488,10 +473,8 @@ do -- TASK_A2G_BAI -- based on detected enemy ground targets. -- -- @field #TASK_A2G_BAI - TASK_A2G_BAI = { - ClassName = "TASK_A2G_BAI", - } - + TASK_A2G_BAI = { ClassName = "TASK_A2G_BAI" } + --- Instantiates a new TASK_A2G_BAI. -- @param #TASK_A2G_BAI self -- @param Tasking.Mission#MISSION Mission @@ -503,14 +486,11 @@ do -- 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 ) ) -- #TASK_A2G_BAI self:F() - + Mission:AddTask( self ) - - self:SetBriefing( - TaskBriefing or - "Execute a Battlefield Air Interdiction of a group of enemy targets." - ) - + + self:SetBriefing( TaskBriefing or "Execute a Battlefield Air Interdiction of a group of enemy targets." ) + return self end @@ -526,7 +506,7 @@ do -- TASK_A2G_BAI 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 @@ -542,7 +522,7 @@ do -- TASK_A2G_BAI 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 @@ -558,15 +538,12 @@ do -- TASK_A2G_BAI local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Failed", "The Battlefield Air Interdiction (BAI) has failed!", Penalty ) - + return self end end - - - do -- TASK_A2G_CAS --- The TASK_A2G_CAS class @@ -581,10 +558,8 @@ do -- TASK_A2G_CAS -- based on detected enemy ground targets. -- -- @field #TASK_A2G_CAS - TASK_A2G_CAS = { - ClassName = "TASK_A2G_CAS", - } - + TASK_A2G_CAS = { ClassName = "TASK_A2G_CAS" } + --- Instantiates a new TASK_A2G_CAS. -- @param #TASK_A2G_CAS self -- @param Tasking.Mission#MISSION Mission @@ -596,19 +571,13 @@ do -- 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 ) ) -- #TASK_A2G_CAS 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! " - ) - + 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 - + end --- Set a score when a target in scope of the A2G attack, has been destroyed . -- @param #TASK_A2G_CAS self @@ -622,7 +591,7 @@ do -- TASK_A2G_CAS 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 @@ -638,7 +607,7 @@ do -- TASK_A2G_CAS 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 @@ -654,9 +623,8 @@ do -- TASK_A2G_CAS local ProcessUnit = self:GetUnitProcess( TaskUnit ) ProcessUnit:AddScore( "Failed", "The Close Air Support (CAS) has failed!", Penalty ) - + return self end - end diff --git a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua index 05bf2d11f..b9aedb1b6 100644 --- a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua @@ -1,24 +1,25 @@ ---- **Tasking** -- Dynamically allocates A2G tasks to human players, based on detected ground targets through reconnaissance. --- +--- **Tasking** -- Dynamically allocates A2G tasks to human players, based on detected ground targets through reconnaissance. +-- -- **Features:** --- +-- -- * Dynamically assign tasks to human players based on detected targets. -- * Dynamically change the tasks as the tactical situation evolves during the mission. -- * Dynamically assign (CAS) Close Air Support tasks for human players. -- * Dynamically assign (BAI) Battlefield Air Interdiction tasks for human players. --- * Dynamically assign (SEAD) Supression of Enemy Air Defense tasks for human players to eliminate G2A missile threats. +-- * Dynamically assign (SEAD) Suppression of Enemy Air Defense tasks for human players to eliminate G2A missile threats. -- * Define and use an EWR (Early Warning Radar) network. -- * Define different ranges to engage upon intruders. -- * Keep task achievements. --- * Score task achievements.-- +-- * Score task achievements. +-- -- === --- +-- -- ### Author: **FlightControl** --- --- ### Contributions: --- +-- +-- ### Contributions: +-- -- === --- +-- -- @module Tasking.Task_A2G_Dispatcher -- @image Task_A2G_Dispatcher.JPG @@ -32,151 +33,151 @@ do -- TASK_A2G_DISPATCHER -- @extends Tasking.DetectionManager#DETECTION_MANAGER --- Orchestrates dynamic **A2G Task Dispatching** based on the detection results of a linked @{Detection} object. - -- + -- -- It uses the Tasking System within the MOOSE framework, which is a multi-player Tasking Orchestration system. -- It provides a truly dynamic battle environment for pilots and ground commanders to engage upon, -- in a true co-operation environment wherein **Multiple Teams** will collaborate in Missions to **achieve a common Mission Goal**. - -- + -- -- The A2G dispatcher will dispatch the A2G Tasks to a defined @{Set} of @{Wrapper.Group}s that will be manned by **Players**. -- We call this the **AttackSet** of the A2G dispatcher. So, the Players are seated in the @{Client}s of the @{Wrapper.Group} @{Set}. - -- + -- -- Depending on the actions of the enemy, preventive tasks are dispatched to the players to orchestrate the engagement in a true co-operation. -- The detection object will group the detected targets by its grouping method, and integrates a @{Set} of @{Wrapper.Group}s that are Recce vehicles or air units. -- We call this the **RecceSet** of the A2G dispatcher. - -- + -- -- Depending on the current detected tactical situation, different task types will be dispatched to the Players seated in the AttackSet.. -- There are currently 3 **Task Types** implemented in the TASK\_A2G\_DISPATCHER: - -- + -- -- - **SEAD Task**: Dispatched when there are ground based Radar Emitters detected within an area. -- - **CAS Task**: Dispatched when there are no ground based Radar Emitters within the area, but there are friendly ground Units within 6 km from the enemy. -- - **BAI Task**: Dispatched when there are no ground based Radar Emitters within the area, and there aren't friendly ground Units within 6 km from the enemy. -- -- # 0. Tactical Situations - -- + -- -- This chapters provides some insights in the tactical situations when certain Task Types are created. -- The Task Types are depending on the enemy positions that were detected, and the current location of friendly units. - -- + -- -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia3.JPG) - -- + -- -- In the demonstration mission [TAD-A2G-000 - AREAS - Detection test], -- the tactical situation is a demonstration how the A2G detection works. -- This example will be taken further in the explanation in the following chapters. - -- + -- -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia4.JPG) - -- + -- -- The red coalition are the players, the blue coalition is the enemy. - -- + -- -- Red reconnaissance vehicles and airborne units are detecting the targets. -- We call this the RecceSet as explained above, which is a Set of Groups that -- have a group name starting with `Recce` (configured in the mission script). - -- + -- -- Red attack units are responsible for executing the mission for the command center. -- We call this the AttackSet, which is a Set of Groups with a group name starting with `Attack` (configured in the mission script). -- These units are setup in this demonstration mission to be ground vehicles and airplanes. -- For demonstration purposes, the attack airplane is stationed on the ground to explain -- the messages and the menus properly. -- Further test missions demonstrate the A2G task dispatcher from within air. - -- + -- -- Depending upon the detection results, the A2G dispatcher will create different tasks. - -- + -- -- # 0.1. SEAD Task - -- + -- -- A SEAD Task is dispatched when there are ground based Radar Emitters detected within an area. - -- + -- -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia9.JPG) - -- + -- -- - Once all Radar Emitting Units have been destroyed, the Task will convert into a BAI or CAS task! -- - A CAS and BAI task may be converted into a SEAD task, once a radar has been detected within the area! - -- + -- -- # 0.2. CAS Task - -- + -- -- A CAS Task is dispatched when there are no ground based Radar Emitters within the area, but there are friendly ground Units within 6 km from the enemy. - -- + -- -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia10.JPG) - -- + -- -- - After the detection of the CAS task, if the friendly Units are destroyed, the CAS task will convert into a BAI task! -- - Only ground Units are taken into account. Airborne units are ships are not considered friendlies that require Close Air Support. - -- + -- -- # 0.3. BAI Task - -- + -- -- A BAI Task is dispatched when there are no ground based Radar Emitters within the area, and there aren't friendly ground Units within 6 km from the enemy. - -- + -- -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia11.JPG) -- -- - A BAI task may be converted into a CAS task if friendly Ground Units approach within 6 km range! -- -- # 1. Player Experience - -- + -- -- The A2G dispatcher is residing under a @{CommandCenter}, which is orchestrating a @{Mission}. -- As a result, you'll find for DCS World missions that implement the A2G dispatcher a **Command Center Menu** and under this one or more **Mission Menus**. - -- + -- -- For example, if there are 2 Command Centers (CC). -- Each CC is controlling a couple of Missions, the Radio Menu Structure could look like this: - -- + -- -- Radio MENU Structure (F10. Other) - -- + -- -- F1. Command Center [Gori] -- F1. Mission "Alpha (Primary)" -- F2. Mission "Beta (Secondary)" -- F3. Mission "Gamma (Tactical)" -- F1. Command Center [Lima] -- F1. Mission "Overlord (High)" - -- - -- Command Center [Gori] is controlling Mission "Alpha", "Beta", "Gamma". Alpha is the Primary mission, Beta the Secondary and there is a Tacical mission Gamma. + -- + -- Command Center [Gori] is controlling Mission "Alpha", "Beta", "Gamma". Alpha is the Primary mission, Beta the Secondary and there is a Tactical mission Gamma. -- Command Center [Lima] is controlling Missions "Overlord", which needs to be executed with High priority. -- -- ## 1.1. Mission Menu (Under the Command Center Menu) - -- + -- -- The Mission Menu controls the information of the mission, including the: - -- + -- -- - **Mission Briefing**: A briefing of the Mission in text, which will be shown as a message. -- - **Mark Task Locations**: A summary of each Task will be shown on the map as a marker. -- - **Create Task Reports**: A menu to create various reports of the current tasks dispatched by the A2G dispatcher. -- - **Create Mission Reports**: A menu to create various reports on the current mission. - -- + -- -- For CC [Lima], Mission "Overlord", the menu structure could look like this: - -- + -- -- Radio MENU Structure (F10. Other) - -- + -- -- F1. Command Center [Lima] -- F1. Mission "Overlord" -- F1. Mission Briefing -- F2. Mark Task Locations on Map -- F3. Task Reports -- F4. Mission Reports - -- + -- -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia5.JPG) - -- + -- -- ### 1.1.1. Mission Briefing Menu - -- + -- -- The Mission Briefing Menu will show in text a summary description of the overall mission objectives and expectations. -- Note that the Mission Briefing is not the briefing of a specific task, but rather provides an overall strategy and tactical situation, - -- and explains the mission goals. - -- - -- + -- and explains the mission goals. + -- + -- -- ### 1.1.2. Mark Task Locations Menu - -- + -- -- The Mark Task Locations Menu will mark the location indications of the Tasks on the map, if this intelligence is known by the Command Center. -- For A2G tasks this information will always be know, but it can be that for other tasks a location intelligence will be less relevant. -- Note that each Planned task and each Engaged task will be marked. Completed, Failed and Cancelled tasks are not marked. -- Depending on the task type, a summary information is shown to bring to the player the relevant information for situational awareness. - -- + -- -- ### 1.1.3. Task Reports Menu - -- + -- -- The Task Reports Menu is a sub menu, that allows to create various reports: - -- + -- -- - **Tasks Summary**: This report will list all the Tasks that are or were active within the mission, indicating its status. -- - **Planned Tasks**: This report will list all the Tasks that are in status Planned, which are Tasks not assigned to any player, and are ready to be executed. -- - **Assigned Tasks**: This report will list all the Tasks that are in status Assigned, which are Tasks assigned to (a) player(s) and are currently executed. -- - **Successful Tasks**: This report will list all the Tasks that are in status Success, which are Tasks executed by (a) player(s) and are completed successfully. -- - **Failed Tasks**: This report will list all the Tasks that are in status Success, which are Tasks executed by (a) player(s) and that have failed. - -- + -- -- The information shown of the tasks will vary according the underlying task type, but are self explanatory. -- -- For CC [Gori], Mission "Alpha", the Task Reports menu structure could look like this: - -- + -- -- Radio MENU Structure (F10. Other) - -- + -- -- F1. Command Center [Gori] -- F1. Mission "Alpha" -- F1. Mission Briefing @@ -188,21 +189,21 @@ do -- TASK_A2G_DISPATCHER -- F4. Successful Tasks -- F5. Failed Tasks -- F4. Mission Reports - -- + -- -- Note that these reports provide an "overview" of the tasks. Detailed information of the task can be retrieved using the Detailed Report on the Task Menu. -- (See later). - -- + -- -- ### 1.1.4. Mission Reports Menu - -- + -- -- The Mission Reports Menu is a sub menu, that provides options to retrieve further information on the current Mission: - -- - -- - **Report Mission Progress**: Shows the progress of the current Mission. Each Task has a %-tage of completion. + -- + -- - **Report Mission Progress**: Shows the progress of the current Mission. Each Task has a % of completion. -- - **Report Players per Task**: Show which players are engaged on which Task within the Mission. - -- + -- -- For CC |Gori|, Mission "Alpha", the Mission Reports menu structure could look like this: - -- + -- -- Radio MENU Structure (F10. Other) - -- + -- -- F1. Command Center [Gori] -- F1. Mission "Alpha" -- F1. Mission Briefing @@ -211,25 +212,25 @@ do -- TASK_A2G_DISPATCHER -- F4. Mission Reports -- F1. Report Mission Progress -- F2. Report Players per Task - -- - -- + -- + -- -- ## 1.2. Task Management Menus - -- + -- -- Very important to remember is: **Multiple Players can be assigned to the same Task, but from the player perspective, the Player can only be assigned to one Task per Mission at the same time!** -- Consider this like the two major modes in which a player can be in. He can be free of tasks or he can be assigned to a Task. -- Depending on whether a Task has been Planned or Assigned to a Player (Group), -- **the Mission Menu will contain extra Menus to control specific Tasks.** - -- + -- -- #### 1.2.1. Join a Planned Task - -- + -- -- If the Player has not yet been assigned to a Task within the Mission, the Mission Menu will contain additionally a: - -- + -- -- - Join Planned Task Menu: This menu structure allows the player to join a planned task (a Task with status Planned). - -- + -- -- For CC |Gori|, Mission "Alpha", the menu structure could look like this: - -- + -- -- Radio MENU Structure (F10. Other) - -- + -- -- F1. Command Center [Gori] -- F1. Mission "Alpha" -- F1. Mission Briefing @@ -237,23 +238,23 @@ do -- TASK_A2G_DISPATCHER -- F3. Task Reports -- F4. Mission Reports -- F5. Join Planned Task - -- + -- -- **The F5. Join Planned Task allows the player to join a Planned Task and take an engagement in the running Mission.** - -- - -- #### 1.2.2. Manage an Assigned Task - -- + -- + -- #### 1.2.2. Manage an Assigned Task + -- -- If the Player has been assigned to one Task within the Mission, the Mission Menu will contain an extra: - -- + -- -- - Assigned Task __TaskName__ Menu: This menu structure allows the player to take actions on the currently engaged task. - -- + -- -- In this example, the Group currently seated by the player is not assigned yet to a Task. -- The Player has the option to assign itself to a Planned Task using menu option F5 under the Mission Menu "Alpha". - -- + -- -- This would be an example menu structure, -- for CC |Gori|, Mission "Alpha", when a player would have joined Task CAS.001: - -- + -- -- Radio MENU Structure (F10. Other) - -- + -- -- F1. Command Center [Gori] -- F1. Mission "Alpha" -- F1. Mission Briefing @@ -261,26 +262,25 @@ do -- TASK_A2G_DISPATCHER -- F3. Task Reports -- F4. Mission Reports -- F5. Assigned Task CAS.001 - -- + -- -- **The F5. Assigned Task __TaskName__ allows the player to control the current Assigned Task and take further actions.** - -- - -- + -- -- ## 1.3. Join Planned Task Menu - -- + -- -- The Join Planned Task Menu contains the different Planned A2G Tasks **in a structured Menu Hierarchy**. - -- The Menu Hierarchy is structuring the Tasks per **Task Type**, and then by **Task Name (ID)**. - -- - -- For example, for CC [Gori], Mission "Alpha", + -- The Menu Hierarchy is structuring the Tasks per **Task Type**, and then by **Task Name (ID)**. + -- + -- For example, for CC [Gori], Mission "Alpha", -- if a Mission "ALpha" contains 5 Planned Tasks, which would be: - -- - -- - 2 CAS Tasks + -- + -- - 2 CAS Tasks -- - 1 BAI Task -- - 2 SEAD Tasks - -- + -- -- the Join Planned Task Menu Hierarchy could look like this: - -- + -- -- Radio MENU Structure (F10. Other) - -- + -- -- F1. Command Center [Gori] -- F1. Mission "Alpha" -- F1. Mission Briefing @@ -296,26 +296,26 @@ do -- TASK_A2G_DISPATCHER -- F1. SEAD.003 -- F2. SEAD.004 -- F3. SEAD.005 - -- + -- -- An example from within a running simulation: - -- + -- -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia6.JPG) - -- + -- -- Each Task Type Menu would have a list of the Task Menus underneath. -- Each Task Menu (eg. `CAS.001`) has a **detailed Task Menu structure to control the specific task**! -- -- ### 1.3.1. Planned Task Menu -- -- Each Planned Task Menu will allow for the following actions: - -- + -- -- - Report Task Details: Provides a detailed report on the Planned Task. -- - Mark Task Location on Map: Mark the approximate location of the Task on the Map, if relevant. -- - Join Task: Join the Task. This is THE menu option to let a Player join the Task, and to engage within the Mission. - -- - -- The Join Planned Task Menu could look like this for for CC |Gori|, Mission "Alpha": - -- + -- + -- The Join Planned Task Menu could look like this for for CC |Gori|, Mission "Alpha": + -- -- Radio MENU Structure (F10. Other) - -- + -- -- F1. Command Center |Gori| -- F1. Mission "Alpha" -- F1. Mission Briefing @@ -328,22 +328,22 @@ do -- TASK_A2G_DISPATCHER -- F1. Report Task Details -- F2. Mark Task Location on Map -- F3. Join Task - -- + -- -- **The Join Task is THE menu option to let a Player join the Task, and to engage within the Mission.** - -- - -- + -- + -- -- ## 1.4. Assigned Task Menu - -- + -- -- The Assigned Task Menu allows to control the **current assigned task** within the Mission. - -- + -- -- Depending on the Type of Task, the following menu options will be available: - -- + -- -- - **Report Task Details**: Provides a detailed report on the Planned Task. -- - **Mark Task Location on Map**: Mark the approximate location of the Task on the Map, if relevant. -- - **Abort Task: Abort the current assigned Task:** This menu option lets the player abort the Task. - -- + -- -- For example, for CC |Gori|, Mission "Alpha", the Assigned Menu could be: - -- + -- -- F1. Command Center |Gori| -- F1. Mission "Alpha" -- F1. Mission Briefing @@ -354,90 +354,89 @@ do -- TASK_A2G_DISPATCHER -- F1. Report Task Details -- F2. Mark Task Location on Map -- F3. Abort Task - -- + -- -- Task abortion will result in the Task to be Cancelled, and the Task **may** be **Replanned**. - -- However, this will depend on the setup of each Mission. - -- + -- However, this will depend on the setup of each Mission. + -- -- ## 1.5. Messages - -- + -- -- During game play, different messages are displayed. -- These messages provide an update of the achievements made, and the state wherein the task is. - -- + -- -- The various reports can be used also to retrieve the current status of the mission and its tasks. - -- + -- -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia7.JPG) - -- + -- -- The @{Settings} menu provides additional options to control the timing of the messages. -- There are: - -- + -- -- - Status messages, which are quick status updates. The settings menu allows to switch off these messages. -- - Information messages, which are shown a bit longer, as they contain important information. -- - Summary reports, which are quick reports showing a high level summary. -- - Overview reports, which are providing the essential information. It provides an overview of a greater thing, and may take a bit of time to read. -- - Detailed reports, which provide with very detailed information. It takes a bit longer to read those reports, so the display of those could be a bit longer. - -- + -- -- # 2. TASK\_A2G\_DISPATCHER constructor - -- + -- -- The @{#TASK_A2G_DISPATCHER.New}() method creates a new TASK\_A2G\_DISPATCHER instance. -- -- # 3. Usage -- -- To use the TASK\_A2G\_DISPATCHER class, you need: - -- + -- -- - A @{CommandCenter} object. The master communication channel. -- - A @{Mission} object. Each task belongs to a Mission. -- - A @{Detection} object. There are several detection grouping methods to choose from. -- - A @{Task_A2G_Dispatcher} object. The master A2G task dispatcher. - -- - A @{Set} of @{Wrapper.Group} objects that will detect the emeny, the RecceSet. This is attached to the @{Detection} object. + -- - A @{Set} of @{Wrapper.Group} objects that will detect the enemy, the RecceSet. This is attached to the @{Detection} object. -- - A @{Set} ob @{Wrapper.Group} objects that will attack the enemy, the AttackSet. This is attached to the @{Task_A2G_Dispatcher} object. - -- - -- Below an example mission declaration that is defines a Task A2G Dispatcher object. -- - -- -- Declare the Command Center + -- Below an example mission declaration that is defines a Task A2G Dispatcher object. + -- + -- -- Declare the Command Center -- local HQ = GROUP -- :FindByName( "HQ", "Bravo HQ" ) -- -- local CommandCenter = COMMANDCENTER -- :New( HQ, "Lima" ) - -- + -- -- -- Declare the Mission for the Command Center. -- local Mission = MISSION -- :New( CommandCenter, "Overlord", "High", "Attack Detect Mission Briefing", coalition.side.RED ) - -- + -- -- -- Define the RecceSet that will detect the enemy. -- local RecceSet = SET_GROUP -- :New() -- :FilterPrefixes( "FAC" ) -- :FilterCoalitions("red") -- :FilterStart() - -- + -- -- -- Setup the detection. We use DETECTION_AREAS to detect and group the enemies within areas of 3 km radius. -- local DetectionAreas = DETECTION_AREAS -- :New( RecceSet, 3000 ) -- The RecceSet will detect the enemies. - -- + -- -- -- Setup the AttackSet, which is a SET_GROUP. - -- -- The SET_GROUP is a dynamic collection of GROUP objects. + -- -- The SET_GROUP is a dynamic collection of GROUP objects. -- local AttackSet = SET_GROUP -- :New() -- Create the SET_GROUP object. -- :FilterCoalitions( "red" ) -- Only incorporate the RED coalitions. -- :FilterPrefixes( "Attack" ) -- Only incorporate groups that start with the name Attack. -- :FilterStart() -- Enable the dynamic filtering. From this moment the AttackSet will contain all groups that are red and start with the name Attack. - -- + -- -- -- Now we have everything to setup the main A2G TaskDispatcher. -- TaskDispatcher = TASK_A2G_DISPATCHER - -- :New( Mission, AttackSet, DetectionAreas ) -- We assign the TaskDispatcher under Mission. The AttackSet will engage the enemy and will recieve the dispatched Tasks. The DetectionAreas will report any detected enemies to the TaskDispatcher. - -- - -- + -- :New( Mission, AttackSet, DetectionAreas ) -- We assign the TaskDispatcher under Mission. The AttackSet will engage the enemy and will receive the dispatched Tasks. The DetectionAreas will report any detected enemies to the TaskDispatcher. + -- + -- -- -- @field #TASK_A2G_DISPATCHER TASK_A2G_DISPATCHER = { ClassName = "TASK_A2G_DISPATCHER", Mission = nil, Detection = nil, - Tasks = {}, + Tasks = {} } - - + --- TASK_A2G_DISPATCHER constructor. -- @param #TASK_A2G_DISPATCHER self -- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done. @@ -445,18 +444,18 @@ do -- TASK_A2G_DISPATCHER -- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players. -- @return #TASK_A2G_DISPATCHER self function TASK_A2G_DISPATCHER:New( Mission, SetGroup, Detection ) - + -- Inherits from DETECTION_MANAGER local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2G_DISPATCHER - + self.Detection = Detection self.Mission = Mission - self.FlashNewTask = true --set to false to suppress flash messages - + self.FlashNewTask = true -- set to false to suppress flash messages + self.Detection:FilterCategories( { Unit.Category.GROUND_UNIT } ) - + self:AddTransition( "Started", "Assign", "Started" ) - + --- OnAfter Transition Handler for Event Assign. -- @function [parent=#TASK_A2G_DISPATCHER] OnAfterAssign -- @param #TASK_A2G_DISPATCHER self @@ -466,19 +465,19 @@ do -- TASK_A2G_DISPATCHER -- @param Tasking.Task_A2G#TASK_A2G Task -- @param Wrapper.Unit#UNIT TaskUnit -- @param #string PlayerName - + self:__Start( 5 ) - + return self end - - --- Set flashing player messages on or off + + --- Set flashing player messages on or off -- @param #TASK_A2G_DISPATCHER self -- @param #boolean onoff Set messages on (true) or off (false) function TASK_A2G_DISPATCHER:SetSendMessages( onoff ) - self.FlashNewTask = onoff + self.FlashNewTask = onoff end - + --- Creates a SEAD task when there are targets for it. -- @param #TASK_A2G_DISPATCHER self -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem @@ -486,7 +485,7 @@ do -- TASK_A2G_DISPATCHER -- @return #nil If there are no targets to be set. function TASK_A2G_DISPATCHER:EvaluateSEAD( DetectedItem ) self:F( { DetectedItem.ItemID } ) - + local DetectedSet = DetectedItem.Set local DetectedZone = DetectedItem.Zone @@ -500,10 +499,10 @@ do -- TASK_A2G_DISPATCHER TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:FilterHasSEAD() TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - + return TargetSetUnit end - + return nil end @@ -514,11 +513,10 @@ do -- TASK_A2G_DISPATCHER -- @return #nil If there are no targets to be set. function TASK_A2G_DISPATCHER:EvaluateCAS( DetectedItem ) self:F( { DetectedItem.ItemID } ) - + local DetectedSet = DetectedItem.Set local DetectedZone = DetectedItem.Zone - -- Determine if the set has ground units. -- There should be ground unit friendlies nearby. Airborne units are valid friendlies types. -- And there shouldn't be any radar. @@ -532,13 +530,13 @@ do -- TASK_A2G_DISPATCHER local TargetSetUnit = SET_UNIT:New() TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - + return TargetSetUnit end - + return nil end - + --- Creates a BAI task when there are targets for it. -- @param #TASK_A2G_DISPATCHER self -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem @@ -546,11 +544,10 @@ do -- TASK_A2G_DISPATCHER -- @return #nil If there are no targets to be set. function TASK_A2G_DISPATCHER:EvaluateBAI( DetectedItem, FriendlyCoalition ) self:F( { DetectedItem.ItemID } ) - + local DetectedSet = DetectedItem.Set local DetectedZone = DetectedItem.Zone - -- Determine if the set has ground units. -- There shouldn't be any ground unit friendlies nearby. -- And there shouldn't be any radar. @@ -564,19 +561,18 @@ do -- TASK_A2G_DISPATCHER local TargetSetUnit = SET_UNIT:New() TargetSetUnit:SetDatabase( DetectedSet ) TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - + return TargetSetUnit end - + return nil end - - + function TASK_A2G_DISPATCHER:RemoveTask( TaskIndex ) self.Mission:RemoveTask( self.Tasks[TaskIndex] ) self.Tasks[TaskIndex] = nil end - + --- Evaluates the removal of the Task from the Mission. -- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned". -- @param #TASK_A2G_DISPATCHER self @@ -586,17 +582,16 @@ do -- TASK_A2G_DISPATCHER -- @param #boolean DetectedItemChange -- @return Tasking.Task#TASK function TASK_A2G_DISPATCHER:EvaluateRemoveTask( Mission, Task, TaskIndex, DetectedItemChanged ) - + if Task then - if ( Task:IsStatePlanned() and DetectedItemChanged == true ) or Task:IsStateCancelled() then - --self:F( "Removing Tasking: " .. Task:GetTaskName() ) + if (Task:IsStatePlanned() and DetectedItemChanged == true) or Task:IsStateCancelled() then + -- self:F( "Removing Tasking: " .. Task:GetTaskName() ) self:RemoveTask( TaskIndex ) end end - + return Task end - --- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}. -- @param #TASK_A2G_DISPATCHER self @@ -604,15 +599,15 @@ do -- TASK_A2G_DISPATCHER -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. function TASK_A2G_DISPATCHER:ProcessDetected( Detection ) self:F() - + local AreaMsg = {} local TaskMsg = {} local ChangeMsg = {} - + local Mission = self.Mission - + if Mission:IsIDLE() or Mission:IsENGAGED() then - + local TaskReport = REPORT:New() -- Checking the task queue for the dispatcher, and removing any obsolete task! @@ -634,21 +629,21 @@ do -- TASK_A2G_DISPATCHER --- First we need to the detected targets. for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - + local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT local DetectedZone = DetectedItem.Zone - --self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } ) - --DetectedSet:Flush( self ) - + -- self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } ) + -- DetectedSet:Flush( self ) + local DetectedItemID = DetectedItem.ID local TaskIndex = DetectedItem.Index local DetectedItemChanged = DetectedItem.Changed - + self:F( { DetectedItemChanged = DetectedItemChanged, DetectedItemID = DetectedItemID, TaskIndex = TaskIndex } ) - + local Task = self.Tasks[TaskIndex] -- Tasking.Task_A2G#TASK_A2G - + if Task then -- If there is a Task and the task was assigned, then we check if the task was changed ... If it was, we need to reevaluate the targets. if Task:IsStateAssigned() then @@ -660,7 +655,7 @@ do -- TASK_A2G_DISPATCHER Task:SetTargetSetUnit( TargetSetUnit ) Task:SetDetection( Detection, DetectedItem ) Task:UpdateTaskInfo( DetectedItem ) - TargetsReport:Add( Detection:GetChangeText( DetectedItem ) ) + TargetsReport:Add( Detection:GetChangeText( DetectedItem ) ) else Task:Cancel() end @@ -691,18 +686,18 @@ do -- TASK_A2G_DISPATCHER end end end - + -- Now we send to each group the changes, if any. for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - local TargetsText = TargetsReport:Text(", ") - if ( Mission:IsGroupAssigned(TaskGroup) ) and TargetsText ~= "" and self.FlashNewTask then + local TargetsText = TargetsReport:Text( ", " ) + if (Mission:IsGroupAssigned( TaskGroup )) and TargetsText ~= "" and self.FlashNewTask 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 -- The detection has changed, thus a new TargetSet is to be evaluated and set @@ -753,7 +748,7 @@ do -- TASK_A2G_DISPATCHER local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then Task = TASK_A2G_SEAD:New( Mission, self.SetGroup, string.format( "SEAD.%03d", DetectedItemID ), TargetSetUnit ) - DetectedItem.DesignateMenuName = string.format( "SEAD.%03d", DetectedItemID ) --inject a name for DESIGNATE, if using same DETECTION object + DetectedItem.DesignateMenuName = string.format( "SEAD.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object Task:SetDetection( Detection, DetectedItem ) end @@ -762,7 +757,7 @@ do -- TASK_A2G_DISPATCHER local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed... if TargetSetUnit then Task = TASK_A2G_CAS:New( Mission, self.SetGroup, string.format( "CAS.%03d", DetectedItemID ), TargetSetUnit ) - DetectedItem.DesignateMenuName = string.format( "CAS.%03d", DetectedItemID ) --inject a name for DESIGNATE, if using same DETECTION object + DetectedItem.DesignateMenuName = string.format( "CAS.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object Task:SetDetection( Detection, DetectedItem ) end @@ -771,19 +766,19 @@ do -- TASK_A2G_DISPATCHER local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.Mission:GetCommandCenter():GetPositionable():GetCoalition() ) -- Returns a SetUnit if there are targets to be BAIed... if TargetSetUnit then Task = TASK_A2G_BAI:New( Mission, self.SetGroup, string.format( "BAI.%03d", DetectedItemID ), TargetSetUnit ) - DetectedItem.DesignateMenuName = string.format( "BAI.%03d", DetectedItemID ) --inject a name for DESIGNATE, if using same DETECTION object + DetectedItem.DesignateMenuName = string.format( "BAI.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object Task:SetDetection( Detection, DetectedItem ) end end end - + if Task then self.Tasks[TaskIndex] = Task Task:SetTargetZone( DetectedZone ) Task:SetDispatcher( self ) Task:UpdateTaskInfo( DetectedItem ) Mission:AddTask( Task ) - + function Task.OnEnterSuccess( Task, From, Event, To ) self:Success( Task ) end @@ -791,7 +786,7 @@ do -- TASK_A2G_DISPATCHER function Task.OnEnterCancelled( Task, From, Event, To ) self:Cancelled( Task ) end - + function Task.OnEnterFailed( Task, From, Event, To ) self:Failed( Task ) end @@ -799,31 +794,29 @@ do -- TASK_A2G_DISPATCHER function Task.OnEnterAborted( Task, From, Event, To ) self:Aborted( Task ) end - - + TaskReport:Add( Task:GetName() ) else - self:F("This should not happen") + self:F( "This should not happen" ) end end - -- OK, so the tasking has been done, now delete the changes reported for the area. Detection:AcceptChanges( DetectedItem ) end - + -- TODO set menus using the HQ coordinator Mission:GetCommandCenter():SetMenu() - - local TaskText = TaskReport:Text(", ") + + local TaskText = TaskReport:Text( ", " ) for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and self.FlashNewTask then + if (not Mission:IsGroupAssigned( TaskGroup )) and TaskText ~= "" and self.FlashNewTask then Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup ) end end - + end - + return true end diff --git a/Moose Development/Moose/Utilities/Profiler.lua b/Moose Development/Moose/Utilities/Profiler.lua index 84e4829b0..4eeb76fb3 100644 --- a/Moose Development/Moose/Utilities/Profiler.lua +++ b/Moose Development/Moose/Utilities/Profiler.lua @@ -9,7 +9,6 @@ -- @module Utilities.PROFILER -- @image Utils_Profiler.jpg - --- PROFILER class. -- @type PROFILER -- @field #string ClassName Name of the class. @@ -129,13 +128,13 @@ PROFILER = { --- Start profiler. -- @param #number Delay Delay in seconds before profiler is stated. Default is immediately. -- @param #number Duration Duration in (game) seconds before the profiler is stopped. Default is when mission ends. -function PROFILER.Start(Delay, Duration) +function PROFILER.Start( Delay, Duration ) -- Check if os, io and lfs are available. - local go=true + local go = true if not os then - env.error("ERROR: Profiler needs os to be desanitized!") - go=false + env.error( "ERROR: Profiler needs os to be de-sanitized!" ) + go = false end if not io then env.error("ERROR: Profiler needs io to be desanitized!") @@ -149,8 +148,8 @@ function PROFILER.Start(Delay, Duration) return end - if Delay and Delay>0 then - BASE:ScheduleOnce(Delay, PROFILER.Start, 0, Duration) + if Delay and Delay > 0 then + BASE:ScheduleOnce( Delay, PROFILER.Start, 0, Duration ) else -- Set start time. @@ -161,11 +160,11 @@ function PROFILER.Start(Delay, Duration) world.addEventHandler(PROFILER.eventHandler) -- Info in log. - env.info('############################ Profiler Started ############################') + env.info( '############################ Profiler Started ############################' ) if Duration then - env.info(string.format("- Will be running for %d seconds", Duration)) + env.info( string.format( "- Will be running for %d seconds", Duration ) ) else - env.info(string.format("- Will be stopped when mission ends")) + env.info( string.format( "- Will be stopped when mission ends" ) ) end env.info(string.format("- Calls per second threshold %.3f/sec", PROFILER.ThreshCPS)) env.info(string.format("- Total function time threshold %.3f sec", PROFILER.ThreshTtot)) @@ -183,7 +182,7 @@ function PROFILER.Start(Delay, Duration) -- Auto stop profiler. if Duration then - PROFILER.Stop(Duration) + PROFILER.Stop( Duration ) end end @@ -192,6 +191,14 @@ end --- Stop profiler. -- @param #number Delay Delay before stop in seconds. +function PROFILER.Stop( Delay ) + + if Delay and Delay > 0 then + + BASE:ScheduleOnce( Delay, PROFILER.Stop ) + end +end + function PROFILER.Stop(Delay) if Delay and Delay>0 then @@ -218,8 +225,8 @@ function PROFILER.Stop(Delay) end --- Event handler. -function PROFILER.eventHandler:onEvent(event) - if event.id==world.event.S_EVENT_MISSION_END then +function PROFILER.eventHandler:onEvent( event ) + if event.id == world.event.S_EVENT_MISSION_END then PROFILER.Stop() end end @@ -246,7 +253,7 @@ function PROFILER.hook(event) end else - PROFILER.Counters[f]=PROFILER.Counters[f]+1 + PROFILER.Counters[f] = PROFILER.Counters[f] + 1 end if PROFILER.fTime[f]==nil then @@ -288,15 +295,15 @@ end --- Write text to log file. -- @param #function f The file. -- @param #string txt The text. -function PROFILER._flog(f, txt) - f:write(txt.."\r\n") +function PROFILER._flog( f, txt ) + f:write( txt .. "\r\n" ) end --- Show table. -- @param #table data Data table. -- @param #function f The file. -- @param #number runTimeGame Game run time in seconds. -function PROFILER.showTable(data, f, runTimeGame) +function PROFILER.showTable( data, f, runTimeGame ) -- Loop over data. for i=1, #data do @@ -322,11 +329,11 @@ end --- Print csv file. -- @param #table data Data table. -- @param #number runTimeGame Game run time in seconds. -function PROFILER.printCSV(data, runTimeGame) +function PROFILER.printCSV( data, runTimeGame ) -- Output file. - local file=PROFILER.getfilename("csv") - local g=io.open(file, 'w') + local file = PROFILER.getfilename( "csv" ) + local g = io.open( file, 'w' ) -- Header. local text="Function,Total Calls,Calls per Sec,Total Time,Total in %,Sec per Call,Source File;Line Number," @@ -337,7 +344,7 @@ function PROFILER.printCSV(data, runTimeGame) local t=data[i] --#PROFILER.Data -- Calls per second. - local cps=t.count/runTimeGame + local cps = t.count / runTimeGame -- Output local txt=string.format("%s,%d,%.1f,%.3f,%.3f,%.3f,%s,%s,", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, t.tm/t.count, tostring(t.src), tostring(t.line)) @@ -349,7 +356,6 @@ function PROFILER.printCSV(data, runTimeGame) g:close() end - --- Write info to output file. -- @param #string ext Extension. -- @return #string File name. @@ -365,11 +371,11 @@ function PROFILER.getfilename(ext) return file end - for i=1,999 do + for i = 1, 999 do - local file=string.format("%s%s-%03d.%s", dir,PROFILER.fileNamePrefix, i, ext) + local file = string.format( "%s%s-%03d.%s", dir, PROFILER.fileNamePrefix, i, ext ) - if not UTILS.FileExists(file) then + if not UTILS.FileExists( file ) then return file end @@ -380,7 +386,7 @@ end --- Write info to output file. -- @param #number runTimeGame Game time in seconds. -- @param #number runTimeOS OS time in seconds. -function PROFILER.showInfo(runTimeGame, runTimeOS) +function PROFILER.showInfo( runTimeGame, runTimeOS ) -- Output file. local file=PROFILER.getfilename(PROFILER.fileNameSuffix) @@ -418,16 +424,16 @@ function PROFILER.showInfo(runTimeGame, runTimeOS) } --#PROFILER.Data -- Collect special cases. Somehow, e.g. "_copy" appears multiple times so we try to gather all data. - if s=="_copy" then - if tcopy==nil then - tcopy=T + if s == "_copy" then + if tcopy == nil then + tcopy = T else - tcopy.count=tcopy.count+T.count - tcopy.tm=tcopy.tm+T.tm + tcopy.count = tcopy.count + T.count + tcopy.tm = tcopy.tm + T.tm end - elseif s=="_Serialize" then - if tserialize==nil then - tserialize=T + elseif s == "_Serialize" then + if tserialize == nil then + tserialize = T else tserialize.count=tserialize.count+T.count tserialize.tm=tserialize.tm+T.tm @@ -447,7 +453,7 @@ function PROFILER.showInfo(runTimeGame, runTimeOS) tpairs.tm=tpairs.tm+T.tm end else - table.insert(t, T) + table.insert( t, T ) end -- Total function time. @@ -462,13 +468,13 @@ function PROFILER.showInfo(runTimeGame, runTimeOS) -- Add special cases. if tcopy then - table.insert(t, tcopy) + table.insert( t, tcopy ) end if tserialize then table.insert(t, tserialize) end if tforgen then - table.insert(t, tforgen) + table.insert( t, tforgen ) end if tpairs then table.insert(t, tpairs) @@ -538,13 +544,13 @@ function PROFILER.showInfo(runTimeGame, runTimeOS) PROFILER.showTable(t, f, runTimeGame) -- Closing. - PROFILER._flog(f,"") - PROFILER._flog(f,"************************************************************************************************************************") - PROFILER._flog(f,"************************************************************************************************************************") - PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog( f, "" ) + PROFILER._flog( f, "************************************************************************************************************************" ) + PROFILER._flog( f, "************************************************************************************************************************" ) + PROFILER._flog( f, "************************************************************************************************************************" ) -- Close file. f:close() -- Print csv file. - PROFILER.printCSV(t, runTimeGame) + PROFILER.printCSV( t, runTimeGame ) end diff --git a/Moose Development/Moose/Utilities/Routines.lua b/Moose Development/Moose/Utilities/Routines.lua index eddd2c8dd..926c8686d 100644 --- a/Moose Development/Moose/Utilities/Routines.lua +++ b/Moose Development/Moose/Utilities/Routines.lua @@ -1,15 +1,13 @@ --- Various routines -- @module routines -- @image MOOSE.JPG - -env.setErrorMessageBoxEnabled(false) +env.setErrorMessageBoxEnabled( false ) --- Extract of MIST functions. -- @author Grimes routines = {} - -- don't change these routines.majorVersion = 3 routines.minorVersion = 3 @@ -21,291 +19,281 @@ routines.build = 22 -- Utils- conversion, Lua utils, etc. routines.utils = {} -routines.utils.round = function(number, decimals) - local power = 10^decimals - return math.floor(number * power) / power +routines.utils.round = function( number, decimals ) + local power = 10 ^ decimals + return math.floor( number * power ) / power end ---from http://lua-users.org/wiki/CopyTable -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 +-- from http://lua-users.org/wiki/CopyTable +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 - -- porting in Slmod's serialize_slmod2 -routines.utils.oneLineSerialize = function(tbl) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function +routines.utils.oneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function - lookup_table = {} - - local function _Serialize( tbl ) + lookup_table = {} - if type(tbl) == 'table' then --function only works for tables! - - if lookup_table[tbl] then - return lookup_table[object] - end + local function _Serialize( tbl ) - local tbl_str = {} - - lookup_table[tbl] = tbl_str - - tbl_str[#tbl_str + 1] = '{' + if type( tbl ) == 'table' then -- function only works for tables! - for ind,val in pairs(tbl) do -- serialize its fields - 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 --must be a string - ind_str[#ind_str + 1] = '[' - ind_str[#ind_str + 1] = routines.utils.basicSerialize(ind) - ind_str[#ind_str + 1] = ']=' - end + if lookup_table[tbl] then + return lookup_table[object] + 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 -- won't ever happen, right? - 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 - -- tbl_str[#tbl_str + 1] = "__index" - -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it - else + local tbl_str = {} - val_str[#val_str + 1] = _Serialize(val) - val_str[#val_str + 1] = ',' --I think this is right, I just added it - 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] = "function " .. tostring(ind) - -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it - 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 - if type(tbl) == 'string' then - return tbl - else - return tostring(tbl) - end - end - end - - local objectreturn = _Serialize(tbl) - return objectreturn + lookup_table[tbl] = tbl_str + + tbl_str[#tbl_str + 1] = '{' + + for ind, val in pairs( tbl ) do -- serialize its fields + 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 -- must be a string + 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 -- won't ever happen, right? + 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 + -- tbl_str[#tbl_str + 1] = "__index" + -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it + else + + val_str[#val_str + 1] = _Serialize( val ) + val_str[#val_str + 1] = ',' -- I think this is right, I just added it + 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] = "function " .. tostring(ind) + -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it + 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 + if type( tbl ) == 'string' then + return tbl + else + return tostring( tbl ) + end + end + end + + local objectreturn = _Serialize( tbl ) + return objectreturn end ---porting in Slmod's "safestring" basic serialize -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('%s', s:gsub( "%%", "%%%%" ) ) - return s - end - end +-- porting in Slmod's "safestring" basic serialize +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( '%s', s:gsub( "%%", "%%%%" ) ) + return s + end + end end - -routines.utils.toDegree = function(angle) - return angle*180/math.pi +routines.utils.toDegree = function( angle ) + return angle * 180 / math.pi end -routines.utils.toRadian = function(angle) - return angle*math.pi/180 +routines.utils.toRadian = function( angle ) + return angle * math.pi / 180 end -routines.utils.metersToNM = function(meters) - return meters/1852 +routines.utils.metersToNM = function( meters ) + return meters / 1852 end -routines.utils.metersToFeet = function(meters) - return meters/0.3048 +routines.utils.metersToFeet = function( meters ) + return meters / 0.3048 end -routines.utils.NMToMeters = function(NM) - return NM*1852 +routines.utils.NMToMeters = function( NM ) + return NM * 1852 end -routines.utils.feetToMeters = function(feet) - return feet*0.3048 +routines.utils.feetToMeters = function( feet ) + return feet * 0.3048 end -routines.utils.mpsToKnots = function(mps) - return mps*3600/1852 +routines.utils.mpsToKnots = function( mps ) + return mps * 3600 / 1852 end -routines.utils.mpsToKmph = function(mps) - return mps*3.6 +routines.utils.mpsToKmph = function( mps ) + return mps * 3.6 end -routines.utils.knotsToMps = function(knots) - return knots*1852/3600 +routines.utils.knotsToMps = function( knots ) + return knots * 1852 / 3600 end -routines.utils.kmphToMps = function(kmph) - return kmph/3.6 +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} -- it was actually already vec2. - 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 } -- it was actually already vec2. + 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} -- it was already Vec3, actually. - 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 } -- it was already Vec3, actually. + end end -function routines.utils.makeVec3GL(Vec2, offset) - local adj = offset or 0 +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 + 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 +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 -- gets heading-error corrected direction from point along vector vec. -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 -- put dir in range of 0 to 2*pi - end - return dir +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 -- put dir in range of 0 to 2*pi + end + return dir end -- gets distance in meters between two points (2 dimensional) -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}) +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 -- gets distance in meters between two points (3 dimensional) -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}) +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 - - - - ---3D Vector manipulation +-- 3D Vector manipulation routines.vec = {} -routines.vec.add = function(vec1, vec2) - return {x = vec1.x + vec2.x, y = vec1.y + vec2.y, z = vec1.z + vec2.z} +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} +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} +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 +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} +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 +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 } +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)} +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 --------------------------------------------------------------------------------------------------------------------------- - - - -- acc- the accuracy of each easting/northing. 0, 1, 2, 3, 4, or 5. -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 +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 --[[acc: @@ -315,86 +303,84 @@ position after the decimal of the least significant digit: So: 42.32 - acc of 2. ]] -routines.tostringLL = function(lat, lon, acc, DMS) +routines.tostringLL = function( lat, lon, acc, DMS ) - local latHemi, lonHemi - if lat > 0 then - latHemi = 'N' - else - latHemi = 'S' - end + local latHemi, lonHemi + if lat > 0 then + latHemi = 'N' + else + latHemi = 'S' + end - if lon > 0 then - lonHemi = 'E' - else - lonHemi = 'W' - end + if lon > 0 then + lonHemi = 'E' + else + lonHemi = 'W' + end - lat = math.abs(lat) - lon = math.abs(lon) + lat = math.abs( lat ) + lon = math.abs( lon ) - local latDeg = math.floor(lat) - local latMin = (lat - latDeg)*60 + local latDeg = math.floor( lat ) + local latMin = (lat - latDeg) * 60 - local lonDeg = math.floor(lon) - local lonMin = (lon - lonDeg)*60 + local lonDeg = math.floor( lon ) + local lonMin = (lon - lonDeg) * 60 - if DMS then -- degrees, minutes, and seconds. - local oldLatMin = latMin - latMin = math.floor(latMin) - local latSec = routines.utils.round((oldLatMin - latMin)*60, acc) + if DMS then -- degrees, minutes, and seconds. + 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) + 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 latSec == 60 then + latSec = 0 + latMin = latMin + 1 + end - if lonSec == 60 then - lonSec = 0 - lonMin = lonMin + 1 - end + if lonSec == 60 then + lonSec = 0 + lonMin = lonMin + 1 + end - local secFrmtStr -- create the formatting string for the seconds place - if acc <= 0 then -- no decimal place. - secFrmtStr = '%02d' - else - local width = 3 + acc -- 01.310 - that's a width of 6, for example. - secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' - end + local secFrmtStr -- create the formatting string for the seconds place + if acc <= 0 then -- no decimal place. + secFrmtStr = '%02d' + else + local width = 3 + acc -- 01.310 - that's a width of 6, for example. + 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 + 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 -- degrees, decimal minutes. - latMin = routines.utils.round(latMin, acc) - lonMin = routines.utils.round(lonMin, acc) + else -- degrees, decimal minutes. + latMin = routines.utils.round( latMin, acc ) + lonMin = routines.utils.round( lonMin, acc ) - if latMin == 60 then - latMin = 0 - latDeg = latDeg + 1 - end + if latMin == 60 then + latMin = 0 + latDeg = latDeg + 1 + end - if lonMin == 60 then - lonMin = 0 - lonDeg = lonDeg + 1 - end + if lonMin == 60 then + lonMin = 0 + lonDeg = lonDeg + 1 + end - local minFrmtStr -- create the formatting string for the minutes place - if acc <= 0 then -- no decimal place. - minFrmtStr = '%02d' - else - local width = 3 + acc -- 01.310 - that's a width of 6, for example. - minFrmtStr = '%0' .. width .. '.' .. acc .. 'f' - end + local minFrmtStr -- create the formatting string for the minutes place + if acc <= 0 then -- no decimal place. + minFrmtStr = '%02d' + else + local width = 3 + acc -- 01.310 - that's a width of 6, for example. + 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 + return string.format( '%02d', latDeg ) .. ' ' .. string.format( minFrmtStr, latMin ) .. '\'' .. latHemi .. ' ' .. string.format( '%02d', lonDeg ) .. ' ' .. string.format( minFrmtStr, lonMin ) .. '\'' .. lonHemi - end + end end --[[ required: az - radian @@ -402,115 +388,106 @@ end optional: alt - meters (set to false or nil if you don't want to use it). optional: metric - set true to get dist and alt in km and m. precision will always be nearest degree and NM or km.]] -routines.tostringBR = function(az, dist, alt, metric) - az = routines.utils.round(routines.utils.toDegree(az), 0) +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 + 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 + 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 + 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) --gets the correction needed for true north - if not point.z then --Vec2; convert to Vec3 - 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) +routines.getNorthCorrection = function( point ) -- gets the correction needed for true north + if not point.z then -- Vec2; convert to Vec3 + 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 + local idNum = 0 - --Simplified event handler - routines.addEventHandler = function(f) --id is optional! - local handler = {} - idNum = idNum + 1 - handler.id = idNum - handler.f = f - handler.onEvent = function(self, event) - self.f(event) - end - world.addEventHandler(handler) - end + -- Simplified event handler + routines.addEventHandler = function( f ) -- id is optional! + 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 + 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 -- need to return a Vec3 or Vec2? -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 +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 + local radMult + if innerRadius and innerRadius <= radius then + radMult = (radius - innerRadius) * rad + innerRadius + else + radMult = radius * rad + end - if not point.z then --might as well work with vec2/3 - point.z = point.y - end + if not point.z then -- might as well work with vec2/3 + 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 + 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 +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 + Controller.setTask( groupCon, misTask ) + return false end - -- Useful atomic functions from mist, ported. routines.ground = {} @@ -860,13 +837,13 @@ end vars.units - table of unit names (NOT unitNameTable- maybe this should change). vars.acc - integer between 0 and 5, inclusive ]] -routines.getMGRSString = function(vars) - local units = vars.units - local acc = vars.acc or 5 - local avgPos = routines.getAvgPos(units) - if avgPos then - return routines.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(avgPos)), acc) - end +routines.getMGRSString = function( vars ) + local units = vars.units + local acc = vars.acc or 5 + local avgPos = routines.getAvgPos( units ) + if avgPos then + return routines.tostringMGRS( coord.LLtoMGRS( coord.LOtoLL( avgPos ) ), acc ) + end end --[[ vars for routines.getLLString @@ -876,15 +853,15 @@ vars.DMS - if true, output in degrees, minutes, seconds. Otherwise, output in d ]] -routines.getLLString = function(vars) - local units = vars.units - local acc = vars.acc or 3 - local DMS = vars.DMS - local avgPos = routines.getAvgPos(units) - if avgPos then - local lat, lon = coord.LOtoLL(avgPos) - return routines.tostringLL(lat, lon, acc, DMS) - end +routines.getLLString = function( vars ) + local units = vars.units + local acc = vars.acc or 3 + local DMS = vars.DMS + local avgPos = routines.getAvgPos( units ) + if avgPos then + local lat, lon = coord.LOtoLL( avgPos ) + return routines.tostringLL( lat, lon, acc, DMS ) + end end --[[ @@ -893,22 +870,22 @@ vars.ref - vec3 ref point, maybe overload for vec2 as well? vars.alt - boolean, if used, includes altitude in string vars.metric - boolean, gives distance in km instead of NM. ]] -routines.getBRStringZone = function(vars) - local zone = trigger.misc.getZone( vars.zone ) - local ref = routines.utils.makeVec3(vars.ref, 0) -- turn it into Vec3 if it is not already. - local alt = vars.alt - local metric = vars.metric - if zone then - local vec = {x = zone.point.x - ref.x, y = zone.point.y - ref.y, z = zone.point.z - ref.z} - local dir = routines.utils.getDir(vec, ref) - local dist = routines.utils.get2DDist(zone.point, ref) - if alt then - alt = zone.y - end - return routines.tostringBR(dir, dist, alt, metric) - else - env.info( 'routines.getBRStringZone: error: zone is nil' ) - end +routines.getBRStringZone = function( vars ) + local zone = trigger.misc.getZone( vars.zone ) + local ref = routines.utils.makeVec3( vars.ref, 0 ) -- turn it into Vec3 if it is not already. + local alt = vars.alt + local metric = vars.metric + if zone then + local vec = { x = zone.point.x - ref.x, y = zone.point.y - ref.y, z = zone.point.z - ref.z } + local dir = routines.utils.getDir( vec, ref ) + local dist = routines.utils.get2DDist( zone.point, ref ) + if alt then + alt = zone.y + end + return routines.tostringBR( dir, dist, alt, metric ) + else + env.info( 'routines.getBRStringZone: error: zone is nil' ) + end end --[[ @@ -917,24 +894,23 @@ vars.ref - vec3 ref point, maybe overload for vec2 as well? vars.alt - boolean, if used, includes altitude in string vars.metric - boolean, gives distance in km instead of NM. ]] -routines.getBRString = function(vars) - local units = vars.units - local ref = routines.utils.makeVec3(vars.ref, 0) -- turn it into Vec3 if it is not already. - local alt = vars.alt - local metric = vars.metric - local avgPos = routines.getAvgPos(units) - if avgPos then - local vec = {x = avgPos.x - ref.x, y = avgPos.y - ref.y, z = avgPos.z - ref.z} - local dir = routines.utils.getDir(vec, ref) - local dist = routines.utils.get2DDist(avgPos, ref) - if alt then - alt = avgPos.y - end - return routines.tostringBR(dir, dist, alt, metric) - end +routines.getBRString = function( vars ) + local units = vars.units + local ref = routines.utils.makeVec3( vars.ref, 0 ) -- turn it into Vec3 if it is not already. + local alt = vars.alt + local metric = vars.metric + local avgPos = routines.getAvgPos( units ) + if avgPos then + local vec = { x = avgPos.x - ref.x, y = avgPos.y - ref.y, z = avgPos.z - ref.z } + local dir = routines.utils.getDir( vec, ref ) + local dist = routines.utils.get2DDist( avgPos, ref ) + if alt then + alt = avgPos.y + end + return routines.tostringBR( dir, dist, alt, metric ) + end end - -- Returns the Vec3 coordinates of the average position of the concentration of units most in the heading direction. --[[ vars for routines.getLeadingPos: vars.units - table of unit names @@ -942,57 +918,56 @@ vars.heading - direction vars.radius - number vars.headingDegrees - boolean, switches heading to degrees ]] -routines.getLeadingPos = function(vars) - local units = vars.units - local heading = vars.heading - local radius = vars.radius - if vars.headingDegrees then - heading = routines.utils.toRadian(vars.headingDegrees) - end +routines.getLeadingPos = function( vars ) + local units = vars.units + local heading = vars.heading + local radius = vars.radius + if vars.headingDegrees then + heading = routines.utils.toRadian( vars.headingDegrees ) + end - local unitPosTbl = {} - for i = 1, #units do - local unit = Unit.getByName(units[i]) - if unit and unit:isExist() then - unitPosTbl[#unitPosTbl + 1] = unit:getPosition().p - end - end - if #unitPosTbl > 0 then -- one more more units found. - -- first, find the unit most in the heading direction - local maxPos = -math.huge + local unitPosTbl = {} + for i = 1, #units do + local unit = Unit.getByName( units[i] ) + if unit and unit:isExist() then + unitPosTbl[#unitPosTbl + 1] = unit:getPosition().p + end + end + if #unitPosTbl > 0 then -- one more more units found. + -- first, find the unit most in the heading direction + local maxPos = -math.huge - local maxPosInd -- maxPos - the furthest in direction defined by heading; maxPosInd = - for i = 1, #unitPosTbl do - local rotatedVec2 = routines.vec.rotateVec2(routines.utils.makeVec2(unitPosTbl[i]), heading) - if (not maxPos) or maxPos < rotatedVec2.x then - maxPos = rotatedVec2.x - maxPosInd = i - end - end + local maxPosInd -- maxPos - the furthest in direction defined by heading; maxPosInd = + for i = 1, #unitPosTbl do + local rotatedVec2 = routines.vec.rotateVec2( routines.utils.makeVec2( unitPosTbl[i] ), heading ) + if (not maxPos) or maxPos < rotatedVec2.x then + maxPos = rotatedVec2.x + maxPosInd = i + end + end - --now, get all the units around this unit... - local avgPos - if radius then - local maxUnitPos = unitPosTbl[maxPosInd] - local avgx, avgy, avgz, totNum = 0, 0, 0, 0 - for i = 1, #unitPosTbl do - if routines.utils.get2DDist(maxUnitPos, unitPosTbl[i]) <= radius then - avgx = avgx + unitPosTbl[i].x - avgy = avgy + unitPosTbl[i].y - avgz = avgz + unitPosTbl[i].z - totNum = totNum + 1 - end - end - avgPos = { x = avgx/totNum, y = avgy/totNum, z = avgz/totNum} - else - avgPos = unitPosTbl[maxPosInd] - end + -- now, get all the units around this unit... + local avgPos + if radius then + local maxUnitPos = unitPosTbl[maxPosInd] + local avgx, avgy, avgz, totNum = 0, 0, 0, 0 + for i = 1, #unitPosTbl do + if routines.utils.get2DDist( maxUnitPos, unitPosTbl[i] ) <= radius then + avgx = avgx + unitPosTbl[i].x + avgy = avgy + unitPosTbl[i].y + avgz = avgz + unitPosTbl[i].z + totNum = totNum + 1 + end + end + avgPos = { x = avgx / totNum, y = avgy / totNum, z = avgz / totNum } + else + avgPos = unitPosTbl[maxPosInd] + end - return avgPos - end + return avgPos + end end - --[[ vars for routines.getLeadingMGRSString: vars.units - table of unit names vars.heading - direction @@ -1000,12 +975,12 @@ vars.radius - number vars.headingDegrees - boolean, switches heading to degrees vars.acc - number, 0 to 5. ]] -routines.getLeadingMGRSString = function(vars) - local pos = routines.getLeadingPos(vars) - if pos then - local acc = vars.acc or 5 - return routines.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(pos)), acc) - end +routines.getLeadingMGRSString = function( vars ) + local pos = routines.getLeadingPos( vars ) + if pos then + local acc = vars.acc or 5 + return routines.tostringMGRS( coord.LLtoMGRS( coord.LOtoLL( pos ) ), acc ) + end end --[[ vars for routines.getLeadingLLString: @@ -1016,18 +991,16 @@ vars.headingDegrees - boolean, switches heading to degrees vars.acc - number of digits after decimal point (can be negative) vars.DMS - boolean, true if you want DMS. ]] -routines.getLeadingLLString = function(vars) - local pos = routines.getLeadingPos(vars) - if pos then - local acc = vars.acc or 3 - local DMS = vars.DMS - local lat, lon = coord.LOtoLL(pos) - return routines.tostringLL(lat, lon, acc, DMS) - end +routines.getLeadingLLString = function( vars ) + local pos = routines.getLeadingPos( vars ) + if pos then + local acc = vars.acc or 3 + local DMS = vars.DMS + local lat, lon = coord.LOtoLL( pos ) + return routines.tostringLL( lat, lon, acc, DMS ) + end end - - --[[ vars for routines.getLeadingBRString: vars.units - table of unit names vars.heading - direction, number @@ -1037,21 +1010,21 @@ vars.metric - boolean, if true, use km instead of NM. vars.alt - boolean, if true, include altitude. vars.ref - vec3/vec2 reference point. ]] -routines.getLeadingBRString = function(vars) - local pos = routines.getLeadingPos(vars) - if pos then - local ref = vars.ref - local alt = vars.alt - local metric = vars.metric +routines.getLeadingBRString = function( vars ) + local pos = routines.getLeadingPos( vars ) + if pos then + local ref = vars.ref + local alt = vars.alt + local metric = vars.metric - local vec = {x = pos.x - ref.x, y = pos.y - ref.y, z = pos.z - ref.z} - local dir = routines.utils.getDir(vec, ref) - local dist = routines.utils.get2DDist(pos, ref) - if alt then - alt = pos.y - end - return routines.tostringBR(dir, dist, alt, metric) - end + local vec = { x = pos.x - ref.x, y = pos.y - ref.y, z = pos.z - ref.z } + local dir = routines.utils.getDir( vec, ref ) + local dist = routines.utils.get2DDist( pos, ref ) + if alt then + alt = pos.y + end + return routines.tostringBR( dir, dist, alt, metric ) + end end --[[ vars for routines.message.add @@ -1068,26 +1041,22 @@ vars.text - text in the message vars.displayTime - self explanatory vars.msgFor - scope ]] -routines.msgMGRS = function(vars) - local units = vars.units - local acc = vars.acc - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor +routines.msgMGRS = function( vars ) + local units = vars.units + local acc = vars.acc + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor - local s = routines.getMGRSString{units = units, acc = acc} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end + local s = routines.getMGRSString { units = units, acc = acc } + local newText + if string.find( text, '%%s' ) then -- look for %s + newText = string.format( text, s ) -- insert the coordinates into the message + else -- else, just append to the end. + newText = text .. s + end - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } + routines.message.add { text = newText, displayTime = displayTime, msgFor = msgFor } end --[[ vars for routines.msgLL @@ -1098,31 +1067,26 @@ vars.text - text in the message vars.displayTime - self explanatory vars.msgFor - scope ]] -routines.msgLL = function(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local acc = vars.acc - local DMS = vars.DMS - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor +routines.msgLL = function( vars ) + local units = vars.units -- technically, I don't really need to do this, but it helps readability. + local acc = vars.acc + local DMS = vars.DMS + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor - local s = routines.getLLString{units = units, acc = acc, DMS = DMS} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end + local s = routines.getLLString { units = units, acc = acc, DMS = DMS } + local newText + if string.find( text, '%%s' ) then -- look for %s + newText = string.format( text, s ) -- insert the coordinates into the message + else -- else, just append to the end. + newText = text .. s + end - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } + routines.message.add { text = newText, displayTime = displayTime, msgFor = msgFor } end - --[[ vars.units- table of unit names (NOT unitNameTable- maybe this should change). vars.ref - vec3 ref point, maybe overload for vec2 as well? @@ -1132,32 +1096,27 @@ vars.text - text of the message vars.displayTime vars.msgFor - scope ]] -routines.msgBR = function(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local ref = vars.ref -- vec2/vec3 will be handled in routines.getBRString - local alt = vars.alt - local metric = vars.metric - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor +routines.msgBR = function( vars ) + local units = vars.units -- technically, I don't really need to do this, but it helps readability. + local ref = vars.ref -- vec2/vec3 will be handled in routines.getBRString + local alt = vars.alt + local metric = vars.metric + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor - local s = routines.getBRString{units = units, ref = ref, alt = alt, metric = metric} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end + local s = routines.getBRString { units = units, ref = ref, alt = alt, metric = metric } + local newText + if string.find( text, '%%s' ) then -- look for %s + newText = string.format( text, s ) -- insert the coordinates into the message + else -- else, just append to the end. + newText = text .. s + end - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } + routines.message.add { text = newText, displayTime = displayTime, msgFor = msgFor } end - -------------------------------------------------------------------------------------------- -- basically, just sub-types of routines.msgBR... saves folks the work of getting the ref point. --[[ @@ -1169,14 +1128,14 @@ vars.text - text of the message vars.displayTime vars.msgFor - scope ]] -routines.msgBullseye = function(vars) - if string.lower(vars.ref) == 'red' then - vars.ref = routines.DBs.missionData.bullseye.red - routines.msgBR(vars) - elseif string.lower(vars.ref) == 'blue' then - vars.ref = routines.DBs.missionData.bullseye.blue - routines.msgBR(vars) - end +routines.msgBullseye = function( vars ) + if string.lower( vars.ref ) == 'red' then + vars.ref = routines.DBs.missionData.bullseye.red + routines.msgBR( vars ) + elseif string.lower( vars.ref ) == 'blue' then + vars.ref = routines.DBs.missionData.bullseye.blue + routines.msgBR( vars ) + end end --[[ @@ -1189,14 +1148,14 @@ vars.displayTime vars.msgFor - scope ]] -routines.msgBRA = function(vars) - if Unit.getByName(vars.ref) then - vars.ref = Unit.getByName(vars.ref):getPosition().p - if not vars.alt then - vars.alt = true - end - routines.msgBR(vars) - end +routines.msgBRA = function( vars ) + if Unit.getByName( vars.ref ) then + vars.ref = Unit.getByName( vars.ref ):getPosition().p + if not vars.alt then + vars.alt = true + end + routines.msgBR( vars ) + end end -------------------------------------------------------------------------------------------- @@ -1210,32 +1169,27 @@ vars.text - text of the message vars.displayTime vars.msgFor - scope ]] -routines.msgLeadingMGRS = function(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local heading = vars.heading - local radius = vars.radius - local headingDegrees = vars.headingDegrees - local acc = vars.acc - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor - - local s = routines.getLeadingMGRSString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, acc = acc} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end - - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } +routines.msgLeadingMGRS = function( vars ) + local units = vars.units -- technically, I don't really need to do this, but it helps readability. + local heading = vars.heading + local radius = vars.radius + local headingDegrees = vars.headingDegrees + local acc = vars.acc + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor + local s = routines.getLeadingMGRSString { units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, acc = acc } + local newText + if string.find( text, '%%s' ) then -- look for %s + newText = string.format( text, s ) -- insert the coordinates into the message + else -- else, just append to the end. + newText = text .. s + end + routines.message.add { text = newText, displayTime = displayTime, msgFor = msgFor } end + --[[ vars for routines.msgLeadingLL: vars.units - table of unit names vars.heading - direction, number @@ -1247,31 +1201,26 @@ vars.text - text of the message vars.displayTime vars.msgFor - scope ]] -routines.msgLeadingLL = function(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local heading = vars.heading - local radius = vars.radius - local headingDegrees = vars.headingDegrees - local acc = vars.acc - local DMS = vars.DMS - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor +routines.msgLeadingLL = function( vars ) + local units = vars.units -- technically, I don't really need to do this, but it helps readability. + local heading = vars.heading + local radius = vars.radius + local headingDegrees = vars.headingDegrees + local acc = vars.acc + local DMS = vars.DMS + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor - local s = routines.getLeadingLLString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, acc = acc, DMS = DMS} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end - - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } + local s = routines.getLeadingLLString { units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, acc = acc, DMS = DMS } + local newText + if string.find( text, '%%s' ) then -- look for %s + newText = string.format( text, s ) -- insert the coordinates into the message + else -- else, just append to the end. + newText = text .. s + end + routines.message.add { text = newText, displayTime = displayTime, msgFor = msgFor } end --[[ @@ -1286,137 +1235,93 @@ vars.text - text of the message vars.displayTime vars.msgFor - scope ]] -routines.msgLeadingBR = function(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local heading = vars.heading - local radius = vars.radius - local headingDegrees = vars.headingDegrees - local metric = vars.metric - local alt = vars.alt - local ref = vars.ref -- vec2/vec3 will be handled in routines.getBRString - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor +routines.msgLeadingBR = function( vars ) + local units = vars.units -- technically, I don't really need to do this, but it helps readability. + local heading = vars.heading + local radius = vars.radius + local headingDegrees = vars.headingDegrees + local metric = vars.metric + local alt = vars.alt + local ref = vars.ref -- vec2/vec3 will be handled in routines.getBRString + local text = vars.text + local displayTime = vars.displayTime + local msgFor = vars.msgFor - local s = routines.getLeadingBRString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, metric = metric, alt = alt, ref = ref} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end + local s = routines.getLeadingBRString { units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, metric = metric, alt = alt, ref = ref } + local newText + if string.find( text, '%%s' ) then -- look for %s + newText = string.format( text, s ) -- insert the coordinates into the message + else -- else, just append to the end. + newText = text .. s + end - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } + routines.message.add { text = newText, displayTime = displayTime, msgFor = msgFor } end +function spairs( t, order ) + -- collect the keys + local keys = {} + for k in pairs( t ) do + keys[#keys + 1] = k + end -function spairs(t, order) - -- collect the keys - local keys = {} - for k in pairs(t) do keys[#keys+1] = k end + -- if order function given, sort by it by passing the table and keys a, b, + -- otherwise just sort the keys + if order then + table.sort( keys, function( a, b ) + return order( t, a, b ) + end ) + else + table.sort( keys ) + end - -- if order function given, sort by it by passing the table and keys a, b, - -- otherwise just sort the keys - if order then - table.sort(keys, function(a,b) return order(t, a, b) end) - else - table.sort(keys) - end - - -- return the iterator function - local i = 0 - return function() - i = i + 1 - if keys[i] then - return keys[i], t[keys[i]] - end + -- return the iterator function + local i = 0 + return function() + i = i + 1 + if keys[i] then + return keys[i], t[keys[i]] end + end end - function routines.IsPartOfGroupInZones( CargoGroup, LandingZones ) ---trace.f() + -- trace.f() - local CurrentZoneID = nil + local CurrentZoneID = nil - if CargoGroup then - local CargoUnits = CargoGroup:getUnits() - for CargoUnitID, CargoUnit in pairs( CargoUnits ) do - if CargoUnit and CargoUnit:getLife() >= 1.0 then - CurrentZoneID = routines.IsUnitInZones( CargoUnit, LandingZones ) - if CurrentZoneID then - break - end - end - end - end + if CargoGroup then + local CargoUnits = CargoGroup:getUnits() + for CargoUnitID, CargoUnit in pairs( CargoUnits ) do + if CargoUnit and CargoUnit:getLife() >= 1.0 then + CurrentZoneID = routines.IsUnitInZones( CargoUnit, LandingZones ) + if CurrentZoneID then + break + end + end + end + end ---trace.r( "", "", { CurrentZoneID } ) - return CurrentZoneID + -- trace.r( "", "", { CurrentZoneID } ) + return CurrentZoneID end - - function routines.IsUnitInZones( TransportUnit, LandingZones ) ---trace.f("", "routines.IsUnitInZones" ) - - local TransportZoneResult = nil - local TransportZonePos = nil - local TransportZone = nil - - -- fill-up some local variables to support further calculations to determine location of units within the zone. - 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 - --trace.i( "routines", "TransportZone:" .. TransportZoneResult ) - else - --trace.i( "routines", "TransportZone:nil logic" ) - end - return TransportZoneResult - else - --trace.i( "routines", "TransportZone:nil hard" ) - return nil - end -end - -function routines.IsUnitNearZonesRadius( TransportUnit, LandingZones, ZoneRadius ) ---trace.f("", "routines.IsUnitInZones" ) + -- trace.f("", "routines.IsUnitInZones" ) local TransportZoneResult = nil local TransportZonePos = nil local TransportZone = nil - -- fill-up some local variables to support further calculations to determine location of units within the zone. + -- fill-up some local variables to support further calculations to determine location of units within the zone. 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 + 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 @@ -1424,59 +1329,97 @@ function routines.IsUnitNearZonesRadius( TransportUnit, LandingZones, ZoneRadius 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 + 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 - --trace.i( "routines", "TransportZone:" .. TransportZoneResult ) + -- trace.i( "routines", "TransportZone:" .. TransportZoneResult ) else - --trace.i( "routines", "TransportZone:nil logic" ) + -- trace.i( "routines", "TransportZone:nil logic" ) end return TransportZoneResult else - --trace.i( "routines", "TransportZone:nil hard" ) + -- trace.i( "routines", "TransportZone:nil hard" ) return nil end end +function routines.IsUnitNearZonesRadius( TransportUnit, LandingZones, ZoneRadius ) + -- trace.f("", "routines.IsUnitInZones" ) -function routines.IsStaticInZones( TransportStatic, LandingZones ) ---trace.f() + local TransportZoneResult = nil + local TransportZonePos = nil + local TransportZone = nil - local TransportZoneResult = nil - local TransportZonePos = nil - local TransportZone = nil - - -- fill-up some local variables to support further calculations to determine location of units within the zone. - 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 - ---trace.r( "", "", { TransportZoneResult } ) + -- fill-up some local variables to support further calculations to determine location of units within the zone. + 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 + -- trace.i( "routines", "TransportZone:" .. TransportZoneResult ) + else + -- trace.i( "routines", "TransportZone:nil logic" ) + end return TransportZoneResult + else + -- trace.i( "routines", "TransportZone:nil hard" ) + return nil + end end +function routines.IsStaticInZones( TransportStatic, LandingZones ) + -- trace.f() + + local TransportZoneResult = nil + local TransportZonePos = nil + local TransportZone = nil + + -- fill-up some local variables to support further calculations to determine location of units within the zone. + 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 + + -- trace.r( "", "", { TransportZoneResult } ) + return TransportZoneResult +end function routines.IsUnitInRadius( CargoUnit, ReferencePosition, Radius ) ---trace.f() + -- trace.f() local Valid = true @@ -1484,7 +1427,7 @@ function routines.IsUnitInRadius( CargoUnit, ReferencePosition, Radius ) local CargoPos = CargoUnit:getPosition().p local ReferenceP = ReferencePosition.p - if (((CargoPos.x - ReferenceP.x)^2 + (CargoPos.z - ReferenceP.z)^2)^0.5 <= Radius) then + if (((CargoPos.x - ReferenceP.x) ^ 2 + (CargoPos.z - ReferenceP.z) ^ 2) ^ 0.5 <= Radius) then else Valid = false end @@ -1493,7 +1436,7 @@ function routines.IsUnitInRadius( CargoUnit, ReferencePosition, Radius ) end function routines.IsPartOfGroupInRadius( CargoGroup, ReferencePosition, Radius ) ---trace.f() + -- trace.f() local Valid = true @@ -1503,11 +1446,11 @@ function routines.IsPartOfGroupInRadius( CargoGroup, ReferencePosition, Radius ) local CargoUnits = CargoGroup:getUnits() for CargoUnitId, CargoUnit in pairs( CargoUnits ) do local CargoUnitPos = CargoUnit:getPosition().p --- env.info( 'routines.IsPartOfGroupInRadius: CargoUnitPos.x = ' .. CargoUnitPos.x .. ' CargoUnitPos.z = ' .. CargoUnitPos.z ) + -- env.info( 'routines.IsPartOfGroupInRadius: CargoUnitPos.x = ' .. CargoUnitPos.x .. ' CargoUnitPos.z = ' .. CargoUnitPos.z ) local ReferenceP = ReferencePosition.p --- env.info( 'routines.IsPartOfGroupInRadius: ReferenceGroupPos.x = ' .. ReferenceGroupPos.x .. ' ReferenceGroupPos.z = ' .. ReferenceGroupPos.z ) + -- env.info( 'routines.IsPartOfGroupInRadius: ReferenceGroupPos.x = ' .. ReferenceGroupPos.x .. ' ReferenceGroupPos.z = ' .. ReferenceGroupPos.z ) - if ((( CargoUnitPos.x - ReferenceP.x)^2 + (CargoUnitPos.z - ReferenceP.z)^2)^0.5 <= Radius) then + if (((CargoUnitPos.x - ReferenceP.x) ^ 2 + (CargoUnitPos.z - ReferenceP.z) ^ 2) ^ 0.5 <= Radius) then else Valid = false break @@ -1517,11 +1460,10 @@ function routines.IsPartOfGroupInRadius( CargoGroup, ReferencePosition, Radius ) return Valid end - function routines.ValidateString( Variable, VariableName, Valid ) ---trace.f() + -- trace.f() - if type( Variable ) == "string" then + if type( Variable ) == "string" then if Variable == "" then error( "routines.ValidateString: error: " .. VariableName .. " must be filled out!" ) Valid = false @@ -1531,65 +1473,64 @@ function routines.ValidateString( Variable, VariableName, Valid ) Valid = false end ---trace.r( "", "", { Valid } ) + -- trace.r( "", "", { Valid } ) return Valid end function routines.ValidateNumber( Variable, VariableName, Valid ) ---trace.f() + -- trace.f() - if type( Variable ) == "number" then + if type( Variable ) == "number" then else error( "routines.ValidateNumber: error: " .. VariableName .. " is not a number." ) Valid = false end ---trace.r( "", "", { Valid } ) + -- trace.r( "", "", { Valid } ) return Valid - end function routines.ValidateGroup( Variable, VariableName, Valid ) ---trace.f() + -- trace.f() - if Variable == nil then - error( "routines.ValidateGroup: error: " .. VariableName .. " is a nil value!" ) - Valid = false - end + if Variable == nil then + error( "routines.ValidateGroup: error: " .. VariableName .. " is a nil value!" ) + Valid = false + end ---trace.r( "", "", { Valid } ) - return Valid + -- trace.r( "", "", { Valid } ) + return Valid end function routines.ValidateZone( LandingZones, VariableName, Valid ) ---trace.f() + -- trace.f() - if LandingZones == nil then - error( "routines.ValidateGroup: error: " .. VariableName .. " is a nil value!" ) - Valid = false - end + 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 + 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 ---trace.r( "", "", { Valid } ) - return Valid + -- trace.r( "", "", { Valid } ) + return Valid end function routines.ValidateEnumeration( Variable, VariableName, Enum, Valid ) ---trace.f() + -- trace.f() local ValidVariable = false @@ -1600,214 +1541,191 @@ function routines.ValidateEnumeration( Variable, VariableName, Enum, Valid ) end end - if ValidVariable then + if ValidVariable then else error( 'TransportValidateEnum: " .. VariableName .. " is not a valid type.' .. Variable ) Valid = false end ---trace.r( "", "", { Valid } ) + -- trace.r( "", "", { Valid } ) return Valid end -function routines.getGroupRoute(groupIdent, task) -- same as getGroupPoints but returns speed and formation type along with vec2 of point} - -- refactor to search by groupId and allow groupId and groupName as inputs - 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 --there is a country table - 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 -- only these types have points - 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 --there's a group! - for group_num, group_data in pairs(obj_type_data.group) do - if group_data and group_data.groupId == gpId then -- this is the group we are looking for - 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 env.mission.version > 7 then - routeData.name = env.getValueDictByKey(point.name) - else - routeData.name = point.name - end - if not point.point then - routeData.x = point.x - routeData.y = point.y - else - routeData.point = point.point --it's possible that the ME could move to the point = Vec2 notation. - 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 --if group_data and group_data.name and group_data.name == 'groupname' - end --for group_num, group_data in pairs(obj_type_data.group) do - end --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 - end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then - end --for obj_type_name, obj_type_data in pairs(cntry_data) do - end --for cntry_id, cntry_data in pairs(coa_data.country) do - end --if coa_data.country then --there is a country table - end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then - end --for coa_name, coa_data in pairs(mission.coalition) do +function routines.getGroupRoute( groupIdent, task ) -- same as getGroupPoints but returns speed and formation type along with vec2 of point} + -- refactor to search by groupId and allow groupId and groupName as inputs + 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 -- there is a country table + 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 -- only these types have points + 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 -- there's a group! + for group_num, group_data in pairs( obj_type_data.group ) do + if group_data and group_data.groupId == gpId then -- this is the group we are looking for + 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 env.mission.version > 7 then + routeData.name = env.getValueDictByKey( point.name ) + else + routeData.name = point.name + end + if not point.point then + routeData.x = point.x + routeData.y = point.y + else + routeData.point = point.point -- it's possible that the ME could move to the point = Vec2 notation. + 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 -- if group_data and group_data.name and group_data.name == 'groupname' + end -- for group_num, group_data in pairs(obj_type_data.group) do + end -- 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 + end -- if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then + end -- for obj_type_name, obj_type_data in pairs(cntry_data) do + end -- for cntry_id, cntry_data in pairs(coa_data.country) do + end -- if coa_data.country then --there is a country table + end -- if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then + end -- for coa_name, coa_data in pairs(mission.coalition) do 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 -- make it so the first WP matches the last WP - 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, - - }, - }, - }, - } +routines.ground.patrolRoute = function( vars ) - - useRoute[#useRoute].task = tempTask - routines.goRoute(gpData, useRoute) - - return + 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 -- make it so the first WP matches the last WP + 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 ) 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) +routines.ground.patrol = function( gpData, pType, form, speed ) + local vars = {} - return + 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 ) end function routines.GetUnitHeight( CheckUnit ) ---trace.f( "routines" ) + -- trace.f( "routines" ) - local UnitPoint = CheckUnit:getPoint() - local UnitPosition = { x = UnitPoint.x, y = UnitPoint.z } - local UnitHeight = UnitPoint.y + local UnitPoint = CheckUnit:getPoint() + local UnitPosition = { x = UnitPoint.x, y = UnitPoint.z } + local UnitHeight = UnitPoint.y - local LandHeight = land.getHeight( UnitPosition ) + local LandHeight = land.getHeight( UnitPosition ) - --env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) + -- env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) - --trace.f( "routines", "Unit Height = " .. UnitHeight - LandHeight ) - - return UnitHeight - LandHeight + -- trace.f( "routines", "Unit Height = " .. UnitHeight - LandHeight ) + return UnitHeight - LandHeight end - - Su34Status = { status = {} } boardMsgRed = { statusMsg = "" } boardMsgAll = { timeMsg = "" } @@ -1815,688 +1733,626 @@ SpawnSettings = {} Su34MenuPath = {} Su34Menus = 0 - -function Su34AttackCarlVinson(groupName) ---trace.menu("", "Su34AttackCarlVinson") - 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 ) +function Su34AttackCarlVinson( groupName ) + -- trace.menu("", "Su34AttackCarlVinson") + 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) ---trace.f("","Su34AttackWest") - 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 ) +function Su34AttackWest( groupName ) + -- trace.f("","Su34AttackWest") + 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) ---trace.menu("","Su34AttackNorth") - 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 ) +function Su34AttackNorth( groupName ) + -- trace.menu("","Su34AttackNorth") + 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) ---trace.menu("","Su34Orbit") - 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 ) +function Su34Orbit( groupName ) + -- trace.menu("","Su34Orbit") + 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) ---trace.menu("","Su34TakeOff") - 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 ) +function Su34TakeOff( groupName ) + -- trace.menu("","Su34TakeOff") + 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) ---trace.menu("","Su34Hold") - 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 ) +function Su34Hold( groupName ) + -- trace.menu("","Su34Hold") + 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) ---trace.menu("","Su34RTB") - Su34Status.status[groupName] = 6 - MessageToRed( string.format('%s: ',groupName) .. 'Return to Krasnodar. ', 10, 'RedStatus' .. groupName ) +function Su34RTB( groupName ) + -- trace.menu("","Su34RTB") + Su34Status.status[groupName] = 6 + MessageToRed( string.format( '%s: ', groupName ) .. 'Return to Krasnodar. ', 10, 'RedStatus' .. groupName ) end -function Su34Destroyed(groupName) ---trace.menu("","Su34Destroyed") - Su34Status.status[groupName] = 7 - MessageToRed( string.format('%s: ',groupName) .. 'Destroyed. ', 30, 'RedStatus' .. groupName ) +function Su34Destroyed( groupName ) + -- trace.menu("","Su34Destroyed") + Su34Status.status[groupName] = 7 + MessageToRed( string.format( '%s: ', groupName ) .. 'Destroyed. ', 30, 'RedStatus' .. groupName ) end function GroupAlive( groupName ) ---trace.menu("","GroupAlive") - local groupTest = Group.getByName( groupName ) + -- trace.menu("","GroupAlive") + local groupTest = Group.getByName( groupName ) - local groupExists = false + local groupExists = false - if groupTest then - groupExists = groupTest:isExist() - end + if groupTest then + groupExists = groupTest:isExist() + end - --trace.r( "", "", { groupExists } ) - return groupExists + -- trace.r( "", "", { groupExists } ) + return groupExists end function Su34IsDead() ---trace.f() + -- trace.f() end function Su34OverviewStatus() ---trace.menu("","Su34OverviewStatus") - local msg = "" - local currentStatus = 0 - local Exists = false + -- trace.menu("","Su34OverviewStatus") + local msg = "" + local currentStatus = 0 + local Exists = false - for groupName, currentStatus in pairs(Su34Status.status) do + for groupName, currentStatus in pairs( Su34Status.status ) do - env.info(('Su34 Overview Status: GroupName = ' .. groupName )) - Alive = GroupAlive( groupName ) + 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 + 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 + boardMsgRed.statusMsg = msg end - function UpdateBoardMsg() ---trace.f() - Su34OverviewStatus() - MessageToRed( boardMsgRed.statusMsg, 15, 'RedStatus' ) + -- trace.f() + Su34OverviewStatus() + MessageToRed( boardMsgRed.statusMsg, 15, 'RedStatus' ) end function MusicReset( flg ) ---trace.f() - trigger.action.setUserFlag(95,flg) + -- trace.f() + trigger.action.setUserFlag( 95, flg ) end -function PlaneActivate(groupNameFormat, flg) ---trace.f() - local groupName = groupNameFormat .. string.format("#%03d", trigger.misc.getUserFlag(flg)) - --trigger.action.outText(groupName,10) - trigger.action.activateGroup(Group.getByName(groupName)) +function PlaneActivate( groupNameFormat, flg ) + -- trace.f() + local groupName = groupNameFormat .. string.format( "#%03d", trigger.misc.getUserFlag( flg ) ) + -- trigger.action.outText(groupName,10) + trigger.action.activateGroup( Group.getByName( groupName ) ) end -function Su34Menu(groupName) ---trace.f() +function Su34Menu( groupName ) + -- trace.f() - --env.info(( 'Su34Menu(' .. groupName .. ')' )) - local groupSu34 = Group.getByName( groupName ) + -- env.info(( '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 - ) + 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 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 west", Su34MenuPath[groupName], Su34AttackWest, groupName ) - missionCommands.addCommandForCoalition( - coalition.side.RED, - "Attack ships in the north", - Su34MenuPath[groupName], - Su34AttackNorth, - 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, "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 + 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 --- Obsolete function, but kept to rework in framework. -function ChooseInfantry ( TeleportPrefixTable, TeleportMax ) ---trace.f("Spawn") - --env.info(( 'ChooseInfantry: ' )) +function ChooseInfantry( TeleportPrefixTable, TeleportMax ) + -- trace.f("Spawn") + -- env.info(( 'ChooseInfantry: ' )) - TeleportPrefixTableCount = #TeleportPrefixTable - TeleportPrefixTableIndex = math.random( 1, TeleportPrefixTableCount ) + TeleportPrefixTableCount = #TeleportPrefixTable + TeleportPrefixTableIndex = math.random( 1, TeleportPrefixTableCount ) - --env.info(( 'ChooseInfantry: TeleportPrefixTableIndex = ' .. TeleportPrefixTableIndex .. ' TeleportPrefixTableCount = ' .. TeleportPrefixTableCount .. ' TeleportMax = ' .. TeleportMax )) + -- env.info(( 'ChooseInfantry: TeleportPrefixTableIndex = ' .. TeleportPrefixTableIndex .. ' TeleportPrefixTableCount = ' .. TeleportPrefixTableCount .. ' TeleportMax = ' .. TeleportMax )) - local TeleportFound = false - local TeleportLoop = true - local Index = TeleportPrefixTableIndex - local TeleportPrefix = '' + 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'] - 1 < TeleportMax then - SpawnSettings[TeleportPrefix]['SpawnCount'] = SpawnSettings[TeleportPrefix]['SpawnCount'] + 1 - TeleportFound = true - else - TeleportFound = false - end - else - SpawnSettings[TeleportPrefix] = {} - SpawnSettings[TeleportPrefix]['SpawnCount'] = 0 - TeleportFound = true - end - if TeleportFound then - TeleportLoop = false - else - if Index < TeleportPrefixTableCount then - Index = Index + 1 - else - TeleportLoop = false - end - end - --env.info(( 'ChooseInfantry: Loop 1 - TeleportPrefix = ' .. TeleportPrefix .. ' Index = ' .. Index )) - end + while TeleportLoop do + TeleportPrefix = TeleportPrefixTable[Index] + if SpawnSettings[TeleportPrefix] then + if SpawnSettings[TeleportPrefix]['SpawnCount'] - 1 < TeleportMax then + SpawnSettings[TeleportPrefix]['SpawnCount'] = SpawnSettings[TeleportPrefix]['SpawnCount'] + 1 + TeleportFound = true + else + TeleportFound = false + end + else + SpawnSettings[TeleportPrefix] = {} + SpawnSettings[TeleportPrefix]['SpawnCount'] = 0 + TeleportFound = true + end + if TeleportFound then + TeleportLoop = false + else + if Index < TeleportPrefixTableCount then + Index = Index + 1 + else + TeleportLoop = false + end + end + -- env.info(( 'ChooseInfantry: Loop 1 - TeleportPrefix = ' .. TeleportPrefix .. ' Index = ' .. Index )) + end - if TeleportFound == false then - TeleportLoop = true - Index = 1 - while TeleportLoop do - TeleportPrefix = TeleportPrefixTable[Index] - if SpawnSettings[TeleportPrefix] then - if SpawnSettings[TeleportPrefix]['SpawnCount'] - 1 < TeleportMax then - SpawnSettings[TeleportPrefix]['SpawnCount'] = SpawnSettings[TeleportPrefix]['SpawnCount'] + 1 - TeleportFound = true - else - TeleportFound = false - end - else - SpawnSettings[TeleportPrefix] = {} - SpawnSettings[TeleportPrefix]['SpawnCount'] = 0 - TeleportFound = true - end - if TeleportFound then - TeleportLoop = false - else - if Index < TeleportPrefixTableIndex then - Index = Index + 1 - else - TeleportLoop = false - end - end - --env.info(( 'ChooseInfantry: Loop 2 - TeleportPrefix = ' .. TeleportPrefix .. ' Index = ' .. Index )) - end - end + if TeleportFound == false then + TeleportLoop = true + Index = 1 + while TeleportLoop do + TeleportPrefix = TeleportPrefixTable[Index] + if SpawnSettings[TeleportPrefix] then + if SpawnSettings[TeleportPrefix]['SpawnCount'] - 1 < TeleportMax then + SpawnSettings[TeleportPrefix]['SpawnCount'] = SpawnSettings[TeleportPrefix]['SpawnCount'] + 1 + TeleportFound = true + else + TeleportFound = false + end + else + SpawnSettings[TeleportPrefix] = {} + SpawnSettings[TeleportPrefix]['SpawnCount'] = 0 + TeleportFound = true + end + if TeleportFound then + TeleportLoop = false + else + if Index < TeleportPrefixTableIndex then + Index = Index + 1 + else + TeleportLoop = false + end + end + -- env.info(( 'ChooseInfantry: Loop 2 - TeleportPrefix = ' .. TeleportPrefix .. ' Index = ' .. Index )) + end + end - local TeleportGroupName = '' - if TeleportFound == true then - TeleportGroupName = TeleportPrefix .. string.format("#%03d", SpawnSettings[TeleportPrefix]['SpawnCount'] ) - else - TeleportGroupName = '' - end + local TeleportGroupName = '' + if TeleportFound == true then + TeleportGroupName = TeleportPrefix .. string.format( "#%03d", SpawnSettings[TeleportPrefix]['SpawnCount'] ) + else + TeleportGroupName = '' + end - --env.info(('ChooseInfantry: TeleportGroupName = ' .. TeleportGroupName )) - --env.info(('ChooseInfantry: return')) + -- env.info(('ChooseInfantry: TeleportGroupName = ' .. TeleportGroupName )) + -- env.info(('ChooseInfantry: return')) - return TeleportGroupName + return TeleportGroupName end SpawnedInfantry = 0 -function LandCarrier ( CarrierGroup, LandingZonePrefix ) ---trace.f() - --env.info(( 'LandCarrier: ' )) - --env.info(( 'LandCarrier: CarrierGroup = ' .. CarrierGroup:getName() )) - --env.info(( 'LandCarrier: LandingZone = ' .. LandingZonePrefix )) +function LandCarrier( CarrierGroup, LandingZonePrefix ) + -- trace.f() + -- env.info(( 'LandCarrier: ' )) + -- env.info(( 'LandCarrier: CarrierGroup = ' .. CarrierGroup:getName() )) + -- env.info(( 'LandCarrier: LandingZone = ' .. LandingZonePrefix )) - local controllerGroup = CarrierGroup:getController() + local controllerGroup = CarrierGroup:getController() - local LandingZone = trigger.misc.getZone(LandingZonePrefix) - local LandingZonePos = {} - LandingZonePos.x = LandingZone.point.x + math.random(LandingZone.radius * -1, LandingZone.radius) - LandingZonePos.y = LandingZone.point.z + math.random(LandingZone.radius * -1, LandingZone.radius) + local LandingZone = trigger.misc.getZone( LandingZonePrefix ) + local LandingZonePos = {} + LandingZonePos.x = LandingZone.point.x + math.random( LandingZone.radius * -1, LandingZone.radius ) + LandingZonePos.y = LandingZone.point.z + math.random( LandingZone.radius * -1, LandingZone.radius ) - controllerGroup:pushTask( { id = 'Land', params = { point = LandingZonePos, durationFlag = true, duration = 10 } } ) + controllerGroup:pushTask( { id = 'Land', params = { point = LandingZonePos, durationFlag = true, duration = 10 } } ) - --env.info(( 'LandCarrier: end' )) + -- env.info(( 'LandCarrier: end' )) end EscortCount = 0 -function EscortCarrier ( CarrierGroup, EscortPrefix, EscortLastWayPoint, EscortEngagementDistanceMax, EscortTargetTypes ) ---trace.f() - --env.info(( 'EscortCarrier: ' )) - --env.info(( 'EscortCarrier: CarrierGroup = ' .. CarrierGroup:getName() )) - --env.info(( 'EscortCarrier: EscortPrefix = ' .. EscortPrefix )) +function EscortCarrier( CarrierGroup, EscortPrefix, EscortLastWayPoint, EscortEngagementDistanceMax, EscortTargetTypes ) + -- trace.f() + -- env.info(( 'EscortCarrier: ' )) + -- env.info(( 'EscortCarrier: CarrierGroup = ' .. CarrierGroup:getName() )) + -- env.info(( 'EscortCarrier: EscortPrefix = ' .. EscortPrefix )) - local CarrierName = CarrierGroup:getName() + local CarrierName = CarrierGroup:getName() - local EscortMission = {} - local CarrierMission = {} + local EscortMission = {} + local CarrierMission = {} - local EscortMission = SpawnMissionGroup( EscortPrefix ) - local CarrierMission = SpawnMissionGroup( CarrierGroup:getName() ) + local EscortMission = SpawnMissionGroup( EscortPrefix ) + local CarrierMission = SpawnMissionGroup( CarrierGroup:getName() ) - if EscortMission ~= nil and CarrierMission ~= nil then + if EscortMission ~= nil and CarrierMission ~= nil then - EscortCount = EscortCount + 1 - EscortMissionName = string.format( EscortPrefix .. '#Escort %s', CarrierName ) - EscortMission.name = EscortMissionName - EscortMission.groupId = nil - EscortMission.lateActivation = false - EscortMission.taskSelected = false + EscortCount = EscortCount + 1 + EscortMissionName = string.format( EscortPrefix .. '#Escort %s', CarrierName ) + EscortMission.name = EscortMissionName + EscortMission.groupId = nil + EscortMission.lateActivation = false + EscortMission.taskSelected = false - local EscortUnits = #EscortMission.units - for u = 1, EscortUnits do - EscortMission.units[u].name = string.format( EscortPrefix .. '#Escort %s %02d', CarrierName, u ) - EscortMission.units[u].unitId = nil - end + local EscortUnits = #EscortMission.units + for u = 1, EscortUnits do + EscortMission.units[u].name = string.format( EscortPrefix .. '#Escort %s %02d', CarrierName, u ) + EscortMission.units[u].unitId = nil + end + EscortMission.route.points[1].task = { + id = "ComboTask", + params = { + tasks = { + [1] = { + enabled = true, + auto = false, + id = "Escort", + number = 1, + params = { + lastWptIndexFlagChangedManually = false, + groupId = CarrierGroup:getID(), + lastWptIndex = nil, + lastWptIndexFlag = false, + engagementDistMax = EscortEngagementDistanceMax, + targetTypes = EscortTargetTypes, + pos = { y = 20, x = 20, z = 0 } -- end of ["pos"] + } -- end of ["params"] + } -- end of [1] + } -- end of ["tasks"] + } -- end of ["params"] + } -- end of ["task"] - EscortMission.route.points[1].task = { id = "ComboTask", - params = - { - tasks = - { - [1] = - { - enabled = true, - auto = false, - id = "Escort", - number = 1, - params = - { - lastWptIndexFlagChangedManually = false, - groupId = CarrierGroup:getID(), - lastWptIndex = nil, - lastWptIndexFlag = false, - engagementDistMax = EscortEngagementDistanceMax, - targetTypes = EscortTargetTypes, - pos = - { - y = 20, - x = 20, - z = 0, - } -- end of ["pos"] - } -- end of ["params"] - } -- end of [1] - } -- end of ["tasks"] - } -- end of ["params"] - } -- end of ["task"] + SpawnGroupAdd( EscortPrefix, EscortMission ) - SpawnGroupAdd( EscortPrefix, EscortMission ) - - end + end end function SendMessageToCarrier( CarrierGroup, CarrierMessage ) ---trace.f() + -- trace.f() - if CarrierGroup ~= nil then - MessageToGroup( CarrierGroup, CarrierMessage, 30, 'Carrier/' .. CarrierGroup:getName() ) - end + if CarrierGroup ~= nil then + MessageToGroup( CarrierGroup, CarrierMessage, 30, 'Carrier/' .. CarrierGroup:getName() ) + end end function MessageToGroup( MsgGroup, MsgText, MsgTime, MsgName ) ---trace.f() + -- trace.f() - if type(MsgGroup) == 'string' then - --env.info( 'MessageToGroup: Converted MsgGroup string "' .. MsgGroup .. '" into a Group structure.' ) - MsgGroup = Group.getByName( MsgGroup ) - end + if type( MsgGroup ) == 'string' then + -- env.info( 'MessageToGroup: Converted MsgGroup string "' .. MsgGroup .. '" into a Group structure.' ) + MsgGroup = Group.getByName( MsgGroup ) + end - if MsgGroup ~= nil then - local MsgTable = {} - MsgTable.text = MsgText - MsgTable.displayTime = MsgTime - MsgTable.msgFor = { units = { MsgGroup:getUnits()[1]:getName() } } - MsgTable.name = MsgName - --routines.message.add( MsgTable ) - --env.info(('MessageToGroup: Message sent to ' .. MsgGroup:getUnits()[1]:getName() .. ' -> ' .. MsgText )) - end + if MsgGroup ~= nil then + local MsgTable = {} + MsgTable.text = MsgText + MsgTable.displayTime = MsgTime + MsgTable.msgFor = { units = { MsgGroup:getUnits()[1]:getName() } } + MsgTable.name = MsgName + -- routines.message.add( MsgTable ) + -- env.info(('MessageToGroup: Message sent to ' .. MsgGroup:getUnits()[1]:getName() .. ' -> ' .. MsgText )) + end end function MessageToUnit( UnitName, MsgText, MsgTime, MsgName ) ---trace.f() + -- trace.f() - if UnitName ~= nil then - local MsgTable = {} - MsgTable.text = MsgText - MsgTable.displayTime = MsgTime - MsgTable.msgFor = { units = { UnitName } } - MsgTable.name = MsgName - --routines.message.add( MsgTable ) - end + if UnitName ~= nil then + local MsgTable = {} + MsgTable.text = MsgText + MsgTable.displayTime = MsgTime + MsgTable.msgFor = { units = { UnitName } } + MsgTable.name = MsgName + -- routines.message.add( MsgTable ) + end end function MessageToAll( MsgText, MsgTime, MsgName ) ---trace.f() + -- trace.f() - MESSAGE:New( MsgText, MsgTime, "Message" ):ToCoalition( coalition.side.RED ):ToCoalition( coalition.side.BLUE ) + MESSAGE:New( MsgText, MsgTime, "Message" ):ToCoalition( coalition.side.RED ):ToCoalition( coalition.side.BLUE ) end function MessageToRed( MsgText, MsgTime, MsgName ) ---trace.f() + -- trace.f() - MESSAGE:New( MsgText, MsgTime, "To Red Coalition" ):ToCoalition( coalition.side.RED ) + MESSAGE:New( MsgText, MsgTime, "To Red Coalition" ):ToCoalition( coalition.side.RED ) end function MessageToBlue( MsgText, MsgTime, MsgName ) ---trace.f() + -- trace.f() - MESSAGE:New( MsgText, MsgTime, "To Blue Coalition" ):ToCoalition( coalition.side.BLUE ) + MESSAGE:New( MsgText, MsgTime, "To Blue Coalition" ):ToCoalition( coalition.side.BLUE ) end function getCarrierHeight( CarrierGroup ) ---trace.f() + -- trace.f() - if CarrierGroup ~= nil then - if table.getn(CarrierGroup:getUnits()) == 1 then - local CarrierUnit = CarrierGroup:getUnits()[1] - local CurrentPoint = CarrierUnit:getPoint() + if CarrierGroup ~= nil then + if table.getn( CarrierGroup:getUnits() ) == 1 then + local CarrierUnit = CarrierGroup:getUnits()[1] + local CurrentPoint = CarrierUnit:getPoint() - local CurrentPosition = { x = CurrentPoint.x, y = CurrentPoint.z } - local CarrierHeight = CurrentPoint.y + local CurrentPosition = { x = CurrentPoint.x, y = CurrentPoint.z } + local CarrierHeight = CurrentPoint.y - local LandHeight = land.getHeight( CurrentPosition ) + local LandHeight = land.getHeight( CurrentPosition ) - --env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) - - return CarrierHeight - LandHeight - else - return 999999 - end - else - return 999999 - end + -- env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) + return CarrierHeight - LandHeight + else + return 999999 + end + else + return 999999 + end end function GetUnitHeight( CheckUnit ) ---trace.f() + -- trace.f() - local UnitPoint = CheckUnit:getPoint() - local UnitPosition = { x = CurrentPoint.x, y = CurrentPoint.z } - local UnitHeight = CurrentPoint.y + local UnitPoint = CheckUnit:getPoint() + local UnitPosition = { x = CurrentPoint.x, y = CurrentPoint.z } + local UnitHeight = CurrentPoint.y - local LandHeight = land.getHeight( CurrentPosition ) + local LandHeight = land.getHeight( CurrentPosition ) - --env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) - - return UnitHeight - LandHeight + -- env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) + return UnitHeight - LandHeight end - _MusicTable = {} _MusicTable.Files = {} _MusicTable.Queue = {} _MusicTable.FileCnt = 0 - function MusicRegister( SndRef, SndFile, SndTime ) ---trace.f() + -- trace.f() - env.info(( 'MusicRegister: SndRef = ' .. SndRef )) - env.info(( 'MusicRegister: SndFile = ' .. SndFile )) - env.info(( 'MusicRegister: SndTime = ' .. SndTime )) + env.info( ('MusicRegister: SndRef = ' .. SndRef) ) + env.info( ('MusicRegister: SndFile = ' .. SndFile) ) + env.info( ('MusicRegister: SndTime = ' .. SndTime) ) + _MusicTable.FileCnt = _MusicTable.FileCnt + 1 - _MusicTable.FileCnt = _MusicTable.FileCnt + 1 - - _MusicTable.Files[_MusicTable.FileCnt] = {} - _MusicTable.Files[_MusicTable.FileCnt].Ref = SndRef - _MusicTable.Files[_MusicTable.FileCnt].File = SndFile - _MusicTable.Files[_MusicTable.FileCnt].Time = SndTime - - if not _MusicTable.Function then - _MusicTable.Function = routines.scheduleFunction( MusicScheduler, { }, timer.getTime() + 10, 10) - end + _MusicTable.Files[_MusicTable.FileCnt] = {} + _MusicTable.Files[_MusicTable.FileCnt].Ref = SndRef + _MusicTable.Files[_MusicTable.FileCnt].File = SndFile + _MusicTable.Files[_MusicTable.FileCnt].Time = SndTime + if not _MusicTable.Function then + _MusicTable.Function = routines.scheduleFunction( MusicScheduler, {}, timer.getTime() + 10, 10 ) + end end function MusicToPlayer( SndRef, PlayerName, SndContinue ) ---trace.f() + -- trace.f() - --env.info(( 'MusicToPlayer: SndRef = ' .. SndRef )) + -- env.info(( 'MusicToPlayer: SndRef = ' .. SndRef )) - local PlayerUnits = AlivePlayerUnits() - for PlayerUnitIdx, PlayerUnit in pairs(PlayerUnits) do - local PlayerUnitName = PlayerUnit:getPlayerName() - --env.info(( 'MusicToPlayer: PlayerUnitName = ' .. PlayerUnitName )) - if PlayerName == PlayerUnitName then - PlayerGroup = PlayerUnit:getGroup() - if PlayerGroup then - --env.info(( 'MusicToPlayer: PlayerGroup = ' .. PlayerGroup:getName() )) - MusicToGroup( SndRef, PlayerGroup, SndContinue ) - end - break - end - end - - --env.info(( 'MusicToPlayer: end' )) + local PlayerUnits = AlivePlayerUnits() + for PlayerUnitIdx, PlayerUnit in pairs( PlayerUnits ) do + local PlayerUnitName = PlayerUnit:getPlayerName() + -- env.info(( 'MusicToPlayer: PlayerUnitName = ' .. PlayerUnitName )) + if PlayerName == PlayerUnitName then + PlayerGroup = PlayerUnit:getGroup() + if PlayerGroup then + -- env.info(( 'MusicToPlayer: PlayerGroup = ' .. PlayerGroup:getName() )) + MusicToGroup( SndRef, PlayerGroup, SndContinue ) + end + break + end + end + -- env.info(( 'MusicToPlayer: end' )) end function MusicToGroup( SndRef, SndGroup, SndContinue ) ---trace.f() + -- trace.f() - --env.info(( 'MusicToGroup: SndRef = ' .. SndRef )) + -- env.info(( 'MusicToGroup: SndRef = ' .. SndRef )) - if SndGroup ~= nil then - if _MusicTable and _MusicTable.FileCnt > 0 then - if SndGroup:isExist() then - if MusicCanStart(SndGroup:getUnit(1):getPlayerName()) then - --env.info(( 'MusicToGroup: OK for Sound.' )) - local SndIdx = 0 - if SndRef == '' then - --env.info(( 'MusicToGroup: SndRef as empty. Queueing at random.' )) - SndIdx = math.random( 1, _MusicTable.FileCnt ) - else - for SndIdx = 1, _MusicTable.FileCnt do - if _MusicTable.Files[SndIdx].Ref == SndRef then - break - end - end - end - --env.info(( 'MusicToGroup: SndIdx = ' .. SndIdx )) - --env.info(( 'MusicToGroup: Queueing Music ' .. _MusicTable.Files[SndIdx].File .. ' for Group ' .. SndGroup:getID() )) - trigger.action.outSoundForGroup( SndGroup:getID(), _MusicTable.Files[SndIdx].File ) - MessageToGroup( SndGroup, 'Playing ' .. _MusicTable.Files[SndIdx].File, 15, 'Music-' .. SndGroup:getUnit(1):getPlayerName() ) + if SndGroup ~= nil then + if _MusicTable and _MusicTable.FileCnt > 0 then + if SndGroup:isExist() then + if MusicCanStart( SndGroup:getUnit( 1 ):getPlayerName() ) then + -- env.info(( 'MusicToGroup: OK for Sound.' )) + local SndIdx = 0 + if SndRef == '' then + -- env.info(( 'MusicToGroup: SndRef as empty. Queueing at random.' )) + SndIdx = math.random( 1, _MusicTable.FileCnt ) + else + for SndIdx = 1, _MusicTable.FileCnt do + if _MusicTable.Files[SndIdx].Ref == SndRef then + break + end + end + end + -- env.info(( 'MusicToGroup: SndIdx = ' .. SndIdx )) + -- env.info(( 'MusicToGroup: Queueing Music ' .. _MusicTable.Files[SndIdx].File .. ' for Group ' .. SndGroup:getID() )) + trigger.action.outSoundForGroup( SndGroup:getID(), _MusicTable.Files[SndIdx].File ) + MessageToGroup( SndGroup, 'Playing ' .. _MusicTable.Files[SndIdx].File, 15, 'Music-' .. SndGroup:getUnit( 1 ):getPlayerName() ) - local SndQueueRef = SndGroup:getUnit(1):getPlayerName() - if _MusicTable.Queue[SndQueueRef] == nil then - _MusicTable.Queue[SndQueueRef] = {} - end - _MusicTable.Queue[SndQueueRef].Start = timer.getTime() - _MusicTable.Queue[SndQueueRef].PlayerName = SndGroup:getUnit(1):getPlayerName() - _MusicTable.Queue[SndQueueRef].Group = SndGroup - _MusicTable.Queue[SndQueueRef].ID = SndGroup:getID() - _MusicTable.Queue[SndQueueRef].Ref = SndIdx - _MusicTable.Queue[SndQueueRef].Continue = SndContinue - _MusicTable.Queue[SndQueueRef].Type = Group - end - end - end - end + local SndQueueRef = SndGroup:getUnit( 1 ):getPlayerName() + if _MusicTable.Queue[SndQueueRef] == nil then + _MusicTable.Queue[SndQueueRef] = {} + end + _MusicTable.Queue[SndQueueRef].Start = timer.getTime() + _MusicTable.Queue[SndQueueRef].PlayerName = SndGroup:getUnit( 1 ):getPlayerName() + _MusicTable.Queue[SndQueueRef].Group = SndGroup + _MusicTable.Queue[SndQueueRef].ID = SndGroup:getID() + _MusicTable.Queue[SndQueueRef].Ref = SndIdx + _MusicTable.Queue[SndQueueRef].Continue = SndContinue + _MusicTable.Queue[SndQueueRef].Type = Group + end + end + end + end end -function MusicCanStart(PlayerName) ---trace.f() +function MusicCanStart( PlayerName ) + -- trace.f() - --env.info(( 'MusicCanStart:' )) + -- env.info(( 'MusicCanStart:' )) - local MusicOut = false + local MusicOut = false - if _MusicTable['Queue'] ~= nil and _MusicTable.FileCnt > 0 then - --env.info(( 'MusicCanStart: PlayerName = ' .. PlayerName )) - 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 - --env.info(( 'MusicCanStart: MusicStart = ' .. MusicStart )) - --env.info(( 'MusicCanStart: MusicTime = ' .. MusicTime )) - --env.info(( 'MusicCanStart: timer.getTime() = ' .. timer.getTime() )) + if _MusicTable['Queue'] ~= nil and _MusicTable.FileCnt > 0 then + -- env.info(( 'MusicCanStart: PlayerName = ' .. PlayerName )) + 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 + -- env.info(( 'MusicCanStart: MusicStart = ' .. MusicStart )) + -- env.info(( 'MusicCanStart: MusicTime = ' .. MusicTime )) + -- env.info(( 'MusicCanStart: timer.getTime() = ' .. timer.getTime() )) - if MusicStart + MusicTime <= timer.getTime() then - MusicOut = true - end - else - MusicOut = true - end - end + if MusicStart + MusicTime <= timer.getTime() then + MusicOut = true + end + else + MusicOut = true + end + end - if MusicOut then - --env.info(( 'MusicCanStart: true' )) - else - --env.info(( 'MusicCanStart: false' )) - end + if MusicOut then + -- env.info(( 'MusicCanStart: true' )) + else + -- env.info(( 'MusicCanStart: false' )) + end - return MusicOut + return MusicOut end function MusicScheduler() ---trace.scheduled("", "MusicScheduler") - - --env.info(( 'MusicScheduler:' )) - if _MusicTable['Queue'] ~= nil and _MusicTable.FileCnt > 0 then - --env.info(( 'MusicScheduler: Walking Sound Queue.')) - for SndQueueIdx, SndQueue in pairs( _MusicTable.Queue ) do - if SndQueue.Continue then - if MusicCanStart(SndQueue.PlayerName) then - --env.info(('MusicScheduler: MusicToGroup')) - MusicToPlayer( '', SndQueue.PlayerName, true ) - end - end - end - end + -- trace.scheduled("", "MusicScheduler") + -- env.info(( 'MusicScheduler:' )) + if _MusicTable['Queue'] ~= nil and _MusicTable.FileCnt > 0 then + -- env.info(( 'MusicScheduler: Walking Sound Queue.')) + for SndQueueIdx, SndQueue in pairs( _MusicTable.Queue ) do + if SndQueue.Continue then + if MusicCanStart( SndQueue.PlayerName ) then + -- env.info(('MusicScheduler: MusicToGroup')) + MusicToPlayer( '', SndQueue.PlayerName, true ) + end + end + end + end end - -env.info(( 'Init: Scripts Loaded v1.1' )) - +env.info( ('Init: Scripts Loaded v1.1') ) diff --git a/Moose Development/Moose/Utilities/STTS.lua b/Moose Development/Moose/Utilities/STTS.lua index f36a8e5e1..36aa2cc43 100644 --- a/Moose Development/Moose/Utilities/STTS.lua +++ b/Moose Development/Moose/Utilities/STTS.lua @@ -1,7 +1,6 @@ --- **Utilities** DCS Simple Text-To-Speech (STTS). --- --- --- +-- +-- -- @module Utils.STTS -- @image MOOSE.JPG @@ -10,29 +9,29 @@ -- @field #string DIRECTORY Path of the SRS directory. --- Simple Text-To-Speech --- +-- -- Version 0.4 - Compatible with SRS version 1.9.6.0+ --- +-- -- # DCS Modification Required --- --- You will need to edit MissionScripting.lua in DCS World/Scripts/MissionScripting.lua and remove the sanitisation. +-- +-- You will need to edit MissionScripting.lua in DCS World/Scripts/MissionScripting.lua and remove the sanitization. -- To do this remove all the code below the comment - the line starts "local function sanitizeModule(name)" -- Do this without DCS running to allow mission scripts to use os functions. --- +-- -- *You WILL HAVE TO REAPPLY AFTER EVERY DCS UPDATE* --- +-- -- # USAGE: --- --- Add this script into the mission as a DO SCRIPT or DO SCRIPT FROM FILE to initialise it +-- +-- Add this script into the mission as a DO SCRIPT or DO SCRIPT FROM FILE to initialize it -- Make sure to edit the STTS.SRS_PORT and STTS.DIRECTORY to the correct values before adding to the mission. -- Then its as simple as calling the correct function in LUA as a DO SCRIPT or in your own scripts. --- +-- -- Example calls: -- -- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2) --- +-- -- Arguments in order are: --- +-- -- * Message to say, make sure not to use a newline (\n) ! -- * Frequency in MHz -- * Modulation - AM/FM @@ -43,44 +42,44 @@ -- * OPTIONAL - Speed -10 to +10 -- * OPTIONAL - Gender male, female or neuter -- * OPTIONAL - Culture - en-US, en-GB etc --- * OPTIONAL - Voice - a specfic voice by name. Run DCS-SR-ExternalAudio.exe with --help to get the ones you can use on the command line +-- * OPTIONAL - Voice - a specific voice by name. Run DCS-SR-ExternalAudio.exe with --help to get the ones you can use on the command line -- * OPTIONAL - Google TTS - Switch to Google Text To Speech - Requires STTS.GOOGLE_CREDENTIALS path and Google project setup correctly -- -- -- ## Example -- -- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only --- +-- -- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,null,-5,"male","en-GB") --- +-- -- ## Example --- ---This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only centered on the position of the Unit called "A UNIT" +-- +-- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only centered on the position of the Unit called "A UNIT" -- -- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,Unit.getByName("A UNIT"):getPoint(),-5,"male","en-GB") --- +-- -- Arguments in order are: --- +-- -- * FULL path to the MP3 OR OGG to play -- * Frequency in MHz - to use multiple separate with a comma - Number of frequencies MUST match number of Modulations -- * Modulation - AM/FM - to use multiple -- * Volume - 1.0 max, 0.5 half -- * Name of the transmitter - ATC, RockFM etc -- * Coalition - 0 spectator, 1 red 2 blue --- +-- -- ## Example --- +-- -- This will play that MP3 on 255MHz AM & 31 FM at half volume with a client called "Multiple" and to Spectators only --- +-- -- STTS.PlayMP3("C:\\Users\\Ciaran\\Downloads\\PR-Music.mp3","255,31","AM,FM","0.5","Multiple",0) --- +-- -- @field #STTS -STTS={ - ClassName="STTS", - DIRECTORY="", - SRS_PORT=5002, - GOOGLE_CREDENTIALS="C:\\Users\\Ciaran\\Downloads\\googletts.json", - EXECUTABLE="DCS-SR-ExternalAudio.exe", +STTS = { + ClassName = "STTS", + DIRECTORY = "", + SRS_PORT = 5002, + GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json", + EXECUTABLE = "DCS-SR-ExternalAudio.exe" } --- FULL Path to the FOLDER containing DCS-SR-ExternalAudio.exe - EDIT TO CORRECT FOLDER @@ -92,37 +91,40 @@ STTS.SRS_PORT = 5002 --- Google credentials file STTS.GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json" ---- DONT CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING +--- DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING STTS.EXECUTABLE = "DCS-SR-ExternalAudio.exe" - --- Function for UUID. function STTS.uuid() local random = math.random - local template ='yxxx-xxxxxxxxxxxx' - return string.gsub(template, '[xy]', function (c) - local v = (c == 'x') and random(0, 0xf) or random(8, 0xb) - return string.format('%x', v) - end) + local template = 'yxxx-xxxxxxxxxxxx' + return string.gsub( template, '[xy]', function( c ) + local v = (c == 'x') and random( 0, 0xf ) or random( 8, 0xb ) + return string.format( '%x', v ) + end ) end --- Round a number. -- @param #number x Number. -- @param #number n Precision. -function STTS.round(x, n) - n = math.pow(10, n or 0) +function STTS.round( x, n ) + n = math.pow( 10, n or 0 ) x = x * n - if x >= 0 then x = math.floor(x + 0.5) else x = math.ceil(x - 0.5) end + if x >= 0 then + x = math.floor( x + 0.5 ) + else + x = math.ceil( x - 0.5 ) + end return x / n end --- Function returns estimated speech time in seconds. --- Assumptions for time calc: 100 Words per min, avarage of 5 letters for english word so --- +-- Assumptions for time calc: 100 Words per min, average of 5 letters for english word so +-- -- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second --- --- So lengh of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function: --- +-- +-- So length of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function: +-- -- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min -- -- @param #number length can also be passed as #string @@ -130,107 +132,106 @@ end -- @param #boolean isGoogle We're using Google TTS function STTS.getSpeechTime(length,speed,isGoogle) - local maxRateRatio = 3 + local maxRateRatio = 3 speed = speed or 1.0 isGoogle = isGoogle or false local speedFactor = 1.0 if isGoogle then - speedFactor = speed + speedFactor = speed else - if speed ~= 0 then - speedFactor = math.abs(speed) * (maxRateRatio - 1) / 10 + 1 - end - if speed < 0 then - speedFactor = 1/speedFactor - end + if speed ~= 0 then + speedFactor = math.abs( speed ) * (maxRateRatio - 1) / 10 + 1 + end + if speed < 0 then + speedFactor = 1 / speedFactor + end end - local wpm = math.ceil(100 * speedFactor) - local cps = math.floor((wpm * 5)/60) + local wpm = math.ceil( 100 * speedFactor ) + local cps = math.floor( (wpm * 5) / 60 ) - if type(length) == "string" then - length = string.len(length) + if type( length ) == "string" then + length = string.len( length ) end return length/cps --math.ceil(length/cps) end --- Text to speech function. -function STTS.TextToSpeech(message, freqs, modulations, volume, name, coalition, point, speed, gender, culture, voice, googleTTS) - if os == nil or io == nil then - env.info("[DCS-STTS] LUA modules os or io are sanitized. skipping. ") - return - end +function STTS.TextToSpeech( message, freqs, modulations, volume, name, coalition, point, speed, gender, culture, voice, googleTTS ) + if os == nil or io == nil then + env.info( "[DCS-STTS] LUA modules os or io are sanitized. skipping. " ) + return + end - speed = speed or 1 - gender = gender or "female" - culture = culture or "" - voice = voice or "" - coalition=coalition or "0" - name=name or "ROBOT" - volume=1 - speed=1 + speed = speed or 1 + gender = gender or "female" + culture = culture or "" + voice = voice or "" + coalition = coalition or "0" + name = name or "ROBOT" + volume = 1 + speed = 1 + message = message:gsub( "\"", "\\\"" ) + + local cmd = string.format( "start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", STTS.DIRECTORY, STTS.EXECUTABLE, freqs or "305", modulations or "AM", coalition, STTS.SRS_PORT, name ) - message = message:gsub("\"","\\\"") - - local cmd = string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", STTS.DIRECTORY, STTS.EXECUTABLE, freqs or "305", modulations or "AM", coalition, STTS.SRS_PORT, name) - if voice ~= "" then - cmd = cmd .. string.format(" -V \"%s\"",voice) + cmd = cmd .. string.format( " -V \"%s\"", voice ) else - if culture ~= "" then - cmd = cmd .. string.format(" -l %s",culture) - end + if culture ~= "" then + cmd = cmd .. string.format( " -l %s", culture ) + end - if gender ~= "" then - cmd = cmd .. string.format(" -g %s",gender) - end + if gender ~= "" then + cmd = cmd .. string.format( " -g %s", gender ) + end end if googleTTS == true then - cmd = cmd .. string.format(" -G \"%s\"",STTS.GOOGLE_CREDENTIALS) + cmd = cmd .. string.format( " -G \"%s\"", STTS.GOOGLE_CREDENTIALS ) end if speed ~= 1 then - cmd = cmd .. string.format(" -s %s",speed) + cmd = cmd .. string.format( " -s %s", speed ) end if volume ~= 1.0 then - cmd = cmd .. string.format(" -v %s",volume) + cmd = cmd .. string.format( " -v %s", volume ) end - if point and type(point) == "table" and point.x then - local lat, lon, alt = coord.LOtoLL(point) + if point and type( point ) == "table" and point.x then + local lat, lon, alt = coord.LOtoLL( point ) - lat = STTS.round(lat,4) - lon = STTS.round(lon,4) - alt = math.floor(alt) + lat = STTS.round( lat, 4 ) + lon = STTS.round( lon, 4 ) + alt = math.floor( alt ) - cmd = cmd .. string.format(" -L %s -O %s -A %s",lat,lon,alt) + cmd = cmd .. string.format( " -L %s -O %s -A %s", lat, lon, alt ) end - cmd = cmd ..string.format(" -t \"%s\"",message) + cmd = cmd .. string.format( " -t \"%s\"", message ) - if string.len(cmd) > 255 then - local filename = os.getenv('TMP') .. "\\DCS_STTS-" .. STTS.uuid() .. ".bat" - local script = io.open(filename,"w+") - script:write(cmd .. " && exit" ) - script:close() - cmd = string.format("\"%s\"",filename) - timer.scheduleFunction(os.remove, filename, timer.getTime() + 1) + if string.len( cmd ) > 255 then + local filename = os.getenv( 'TMP' ) .. "\\DCS_STTS-" .. STTS.uuid() .. ".bat" + local script = io.open( filename, "w+" ) + script:write( cmd .. " && exit" ) + script:close() + cmd = string.format( "\"%s\"", filename ) + timer.scheduleFunction( os.remove, filename, timer.getTime() + 1 ) end - if string.len(cmd) > 255 then - env.info("[DCS-STTS] - cmd string too long") - env.info("[DCS-STTS] TextToSpeech Command :\n" .. cmd.."\n") + if string.len( cmd ) > 255 then + env.info( "[DCS-STTS] - cmd string too long" ) + env.info( "[DCS-STTS] TextToSpeech Command :\n" .. cmd .. "\n" ) end - os.execute(cmd) + os.execute( cmd ) - return STTS.getSpeechTime(message,speed,googleTTS) + return STTS.getSpeechTime( message, speed, googleTTS ) end --- Play mp3 function. @@ -238,22 +239,21 @@ end -- @param #string freqs Frequencies, e.g. "305, 256". -- @param #string modulations Modulations, e.g. "AM, FM". -- @param #string volume Volume, e.g. "0.5". -function STTS.PlayMP3(pathToMP3, freqs, modulations, volume, name, coalition, point) +function STTS.PlayMP3( pathToMP3, freqs, modulations, volume, name, coalition, point ) - local cmd = string.format("start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h", - STTS.DIRECTORY, STTS.EXECUTABLE, pathToMP3, freqs or "305", modulations or "AM", coalition or "0", STTS.SRS_PORT, name or "ROBOT", volume or "1") - - if point and type(point) == "table" and point.x then - local lat, lon, alt = coord.LOtoLL(point) + local cmd = string.format( "start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h", STTS.DIRECTORY, STTS.EXECUTABLE, pathToMP3, freqs or "305", modulations or "AM", coalition or "0", STTS.SRS_PORT, name or "ROBOT", volume or "1" ) - lat = STTS.round(lat,4) - lon = STTS.round(lon,4) - alt = math.floor(alt) + if point and type( point ) == "table" and point.x then + local lat, lon, alt = coord.LOtoLL( point ) - cmd = cmd .. string.format(" -L %s -O %s -A %s",lat,lon,alt) - end + lat = STTS.round( lat, 4 ) + lon = STTS.round( lon, 4 ) + alt = math.floor( alt ) - env.info("[DCS-STTS] MP3/OGG Command :\n" .. cmd.."\n") - os.execute(cmd) + cmd = cmd .. string.format( " -L %s -O %s -A %s", lat, lon, alt ) + end -end \ No newline at end of file + env.info( "[DCS-STTS] MP3/OGG Command :\n" .. cmd .. "\n" ) + os.execute( cmd ) + +end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 57035a324..2036df9cc 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -54,7 +54,7 @@ -- * @{#AIRBASE.Find}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase object. -- * @{#AIRBASE.FindByName}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase name. -- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these AIRBASE OBJECT REFERENCES! (make the AIRBASE object references nil). +-- IMPORTANT: ONE SHOULD NEVER SANITIZE these AIRBASE OBJECT REFERENCES! (make the AIRBASE object references nil). -- -- ## DCS Airbase APIs -- @@ -65,13 +65,14 @@ -- -- @field #AIRBASE AIRBASE AIRBASE = { - ClassName="AIRBASE", + ClassName = "AIRBASE", CategoryName = { - [Airbase.Category.AIRDROME] = "Airdrome", - [Airbase.Category.HELIPAD] = "Helipad", - [Airbase.Category.SHIP] = "Ship", - }, - } + [Airbase.Category.AIRDROME] = "Airdrome", + [Airbase.Category.HELIPAD] = "Helipad", + [Airbase.Category.SHIP] = "Ship", + }, + activerwyno = nil, +} --- Enumeration to identify the airbases in the Caucasus region. -- @@ -122,7 +123,7 @@ AIRBASE.Caucasus = { ["Nalchik"] = "Nalchik", ["Mozdok"] = "Mozdok", ["Beslan"] = "Beslan", - } +} --- Airbases of the Nevada map: -- @@ -163,7 +164,7 @@ AIRBASE.Nevada = { ["Pahute_Mesa_Airstrip"] = "Pahute Mesa", ["Tonopah_Airport"] = "Tonopah", ["Tonopah_Test_Range_Airfield"] = "Tonopah Test Range", - } +} --- Airbases of the Normandy map: -- @@ -198,7 +199,7 @@ AIRBASE.Nevada = { -- * AIRBASE.Normandy.Funtington -- * AIRBASE.Normandy.Tangmere -- * AIRBASE.Normandy.Ford_AF --- +-- -- @field Normandy AIRBASE.Normandy = { ["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont", @@ -272,7 +273,7 @@ AIRBASE.Normandy = { -- * AIRBASE.PersianGulf.Sirri_Island -- * AIRBASE.PersianGulf.Tunb_Island_AFB -- * AIRBASE.PersianGulf.Tunb_Kochak --- +-- -- @field PersianGulf AIRBASE.PersianGulf = { ["Abu_Dhabi_International_Airport"] = "Abu Dhabi Intl", @@ -320,7 +321,7 @@ AIRBASE.PersianGulf = { -- * AIRBASE.TheChannel.Biggin_Hill -- * AIRBASE.TheChannel.Eastchurch -- * AIRBASE.TheChannel.Headcorn --- +-- -- @field TheChannel AIRBASE.TheChannel = { ["Abbeville_Drucat"] = "Abbeville Drucat", @@ -466,7 +467,7 @@ AIRBASE.Syria={ ["Ruwayshid"]="Ruwayshid", ["Sanliurfa"]="Sanliurfa", ["Tal_Siman"]="Tal Siman", - ["Deir_ez_Zor"] = "Deir ez-Zor", + ["Deir_ez_Zor"] = "Deir ez-Zor", } --- Airbases of the Mariana Islands map: @@ -478,14 +479,14 @@ AIRBASE.Syria={ -- * AIRBASE.MarianaIslands.Tinian_Intl -- * AIRBASE.MarianaIslands.Olf_Orote -- ---@field MarianaIslands -AIRBASE.MarianaIslands={ - ["Rota_Intl"]="Rota Intl", - ["Andersen_AFB"]="Andersen AFB", - ["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl", - ["Saipan_Intl"]="Saipan Intl", - ["Tinian_Intl"]="Tinian Intl", - ["Olf_Orote"]="Olf Orote", +-- @field MarianaIslands +AIRBASE.MarianaIslands = { + ["Rota_Intl"] = "Rota Intl", + ["Andersen_AFB"] = "Andersen AFB", + ["Antonio_B_Won_Pat_Intl"] = "Antonio B. Won Pat Intl", + ["Saipan_Intl"] = "Saipan Intl", + ["Tinian_Intl"] = "Tinian Intl", + ["Olf_Orote"] = "Olf Orote", } --- Airbases of the South Atlantic map: diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 0fc60f6cd..156fb8be2 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1,4 +1,4 @@ - --- **Wrapper** -- CONTROLLABLE is an intermediate class wrapping Group and Unit classes "controllers". +--- **Wrapper** -- CONTROLLABLE is an intermediate class wrapping Group and Unit classes "controllers". -- -- === -- @@ -11,14 +11,11 @@ -- @module Wrapper.Controllable -- @image Wrapper_Controllable.JPG - --- @type CONTROLLABLE -- @field DCS#Controllable DCSControllable The DCS controllable class. -- @field #string ControllableName The name of the controllable. -- @extends Wrapper.Positionable#POSITIONABLE - - --- Wrapper class to handle the "DCS Controllable objects", which are Groups and Units: -- -- * Support all DCS Controllable APIs. @@ -62,10 +59,10 @@ -- * @{#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the controllable. -- * @{#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. -- * @{#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the controllable at a @{Core.Zone#ZONE_RADIUS). --- * @{#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. --- * @{#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. +-- * @{#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the controllable at a specified altitude. +-- * @{#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified altitude during a specified duration with a specified speed. -- * @{#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. --- * @{#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Misson task to follow a given route defined by Points. +-- * @{#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Mission task to follow a given route defined by Points. -- * @{#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Controllable move to a given point. -- * @{#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Controllable move to a given point. -- * @{#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the controllable to a given zone. @@ -86,7 +83,7 @@ -- -- ## 2.3) Task preparation -- --- There are certain task methods that allow to tailor the task behaviour: +-- There are certain task methods that allow to tailor the task behavior: -- -- * @{#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. -- * @{#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. @@ -133,7 +130,7 @@ -- -- # 5) Option methods -- --- Controllable **Option methods** change the behaviour of the Controllable while being alive. +-- Controllable **Option methods** change the behavior of the Controllable while being alive. -- -- ## 5.1) Rule of Engagement: -- @@ -190,7 +187,7 @@ CONTROLLABLE = { -- @return #CONTROLLABLE self function CONTROLLABLE:New( ControllableName ) local self = BASE:Inherit( self, POSITIONABLE:New( ControllableName ) ) -- #CONTROLLABLE - --self:F( ControllableName ) + -- self:F( ControllableName ) self.ControllableName = ControllableName self.TaskScheduler = SCHEDULER:New( self ) @@ -215,7 +212,6 @@ end -- Get methods - --- Returns the health. Dead controllables have health <= 1.0. -- @param #CONTROLLABLE self -- @return #number The controllable health value (unit or group average). @@ -274,7 +270,7 @@ function CONTROLLABLE:GetLife0() end --- Returns relative minimum amount of fuel (from 0.0 to 1.0) a unit or group has in its internal tanks. --- This method returns nil to ensure polymorphic behaviour! This method needs to be overridden by GROUP or UNIT. +-- This method returns nil to ensure polymorphic behavior! This method needs to be overridden by GROUP or UNIT. -- @param #CONTROLLABLE self -- @return #nil The CONTROLLABLE is not existing or alive. function CONTROLLABLE:GetFuelMin() @@ -284,7 +280,7 @@ function CONTROLLABLE:GetFuelMin() end --- Returns relative average amount of fuel (from 0.0 to 1.0) a unit or group has in its internal tanks. --- This method returns nil to ensure polymorphic behaviour! This method needs to be overridden by GROUP or UNIT. +-- This method returns nil to ensure polymorphic behavior! This method needs to be overridden by GROUP or UNIT. -- @param #CONTROLLABLE self -- @return #nil The CONTROLLABLE is not existing or alive. function CONTROLLABLE:GetFuelAve() @@ -294,7 +290,7 @@ function CONTROLLABLE:GetFuelAve() end --- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks. --- This method returns nil to ensure polymorphic behaviour! This method needs to be overridden by GROUP or UNIT. +-- This method returns nil to ensure polymorphic behavior! This method needs to be overridden by GROUP or UNIT. -- @param #CONTROLLABLE self -- @return #nil The CONTROLLABLE is not existing or alive. function CONTROLLABLE:GetFuel() @@ -302,7 +298,6 @@ function CONTROLLABLE:GetFuel() return nil end - -- Tasks --- Clear all tasks from the controllable. @@ -321,7 +316,6 @@ function CONTROLLABLE:ClearTasks() return nil end - --- Popping current Task from the controllable. -- @param #CONTROLLABLE self -- @return Wrapper.Controllable#CONTROLLABLE self @@ -399,7 +393,7 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime ) local function SetTask( Controller, DCSTask ) if self and self:IsAlive() then local Controller = self:_GetController() - --self:I( "Before SetTask" ) + -- self:I( "Before SetTask" ) Controller:setTask( DCSTask ) -- AI_FORMATION class (used by RESCUEHELO) calls SetTask twice per second! hence spamming the DCS log file ==> setting this to trace. self:T( { ControllableName = self:GetName(), DCSTask = DCSTask } ) @@ -425,7 +419,7 @@ end --- Checking the Task Queue of the controllable. Returns false if no task is on the queue. true if there is a task. -- @param #CONTROLLABLE self -- @return Wrapper.Controllable#CONTROLLABLE self -function CONTROLLABLE:HasTask() --R2.2 +function CONTROLLABLE:HasTask() -- R2.2 local HasTaskResult = false @@ -440,7 +434,6 @@ function CONTROLLABLE:HasTask() --R2.2 return HasTaskResult end - --- Return a condition section for a controlled task. -- @param #CONTROLLABLE self -- @param DCS#Time time DCS mission time. @@ -452,7 +445,7 @@ end -- return DCS#Task function CONTROLLABLE:TaskCondition( time, userFlag, userFlagValue, condition, duration, lastWayPoint ) ---[[ + --[[ StopCondition = { time = Time, userFlag = string, @@ -485,8 +478,8 @@ function CONTROLLABLE:TaskControlled( DCSTask, DCSStopCondition ) id = 'ControlledTask', params = { task = DCSTask, - stopCondition = DCSStopCondition - } + stopCondition = DCSStopCondition, + }, } return DCSTaskControlled @@ -501,8 +494,8 @@ function CONTROLLABLE:TaskCombo( DCSTasks ) local DCSTaskCombo = { id = 'ComboTask', params = { - tasks = DCSTasks - } + tasks = DCSTasks, + }, } return DCSTaskCombo @@ -540,9 +533,6 @@ function CONTROLLABLE:SetTaskWaypoint( Waypoint, Task ) return Waypoint.task end - - - --- Executes a command action for the CONTROLLABLE. -- @param #CONTROLLABLE self -- @param DCS#Command DCSCommand The command to be executed. @@ -567,17 +557,18 @@ end -- @param #number ToWayPoint -- @return DCS#Task -- @usage --- --- This test demonstrates the use(s) of the SwitchWayPoint method of the GROUP class. --- HeliGroup = GROUP:FindByName( "Helicopter" ) -- --- --- Route the helicopter back to the FARP after 60 seconds. --- -- We use the SCHEDULER class to do this. --- SCHEDULER:New( nil, --- function( HeliGroup ) --- local CommandRTB = HeliGroup:CommandSwitchWayPoint( 2, 8 ) --- HeliGroup:SetCommand( CommandRTB ) --- end, { HeliGroup }, 90 --- ) +-- -- This test demonstrates the use(s) of the SwitchWayPoint method of the GROUP class. +-- HeliGroup = GROUP:FindByName( "Helicopter" ) +-- +-- -- Route the helicopter back to the FARP after 60 seconds. +-- -- We use the SCHEDULER class to do this. +-- SCHEDULER:New( nil, +-- function( HeliGroup ) +-- local CommandRTB = HeliGroup:CommandSwitchWayPoint( 2, 8 ) +-- HeliGroup:SetCommand( CommandRTB ) +-- end, { HeliGroup }, 90 +-- ) function CONTROLLABLE:CommandSwitchWayPoint( FromWayPoint, ToWayPoint ) self:F2( { FromWayPoint, ToWayPoint } ) @@ -619,16 +610,15 @@ function CONTROLLABLE:CommandStopRoute( StopRoute ) return CommandStopRoute end - --- Give an uncontrolled air controllable the start command. -- @param #CONTROLLABLE self -- @param #number delay (Optional) Delay before start command in seconds. -- @return #CONTROLLABLE self -function CONTROLLABLE:StartUncontrolled(delay) - if delay and delay>0 then - SCHEDULER:New(nil, CONTROLLABLE.StartUncontrolled, {self}, delay) +function CONTROLLABLE:StartUncontrolled( delay ) + if delay and delay > 0 then + SCHEDULER:New( nil, CONTROLLABLE.StartUncontrolled, { self }, delay ) else - self:SetCommand({id='Start', params={}}) + self:SetCommand( { id = 'Start', params = {} } ) end return self end @@ -640,7 +630,7 @@ end -- @param Core.Radio#BEACON.Type Type Beacon type (VOR, DME, TACAN, RSBN, ILS etc). -- @param Core.Radio#BEACON.System System Beacon system (VOR, DME, TACAN, RSBN, ILS etc). -- @param #number Frequency Frequency in Hz the beacon is running on. Use @{#UTILS.TACANToFrequency} to generate a frequency for TACAN beacons. --- @param #number UnitID The ID of the unit the beacon is attached to. Usefull if more units are in one group. +-- @param #number UnitID The ID of the unit the beacon is attached to. Useful if more units are in one group. -- @param #number Channel Channel the beacon is using. For, e.g. TACAN beacons. -- @param #string ModeChannel The TACAN mode of the beacon, i.e. "X" or "Y". -- @param #boolean AA If true, create and Air-Air beacon. IF nil, automatically set if CONTROLLABLE depending on whether unit is and aircraft or not. @@ -648,13 +638,13 @@ end -- @param #boolean Bearing If true, beacon provides bearing information - if supported by the unit the beacon is attached to. -- @param #number Delay (Optional) Delay in seconds before the beacon is activated. -- @return #CONTROLLABLE self -function CONTROLLABLE:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, ModeChannel, AA, Callsign, Bearing, Delay) +function CONTROLLABLE:CommandActivateBeacon( Type, System, Frequency, UnitID, Channel, ModeChannel, AA, Callsign, Bearing, Delay ) - AA=AA or self:IsAir() - UnitID=UnitID or self:GetID() + AA = AA or self:IsAir() + UnitID = UnitID or self:GetID() -- Command - local CommandActivateBeacon= { + local CommandActivateBeacon = { id = "ActivateBeacon", params = { ["type"] = Type, @@ -666,13 +656,13 @@ function CONTROLLABLE:CommandActivateBeacon(Type, System, Frequency, UnitID, Cha ["AA"] = AA, ["callsign"] = Callsign, ["bearing"] = Bearing, - } + }, } - if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandActivateBeacon, {self, Type, System, Frequency, UnitID, Channel, ModeChannel, AA, Callsign, Bearing}, Delay) + if Delay and Delay > 0 then + SCHEDULER:New( nil, self.CommandActivateBeacon, { self, Type, System, Frequency, UnitID, Channel, ModeChannel, AA, Callsign, Bearing }, Delay ) else - self:SetCommand(CommandActivateBeacon) + self:SetCommand( CommandActivateBeacon ) end return self @@ -685,23 +675,51 @@ end -- @param #string Callsign Morse code identification callsign. -- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated. -- @return #CONTROLLABLE self -function CONTROLLABLE:CommandActivateICLS(Channel, UnitID, Callsign, Delay) +function CONTROLLABLE:CommandActivateICLS( Channel, UnitID, Callsign, Delay ) -- Command to activate ICLS system. - local CommandActivateICLS= { + local CommandActivateICLS = { id = "ActivateICLS", - params= { + params = { ["type"] = BEACON.Type.ICLS, ["channel"] = Channel, ["unitId"] = UnitID, ["callsign"] = Callsign, + }, + } + + if Delay and Delay > 0 then + SCHEDULER:New( nil, self.CommandActivateICLS, { self }, Delay ) + else + self:SetCommand( CommandActivateICLS ) + end + + return self +end + +--- Activate LINK4 system of the CONTROLLABLE. The controllable should be an aircraft carrier! +-- @param #CONTROLLABLE self +-- @param #number Frequency Link4 Frequency in MHz, e.g. 336 +-- @param #number UnitID The DCS UNIT ID of the unit the LINK4 system is attached to. Useful if more units are in one group. +-- @param #string Callsign Morse code identification callsign. +-- @param #number Delay (Optional) Delay in seconds before the LINK4 is deactivated. +-- @return #CONTROLLABLE self +function CONTROLLABLE:CommandActivateLink4(Frequency, UnitID, Callsign, Delay) + + -- Command to activate Link4 system. + local CommandActivateLink4= { + id = "ActivateLink4", + params= { + ["frequency "] = Frequency*1000, + ["unitId"] = UnitID, + ["name"] = Callsign, } } if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandActivateICLS, {self}, Delay) + SCHEDULER:New(nil, self.CommandActivateLink4, {self}, Delay) else - self:SetCommand(CommandActivateICLS) + self:SetCommand(CommandActivateLink4) end return self @@ -739,15 +757,17 @@ end -- @param #CONTROLLABLE self -- @param #number Delay (Optional) Delay in seconds before the beacon is deactivated. -- @return #CONTROLLABLE self -function CONTROLLABLE:CommandDeactivateBeacon(Delay) +function CONTROLLABLE:CommandDeactivateBeacon( Delay ) -- Command to deactivate + local CommandDeactivateBeacon = { id = 'DeactivateBeacon', params = {} } + local CommandDeactivateBeacon={id='DeactivateBeacon', params={}} if Delay and Delay>0 then SCHEDULER:New(nil, self.CommandDeactivateBeacon, {self}, Delay) else - self:SetCommand(CommandDeactivateBeacon) + self:SetCommand( CommandDeactivateBeacon ) end return self @@ -775,15 +795,33 @@ end -- @param #CONTROLLABLE self -- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated. -- @return #CONTROLLABLE self -function CONTROLLABLE:CommandDeactivateICLS(Delay) +function CONTROLLABLE:CommandDeactivateICLS( Delay ) -- Command to deactivate - local CommandDeactivateICLS={id='DeactivateICLS', params={}} + local CommandDeactivateICLS = { id = 'DeactivateICLS', params = {} } + + if Delay and Delay > 0 then + SCHEDULER:New( nil, self.CommandDeactivateICLS, { self }, Delay ) + else + self:SetCommand( CommandDeactivateICLS ) + end + + return self +end + +--- Deactivate the active Link4 of the CONTROLLABLE. +-- @param #CONTROLLABLE self +-- @param #number Delay (Optional) Delay in seconds before the Link4 is deactivated. +-- @return #CONTROLLABLE self +function CONTROLLABLE:CommandDeactivateLink4(Delay) + + -- Command to deactivate + local CommandDeactivateLink4={id='DeactivateLink4', params={}} if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandDeactivateICLS, {self}, Delay) + SCHEDULER:New(nil, self.CommandDeactivateLink4, {self}, Delay) else - self:SetCommand(CommandDeactivateICLS) + self:SetCommand(CommandDeactivateLink4) end return self @@ -795,15 +833,15 @@ end -- @param #number CallNumber The number value the group will be referred to as. Only valid numbers are 1-9. For example Uzi **5**-1. Default 1. -- @param #number Delay (Optional) Delay in seconds before the callsign is set. Default is immediately. -- @return #CONTROLLABLE self -function CONTROLLABLE:CommandSetCallsign(CallName, CallNumber, Delay) +function CONTROLLABLE:CommandSetCallsign( CallName, CallNumber, Delay ) -- Command to set the callsign. - local CommandSetCallsign={id='SetCallsign', params={callname=CallName, number=CallNumber or 1}} + local CommandSetCallsign = { id = 'SetCallsign', params = { callname = CallName, number = CallNumber or 1 } } - if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandSetCallsign, {self, CallName, CallNumber}, Delay) + if Delay and Delay > 0 then + SCHEDULER:New( nil, self.CommandSetCallsign, { self, CallName, CallNumber }, Delay ) else - self:SetCommand(CommandSetCallsign) + self:SetCommand( CommandSetCallsign ) end return self @@ -814,26 +852,26 @@ end -- @param #boolean SwitchOnOff If true (or nil) switch EPLRS on. If false switch off. -- @param #number Delay (Optional) Delay in seconds before the callsign is set. Default is immediately. -- @return #CONTROLLABLE self -function CONTROLLABLE:CommandEPLRS(SwitchOnOff, Delay) +function CONTROLLABLE:CommandEPLRS( SwitchOnOff, Delay ) - if SwitchOnOff==nil then - SwitchOnOff=true + if SwitchOnOff == nil then + SwitchOnOff = true end -- Command to set the callsign. - local CommandEPLRS={ - id='EPLRS', - params={ - value=SwitchOnOff, - groupId=self:GetID() - } + local CommandEPLRS = { + id = 'EPLRS', + params = { + value = SwitchOnOff, + groupId = self:GetID(), + }, } - if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandEPLRS, {self, SwitchOnOff}, Delay) + if Delay and Delay > 0 then + SCHEDULER:New( nil, self.CommandEPLRS, { self, SwitchOnOff }, Delay ) else - self:T(string.format("EPLRS=%s for controllable %s (id=%s)", tostring(SwitchOnOff), tostring(self:GetName()), tostring(self:GetID()))) - self:SetCommand(CommandEPLRS) + self:T( string.format( "EPLRS=%s for controllable %s (id=%s)", tostring( SwitchOnOff ), tostring( self:GetName() ), tostring( self:GetID() ) ) ) + self:SetCommand( CommandEPLRS ) end return self @@ -843,52 +881,50 @@ end -- @param #CONTROLLABLE self -- @param #number Frequency Radio frequency in MHz. -- @param #number Modulation Radio modulation. Default `radio.modulation.AM`. --- @param #number Delay (Optional) Delay in seconds before the frequncy is set. Default is immediately. +-- @param #number Delay (Optional) Delay in seconds before the frequency is set. Default is immediately. -- @return #CONTROLLABLE self -function CONTROLLABLE:CommandSetFrequency(Frequency, Modulation, Delay) +function CONTROLLABLE:CommandSetFrequency( Frequency, Modulation, Delay ) local CommandSetFrequency = { id = 'SetFrequency', params = { - frequency = Frequency*1000000, + frequency = Frequency * 1000000, modulation = Modulation or radio.modulation.AM, - } + }, } - if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandSetFrequency, {self, Frequency, Modulation}, Delay) + if Delay and Delay > 0 then + SCHEDULER:New( nil, self.CommandSetFrequency, { self, Frequency, Modulation }, Delay ) else - self:SetCommand(CommandSetFrequency) + self:SetCommand( CommandSetFrequency ) end return self end - --- Set EPLRS data link on/off. -- @param #CONTROLLABLE self -- @param #boolean SwitchOnOff If true (or nil) switch EPLRS on. If false switch off. -- @param #number idx Task index. Default 1. -- @return #table Task wrapped action. -function CONTROLLABLE:TaskEPLRS(SwitchOnOff, idx) +function CONTROLLABLE:TaskEPLRS( SwitchOnOff, idx ) - if SwitchOnOff==nil then - SwitchOnOff=true + if SwitchOnOff == nil then + SwitchOnOff = true end -- Command to set the callsign. - local CommandEPLRS={ - id='EPLRS', - params={ - value=SwitchOnOff, - groupId=self:GetID() - } + local CommandEPLRS = { + id = 'EPLRS', + params = { + value = SwitchOnOff, + groupId = self:GetID(), + }, } - return self:TaskWrappedAction(CommandEPLRS, idx or 1) + return self:TaskWrappedAction( CommandEPLRS, idx or 1 ) end - -- TASKS FOR AIR CONTROLLABLES --- (AIR) Attack a Controllable. @@ -896,14 +932,14 @@ end -- @param Wrapper.Group#GROUP AttackGroup The Group to be attacked. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aircraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aircraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. -- @param DCS#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param DCS#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. -- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackGroup" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. -- @param #boolean GroupAttack (Optional) If true, attack as group. -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit, GroupAttack ) - --self:F2( { self.ControllableName, AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) + -- self:F2( { self.ControllableName, AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) -- AttackGroup = { -- id = 'AttackGroup', @@ -944,12 +980,12 @@ end -- @param Wrapper.Unit#UNIT AttackUnit The UNIT to be attacked -- @param #boolean GroupAttack (Optional) If true, all units in the group will attack the Unit when found. Default false. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (Optional) Determines how many weapons will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (Optional) Limits maximal quantity of attack. The aicraft/controllable will not make more attacks than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param #number AttackQty (Optional) Limits maximal quantity of attack. The aircraft/controllable will not make more attacks than allowed even if the target controllable not destroyed and the aircraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. -- @param DCS#Azimuth Direction (Optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. -- @param #number Altitude (Optional) The (minimum) altitude in meters from where to attack. Default is altitude of unit to attack but at least 1000 m. -- @param #number WeaponType (optional) The WeaponType. See [DCS Enumerator Weapon Type](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) on Hoggit. -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:TaskAttackUnit(AttackUnit, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType) +function CONTROLLABLE:TaskAttackUnit( AttackUnit, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType ) local DCSTask = { id = 'AttackUnit', @@ -964,19 +1000,18 @@ function CONTROLLABLE:TaskAttackUnit(AttackUnit, GroupAttack, WeaponExpend, Atta attackQtyLimit = AttackQty and true or false, attackQty = AttackQty, weaponType = WeaponType or 1073741822, - } + }, } return DCSTask end - --- (AIR) Delivering weapon at the point on the ground. -- @param #CONTROLLABLE self -- @param DCS#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. -- @param #boolean GroupAttack (optional) If true, all units in the group will attack the Unit when found. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aircraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aircraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. -- @param DCS#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param #number Altitude (optional) The altitude from where to attack. -- @param #number WeaponType (optional) The WeaponType. @@ -1011,7 +1046,7 @@ end -- @param DCS#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. -- @param #boolean GroupAttack (Optional) If true, all units in the group will attack the Unit when found. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (Optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit will choose expend on its own discretion. --- @param #number AttackQty (Optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param #number AttackQty (Optional) This parameter limits maximal quantity of attack. The aircraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aircraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. -- @param DCS#Azimuth Direction (Optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param #number Altitude (Optional) The altitude [meters] from where to attack. Default 30 m. -- @param #number WeaponType (Optional) The WeaponType. Default Auto=1073741822. @@ -1039,19 +1074,18 @@ function CONTROLLABLE:TaskAttackMapObject( Vec2, GroupAttack, WeaponExpend, Atta return DCSTask end - --- (AIR) Delivering weapon via CarpetBombing (all bombers in formation release at same time) at the point on the ground. -- @param #CONTROLLABLE self -- @param DCS#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. -- @param #boolean GroupAttack (optional) If true, all units in the group will attack the Unit when found. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aircraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aircraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. -- @param DCS#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param #number Altitude (optional) The altitude from where to attack. -- @param #number WeaponType (optional) The WeaponType. -- @param #number CarpetLength (optional) default to 500 m. -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:TaskCarpetBombing(Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType, CarpetLength) +function CONTROLLABLE:TaskCarpetBombing( Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType, CarpetLength ) -- Build Task Structure local DCSTask = { @@ -1070,14 +1104,12 @@ function CONTROLLABLE:TaskCarpetBombing(Vec2, GroupAttack, WeaponExpend, AttackQ direction = Direction and math.rad(Direction) or 0, altitudeEnabled = Altitude and true or false, altitude = Altitude, - } + }, } return DCSTask end - - --- (AIR) Following another airborne controllable. -- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. -- Used to support CarpetBombing Task @@ -1086,7 +1118,7 @@ end -- @param DCS#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. -- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:TaskFollowBigFormation(FollowControllable, Vec3, LastWaypointIndex ) +function CONTROLLABLE:TaskFollowBigFormation( FollowControllable, Vec3, LastWaypointIndex ) local DCSTask = { id = 'FollowBigFormation', @@ -1094,14 +1126,13 @@ function CONTROLLABLE:TaskFollowBigFormation(FollowControllable, Vec3, LastWaypo groupId = FollowControllable:GetID(), pos = Vec3, lastWptIndexFlag = LastWaypointIndex and true or false, - lastWptIndex = LastWaypointIndex - } + lastWptIndex = LastWaypointIndex, + }, } return DCSTask end - --- (AIR HELICOPTER) Move the controllable to a Vec2 Point, wait for a defined duration and embark infantry groups. -- @param #CONTROLLABLE self -- @param Core.Point#COORDINATE Coordinate The point where to pickup the troops. @@ -1109,29 +1140,29 @@ end -- @param #number Duration (Optional) The maximum duration in seconds to wait until all groups have embarked. -- @param #table Distribution (Optional) Distribution used to put the infantry groups into specific carrier units. -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:TaskEmbarking(Coordinate, GroupSetForEmbarking, Duration, Distribution) +function CONTROLLABLE:TaskEmbarking( Coordinate, GroupSetForEmbarking, Duration, Distribution ) -- Table of group IDs for embarking. - local g4e={} + local g4e = {} if GroupSetForEmbarking then - for _,_group in pairs(GroupSetForEmbarking:GetSet()) do - local group=_group --Wrapper.Group#GROUP - table.insert(g4e, group:GetID()) + for _, _group in pairs( GroupSetForEmbarking:GetSet() ) do + local group = _group -- Wrapper.Group#GROUP + table.insert( g4e, group:GetID() ) end else - self:E("ERROR: No groups for embarking specified!") + self:E( "ERROR: No groups for embarking specified!" ) return nil end -- Table of group IDs for embarking. - --local Distribution={} + -- local Distribution={} -- Distribution - --local distribution={} - --distribution[id]=gids + -- local distribution={} + -- distribution[id]=gids - local groupID=self and self:GetID() + local groupID = self and self:GetID() local DCSTask = { id = 'Embarking', @@ -1144,13 +1175,12 @@ function CONTROLLABLE:TaskEmbarking(Coordinate, GroupSetForEmbarking, Duration, duration = Duration, distributionFlag = Distribution and true or false, distribution = Distribution, - } + }, } return DCSTask end - --- Used in conjunction with the embarking task for a transport helicopter group. The Ground units will move to the specified location and wait to be picked up by a helicopter. -- The helicopter will then fly them to their dropoff point defined by another task for the ground forces; DisembarkFromTransport task. -- The controllable has to be an infantry group! @@ -1159,7 +1189,7 @@ end -- @param #number Radius Radius in meters. Default 200 m. -- @param #string UnitType The unit type name of the carrier, e.g. "UH-1H". Must not be specified. -- @return DCS#Task Embark to transport task. -function CONTROLLABLE:TaskEmbarkToTransport(Coordinate, Radius, UnitType) +function CONTROLLABLE:TaskEmbarkToTransport( Coordinate, Radius, UnitType ) local EmbarkToTransport = { id = "EmbarkToTransport", @@ -1168,46 +1198,44 @@ function CONTROLLABLE:TaskEmbarkToTransport(Coordinate, Radius, UnitType) y = Coordinate.z, zoneRadius = Radius or 200, selectedType = UnitType, - } + }, } return EmbarkToTransport end - --- Specifies the location infantry groups that is being transported by helicopters will be unloaded at. Used in conjunction with the EmbarkToTransport task. -- @param #CONTROLLABLE self -- @param Core.Point#COORDINATE Coordinate Coordinates where AI is expecting to be picked up. -- @return DCS#Task Embark to transport task. -function CONTROLLABLE:TaskDisembarking(Coordinate, GroupSetToDisembark) +function CONTROLLABLE:TaskDisembarking( Coordinate, GroupSetToDisembark ) -- Table of group IDs for disembarking. - local g4e={} + local g4e = {} if GroupSetToDisembark then - for _,_group in pairs(GroupSetToDisembark:GetSet()) do - local group=_group --Wrapper.Group#GROUP - table.insert(g4e, group:GetID()) + for _, _group in pairs( GroupSetToDisembark:GetSet() ) do + local group = _group -- Wrapper.Group#GROUP + table.insert( g4e, group:GetID() ) end else - self:E("ERROR: No groups for disembarking specified!") + self:E( "ERROR: No groups for disembarking specified!" ) return nil end - local Disembarking={ - id = "Disembarking", - params = { - x = Coordinate.x, - y = Coordinate.z, - groupsForEmbarking = g4e, -- This is no bug, the entry is really "groupsForEmbarking" even if we disembark the troops. - } - } + local Disembarking = { + id = "Disembarking", + params = { + x = Coordinate.x, + y = Coordinate.z, + groupsForEmbarking = g4e, -- This is no bug, the entry is really "groupsForEmbarking" even if we disembark the troops. + }, + } return Disembarking end - ---- (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. +--- (AIR) Orbit at a specified position at a specified altitude during a specified duration with a specified speed. -- @param #CONTROLLABLE self -- @param DCS#Vec2 Point The point to hold the position. -- @param #number Altitude The altitude AGL in meters to hold the position. @@ -1222,8 +1250,8 @@ function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed ) pattern = AI.Task.OrbitPattern.CIRCLE, point = Point, speed = Speed, - altitude = Altitude + land.getHeight( Point ) - } + altitude = Altitude + land.getHeight( Point ), + }, } return DCSTask @@ -1236,15 +1264,15 @@ end -- @param #number Speed Speed [m/s] flying the orbit pattern. Default 128 m/s = 250 knots. -- @param Core.Point#COORDINATE CoordRaceTrack (Optional) If this coordinate is specified, the CONTROLLABLE will fly a race-track pattern using this and the initial coordinate. -- @return #CONTROLLABLE self -function CONTROLLABLE:TaskOrbit(Coord, Altitude, Speed, CoordRaceTrack) +function CONTROLLABLE:TaskOrbit( Coord, Altitude, Speed, CoordRaceTrack ) - local Pattern=AI.Task.OrbitPattern.CIRCLE + local Pattern = AI.Task.OrbitPattern.CIRCLE - local P1=Coord:GetVec2() - local P2=nil + local P1 = Coord:GetVec2() + local P2 = nil if CoordRaceTrack then - Pattern=AI.Task.OrbitPattern.RACE_TRACK - P2=CoordRaceTrack:GetVec2() + Pattern = AI.Task.OrbitPattern.RACE_TRACK + P2 = CoordRaceTrack:GetVec2() end local Task = { @@ -1255,13 +1283,13 @@ function CONTROLLABLE:TaskOrbit(Coord, Altitude, Speed, CoordRaceTrack) point2 = P2, speed = Speed or UTILS.KnotsToMps(250), altitude = Altitude or Coord.y, - } + }, } return Task end ---- (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. +--- (AIR) Orbit at the current position of the first unit of the controllable at a specified altitude. -- @param #CONTROLLABLE self -- @param #number Altitude The altitude [m] to hold the position. -- @param #number Speed The speed [m/s] flying when holding the position. @@ -1280,8 +1308,6 @@ function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed, Coordinate ) return nil end - - --- (AIR) Hold position at the current position of the first unit of the controllable. -- @param #CONTROLLABLE self -- @param #number Duration The maximum duration in seconds to hold the position. @@ -1292,7 +1318,6 @@ function CONTROLLABLE:TaskHoldPosition() return self:TaskOrbitCircle( 30, 10 ) end - --- (AIR) Delivering weapon on the runway. See [hoggit](https://wiki.hoggitworld.com/view/DCS_task_bombingRunway) -- -- Make sure the aircraft has the following role: @@ -1312,7 +1337,7 @@ end -- @param DCS#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param #boolean GroupAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a group and not to a single aircraft. -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:TaskBombingRunway(Airbase, WeaponType, WeaponExpend, AttackQty, Direction, GroupAttack) +function CONTROLLABLE:TaskBombingRunway( Airbase, WeaponType, WeaponExpend, AttackQty, Direction, GroupAttack ) local DCSTask = { id = 'BombingRunway', @@ -1329,27 +1354,25 @@ function CONTROLLABLE:TaskBombingRunway(Airbase, WeaponType, WeaponExpend, Attac return DCSTask end - --- (AIR) Refueling from the nearest tanker. No parameters. -- @param #CONTROLLABLE self -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskRefueling() - local DCSTask={ - id='Refueling', - params={} + local DCSTask = { + id = 'Refueling', + params = {}, } return DCSTask end - --- (AIR HELICOPTER) Landing at the ground. For helicopters only. -- @param #CONTROLLABLE self -- @param DCS#Vec2 Vec2 The point where to land. -- @param #number Duration The duration in seconds to stay on the ground. -- @return #CONTROLLABLE self -function CONTROLLABLE:TaskLandAtVec2(Vec2, Duration) +function CONTROLLABLE:TaskLandAtVec2( Vec2, Duration ) local DCSTask = { id = 'Land', @@ -1371,15 +1394,13 @@ end function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint ) -- Get landing point - local Point=RandomPoint and Zone:GetRandomVec2() or Zone:GetVec2() + local Point = RandomPoint and Zone:GetRandomVec2() or Zone:GetVec2() local DCSTask = CONTROLLABLE.TaskLandAtVec2( self, Point, Duration ) return DCSTask end - - --- (AIR) Following another airborne controllable. -- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. -- If another controllable is on land the unit / controllable will orbit around. @@ -1391,15 +1412,15 @@ end function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex ) self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex } ) --- Follow = { --- id = 'Follow', --- params = { --- groupId = Group.ID, --- pos = Vec3, --- lastWptIndexFlag = boolean, --- lastWptIndex = number --- } --- } + -- Follow = { + -- id = 'Follow', + -- params = { + -- groupId = Group.ID, + -- pos = Vec3, + -- lastWptIndexFlag = boolean, + -- lastWptIndex = number + -- } + -- } local LastWaypointIndexFlag = false local lastWptIndexFlagChangedManually = false @@ -1416,14 +1437,13 @@ function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex ) lastWptIndexFlag = LastWaypointIndexFlag, lastWptIndex = LastWaypointIndex, lastWptIndexFlagChangedManually = lastWptIndexFlagChangedManually, - } + }, } self:T3( { DCSTask } ) return DCSTask end - --- (AIR) Escort another airborne controllable. -- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. -- The unit / controllable will also protect that controllable from threats of specified types. @@ -1436,17 +1456,17 @@ end -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes ) --- Escort = { --- id = 'Escort', --- params = { --- groupId = Group.ID, --- pos = Vec3, --- lastWptIndexFlag = boolean, --- lastWptIndex = number, --- engagementDistMax = Distance, --- targetTypes = array of AttributeName, --- } --- } + -- Escort = { + -- id = 'Escort', + -- params = { + -- groupId = Group.ID, + -- pos = Vec3, + -- lastWptIndexFlag = boolean, + -- lastWptIndex = number, + -- engagementDistMax = Distance, + -- targetTypes = array of AttributeName, + -- } + -- } local DCSTask DCSTask = { @@ -1464,7 +1484,6 @@ function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, E return DCSTask end - -- GROUND TASKS --- (GROUND) Fire at a VEC2 point until ammunition is finished. @@ -1488,8 +1507,8 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti radius = Radius, expendQty = 1, -- dummy value expendQtyEnabled = false, - alt_type = ASL and 0 or 1 - } + alt_type = ASL and 0 or 1, + }, } if AmmoCount then @@ -1498,11 +1517,11 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti end if Altitude then - DCSTask.params.altitude=Altitude + DCSTask.params.altitude = Altitude end if WeaponType then - DCSTask.params.weaponType=WeaponType + DCSTask.params.weaponType = WeaponType end --env.info("FF fireatpoint") @@ -1515,11 +1534,10 @@ end -- @param #CONTROLLABLE self -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskHold() - local DCSTask = {id = 'Hold', params = {}} + local DCSTask = { id = 'Hold', params = {} } return DCSTask end - -- TASKS FOR AIRBORNE AND GROUND UNITS/CONTROLLABLES --- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. @@ -1549,7 +1567,7 @@ function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, modulation = Modulation or radio.modulation.AM, callname = CallsignName, number = CallsignNumber, - } + }, } return DCSTask @@ -1572,14 +1590,12 @@ function CONTROLLABLE:EnRouteTaskEngageTargets( Distance, TargetTypes, Priority maxDist = Distance, targetTypes = TargetTypes or {"Air"}, priority = Priority or 0, - } + }, } return DCSTask end - - --- (AIR) Engaging a targets of defined types at circle-shaped zone. -- @param #CONTROLLABLE self -- @param DCS#Vec2 Vec2 2D-coordinates of the zone. @@ -1596,7 +1612,7 @@ function CONTROLLABLE:EnRouteTaskEngageTargetsInZone( Vec2, Radius, TargetTypes, zoneRadius = Radius, targetTypes = TargetTypes or {"Air"}, priority = Priority or 0 - } + }, } return DCSTask @@ -1630,7 +1646,7 @@ end -- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aircraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aircraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. -- @param DCS#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param DCS#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. -- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackGroup" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. @@ -1672,14 +1688,13 @@ function CONTROLLABLE:EnRouteTaskEngageGroup( AttackGroup, Priority, WeaponType, return DCSTask end - --- (AIR) Search and attack the Unit. -- @param #CONTROLLABLE self -- @param Wrapper.Unit#UNIT EngageUnit The UNIT. -- @param #number Priority (optional) All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. -- @param #boolean GroupAttack (optional) If true, all units in the group will attack the Unit when found. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aircraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aircraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. -- @param DCS#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param DCS#Distance Altitude (optional) Desired altitude to perform the unit engagement. -- @param #boolean Visible (optional) Unit must be visible. @@ -1708,12 +1723,10 @@ function CONTROLLABLE:EnRouteTaskEngageUnit( EngageUnit, Priority, GroupAttack, return DCSTask end - - --- (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. -- @param #CONTROLLABLE self -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskAWACS( ) +function CONTROLLABLE:EnRouteTaskAWACS() local DCSTask = { id = 'AWACS', @@ -1723,11 +1736,10 @@ function CONTROLLABLE:EnRouteTaskAWACS( ) return DCSTask end - --- (AIR) Aircraft will act as a tanker for friendly units. No parameters. -- @param #CONTROLLABLE self -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskTanker( ) +function CONTROLLABLE:EnRouteTaskTanker() local DCSTask = { id = 'Tanker', @@ -1737,13 +1749,12 @@ function CONTROLLABLE:EnRouteTaskTanker( ) return DCSTask end - -- En-route tasks for ground units/controllables --- (GROUND) Ground unit (EW-radar) will act as an EWR for friendly units (will provide them with information about contacts). No parameters. -- @param #CONTROLLABLE self -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEWR( ) +function CONTROLLABLE:EnRouteTaskEWR() local DCSTask = { id = 'EWR', @@ -1753,7 +1764,6 @@ function CONTROLLABLE:EnRouteTaskEWR( ) return DCSTask end - -- En-route tasks for airborne and ground units/controllables --- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. @@ -1776,13 +1786,12 @@ function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponT designation = Designation, datalink = Datalink and Datalink or false, priority = Priority or 0, - } + }, } return DCSTask end - --- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. -- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. -- If the task is assigned to the controllable lead unit will be a FAC. @@ -1792,13 +1801,13 @@ end -- @return DCS#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskFAC( Radius, Priority ) --- FAC = { --- id = 'FAC', --- params = { --- radius = Distance, --- priority = number --- } --- } + -- FAC = { + -- id = 'FAC', + -- params = { + -- radius = Distance, + -- priority = number + -- } + -- } local DCSTask = { id = 'FAC', @@ -1811,7 +1820,6 @@ function CONTROLLABLE:EnRouteTaskFAC( Radius, Priority ) return DCSTask end - --- This creates a Task element, with an action to call a function as part of a Wrapped Task. -- This Task can then be embedded at a Waypoint by calling the method @{#CONTROLLABLE.SetTaskWaypoint}. -- @param #CONTROLLABLE self @@ -1864,24 +1872,22 @@ function CONTROLLABLE:TaskFunction( FunctionString, ... ) -- Script local DCSScript = {} - DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:Find( ... ) " + DCSScript[#DCSScript + 1] = "local MissionControllable = GROUP:Find( ... ) " if arg and arg.n > 0 then - local ArgumentKey = '_' .. tostring( arg ):match("table: (.*)") + 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 ) )" + DCSScript[#DCSScript + 1] = "local Arguments = MissionControllable:GetState( MissionControllable, '" .. ArgumentKey .. "' ) " + DCSScript[#DCSScript + 1] = FunctionString .. "( MissionControllable, unpack( Arguments ) )" else - DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable )" + DCSScript[#DCSScript + 1] = FunctionString .. "( MissionControllable )" end -- DCS task. - local DCSTask = self:TaskWrappedAction(self:CommandDoScript(table.concat( DCSScript ))) + local DCSTask = self:TaskWrappedAction( self:CommandDoScript( table.concat( DCSScript ) ) ) return DCSTask end - - --- (AIR + GROUND) Return a mission task from a mission template. -- @param #CONTROLLABLE self -- @param #table TaskMission A table containing the mission task. @@ -1890,13 +1896,14 @@ function CONTROLLABLE:TaskMission( TaskMission ) local DCSTask = { id = 'Mission', - params = { TaskMission, }, + params = { + TaskMission, + }, } return DCSTask end - do -- Patrol methods --- (GROUND) Patrol iteratively using the waypoints of the (parent) group. @@ -1943,7 +1950,7 @@ do -- Patrol methods local TaskRoute = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRoute" ) - self:F({Waypoints = Waypoints}) + self:F( { Waypoints = Waypoints } ) local Waypoint = Waypoints[#Waypoints] PatrolGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. @@ -1989,25 +1996,25 @@ do -- Patrol methods end end -- Loop until a waypoint has been found that is not the same as the current waypoint. - -- Otherwise the object zon't move or drive in circles and the algorithm would not do exactly + -- Otherwise the object won't move, or drive in circles, and the algorithm would not do exactly -- what it is supposed to do, which is making groups drive around. local ToWaypoint repeat -- Select a random waypoint and check if it is not the same waypoint as where the object is about. ToWaypoint = math.random( 1, #Waypoints ) - until( ToWaypoint ~= FromWaypoint ) + until (ToWaypoint ~= FromWaypoint) self:F( { FromWaypoint = FromWaypoint, ToWaypoint = ToWaypoint } ) - local Waypoint = Waypoints[ToWaypoint] -- Select random waypoint. + local Waypoint = Waypoints[ToWaypoint] -- Select random waypoint. local ToCoord = COORDINATE:NewFromVec2( { x = Waypoint.x, y = Waypoint.y } ) -- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task local Route = {} if IsSub then - Route[#Route+1] = FromCoord:WaypointNaval( Speed, depth ) - Route[#Route+1] = ToCoord:WaypointNaval( Speed, Waypoint.alt ) + Route[#Route + 1] = FromCoord:WaypointNaval( Speed, depth ) + Route[#Route + 1] = ToCoord:WaypointNaval( Speed, Waypoint.alt ) else - Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation ) - Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) + Route[#Route + 1] = FromCoord:WaypointGround( Speed, Formation ) + Route[#Route + 1] = ToCoord:WaypointGround( Speed, Formation ) end local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRouteRandom", Speed, Formation, ToWaypoint ) @@ -2039,12 +2046,12 @@ do -- Patrol methods PatrolGroup = self:GetGroup() -- Wrapper.Group#GROUP end - DelayMin=DelayMin or 1 - if not DelayMax or DelayMax LengthDirect*10) or (LengthRoad/LengthOnRoad*100<5)) + LongRoad = LengthOnRoad and ((LengthOnRoad > LengthDirect * 10) or (LengthRoad / LengthOnRoad * 100 < 5)) -- Debug info. - self:T(string.format("Length on road = %.3f km", LengthOnRoad/1000)) - self:T(string.format("Length directly = %.3f km", LengthDirect/1000)) - self:T(string.format("Length fraction = %.3f km", LengthOnRoad/LengthDirect)) - self:T(string.format("Length only road = %.3f km", LengthRoad/1000)) - self:T(string.format("Length off road = %.3f km", LengthOffRoad/1000)) - self:T(string.format("Percent on road = %.1f", LengthRoad/LengthOnRoad*100)) + self:T( string.format( "Length on road = %.3f km", LengthOnRoad / 1000 ) ) + self:T( string.format( "Length directly = %.3f km", LengthDirect / 1000 ) ) + self:T( string.format( "Length fraction = %.3f km", LengthOnRoad / LengthDirect ) ) + self:T( string.format( "Length only road = %.3f km", LengthRoad / 1000 ) ) + self:T( string.format( "Length off road = %.3f km", LengthOffRoad / 1000 ) ) + self:T( string.format( "Percent on road = %.1f", LengthRoad / LengthOnRoad * 100 ) ) end -- Route, ground waypoints along road. - local route={} - local canroad=false + local route = {} + local canroad = false -- Check if a valid path on road could be found. if GotPath and LengthRoad and LengthDirect > 2000 then -- if the length of the movement is less than 1 km, drive directly. @@ -2422,43 +2420,43 @@ do -- Route methods -- Road is long ==> we take the short cut. - table.insert(route, FromCoordinate:WaypointGround(Speed, OffRoadFormation)) - table.insert(route, ToCoordinate:WaypointGround(Speed, OffRoadFormation)) + table.insert( route, FromCoordinate:WaypointGround( Speed, OffRoadFormation ) ) + table.insert( route, ToCoordinate:WaypointGround( Speed, OffRoadFormation ) ) else -- Create waypoints. - table.insert(route, FromCoordinate:WaypointGround(Speed, OffRoadFormation)) - table.insert(route, PathOnRoad[2]:WaypointGround(Speed, "On Road")) - table.insert(route, PathOnRoad[#PathOnRoad-1]:WaypointGround(Speed, "On Road")) + table.insert( route, FromCoordinate:WaypointGround( Speed, OffRoadFormation ) ) + table.insert( route, PathOnRoad[2]:WaypointGround( Speed, "On Road" ) ) + table.insert( route, PathOnRoad[#PathOnRoad - 1]:WaypointGround( Speed, "On Road" ) ) -- Add the final coordinate because the final might not be on the road. - local dist=ToCoordinate:Get2DDistance(PathOnRoad[#PathOnRoad-1]) - if dist>10 then - table.insert(route, ToCoordinate:WaypointGround(Speed, OffRoadFormation)) - table.insert(route, ToCoordinate:GetRandomCoordinateInRadius(10,5):WaypointGround(5, OffRoadFormation)) - table.insert(route, ToCoordinate:GetRandomCoordinateInRadius(10,5):WaypointGround(5, OffRoadFormation)) + local dist = ToCoordinate:Get2DDistance( PathOnRoad[#PathOnRoad - 1] ) + if dist > 10 then + table.insert( route, ToCoordinate:WaypointGround( Speed, OffRoadFormation ) ) + table.insert( route, ToCoordinate:GetRandomCoordinateInRadius( 10, 5 ):WaypointGround( 5, OffRoadFormation ) ) + table.insert( route, ToCoordinate:GetRandomCoordinateInRadius( 10, 5 ):WaypointGround( 5, OffRoadFormation ) ) end end - canroad=true + canroad = true else -- No path on road could be found (can happen!) ==> Route group directly from A to B. - table.insert(route, FromCoordinate:WaypointGround(Speed, OffRoadFormation)) - table.insert(route, ToCoordinate:WaypointGround(Speed, OffRoadFormation)) + table.insert( route, FromCoordinate:WaypointGround( Speed, OffRoadFormation ) ) + table.insert( route, ToCoordinate:WaypointGround( Speed, OffRoadFormation ) ) end -- Add passing waypoint function. if WaypointFunction then - local N=#route - for n,waypoint in pairs(route) do + local N = #route + for n, waypoint in pairs( route ) do waypoint.task = {} waypoint.task.id = "ComboTask" waypoint.task.params = {} - waypoint.task.params.tasks = {self:TaskFunction("CONTROLLABLE.___PassingWaypoint", n, N, WaypointFunction, unpack(WaypointFunctionArguments or {}))} + waypoint.task.params.tasks = { self:TaskFunction( "CONTROLLABLE.___PassingWaypoint", n, N, WaypointFunction, unpack( WaypointFunctionArguments or {} ) ) } end end @@ -2472,40 +2470,40 @@ do -- Route methods -- @param #function WaypointFunction (Optional) Function called when passing a waypoint. First parameters of the function are the @{CONTROLLABLE} object, the number of the waypoint and the total number of waypoints. -- @param #table WaypointFunctionArguments (Optional) List of parameters passed to the *WaypointFunction*. -- @return Task - function CONTROLLABLE:TaskGroundOnRailRoads(ToCoordinate, Speed, WaypointFunction, WaypointFunctionArguments ) - self:F2({ToCoordinate=ToCoordinate, Speed=Speed}) + function CONTROLLABLE:TaskGroundOnRailRoads( ToCoordinate, Speed, WaypointFunction, WaypointFunctionArguments ) + self:F2( { ToCoordinate = ToCoordinate, Speed = Speed } ) -- Defaults. - Speed=Speed or 20 + Speed = Speed or 20 -- Current coordinate. local FromCoordinate = self:GetCoordinate() -- Get path and path length on railroad. - local PathOnRail, LengthOnRail=FromCoordinate:GetPathOnRoad(ToCoordinate, false, true) + local PathOnRail, LengthOnRail = FromCoordinate:GetPathOnRoad( ToCoordinate, false, true ) -- Debug info. - self:T(string.format("Length on railroad = %.3f km", LengthOnRail/1000)) + self:T( string.format( "Length on railroad = %.3f km", LengthOnRail / 1000 ) ) -- Route, ground waypoints along road. - local route={} + local route = {} -- Check if a valid path on railroad could be found. if PathOnRail then - table.insert(route, PathOnRail[1]:WaypointGround(Speed, "On Railroad")) - table.insert(route, PathOnRail[2]:WaypointGround(Speed, "On Railroad")) + table.insert( route, PathOnRail[1]:WaypointGround( Speed, "On Railroad" ) ) + table.insert( route, PathOnRail[2]:WaypointGround( Speed, "On Railroad" ) ) end -- Add passing waypoint function. if WaypointFunction then - local N=#route - for n,waypoint in pairs(route) do + local N = #route + for n, waypoint in pairs( route ) do waypoint.task = {} waypoint.task.id = "ComboTask" waypoint.task.params = {} - waypoint.task.params.tasks = {self:TaskFunction("CONTROLLABLE.___PassingWaypoint", n, N, WaypointFunction, unpack(WaypointFunctionArguments or {}))} + waypoint.task.params.tasks = { self:TaskFunction( "CONTROLLABLE.___PassingWaypoint", n, N, WaypointFunction, unpack( WaypointFunctionArguments or {} ) ) } end end @@ -2517,11 +2515,10 @@ do -- Route methods -- @param #number n Current waypoint number passed. -- @param #number N Total number of waypoints. -- @param #function waypointfunction Function called when a waypoint is passed. - function CONTROLLABLE.___PassingWaypoint(controllable, n, N, waypointfunction, ...) - waypointfunction(controllable, n, N, ...) + function CONTROLLABLE.___PassingWaypoint( controllable, n, N, waypointfunction, ... ) + waypointfunction( controllable, n, N, ... ) end - --- Make the AIR Controllable fly towards a specific point. -- @param #CONTROLLABLE self -- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. @@ -2543,7 +2540,6 @@ do -- Route methods return self end - --- (AIR + GROUND) Route the controllable to a given zone. -- The controllable final destination point can be randomized. -- A speed can be given in km/h. @@ -2569,7 +2565,6 @@ do -- Route methods PointFrom.action = Formation or "Cone" PointFrom.speed = 20 / 3.6 - local PointTo = {} local ZonePoint @@ -2629,7 +2624,6 @@ do -- Route methods PointFrom.action = Formation or "Cone" PointFrom.speed = 20 / 3.6 - local PointTo = {} PointTo.x = Vec2.x @@ -2681,7 +2675,6 @@ function CONTROLLABLE:CommandDoScript( DoScript ) return DCSDoScript end - --- Return the mission template of the controllable. -- @param #CONTROLLABLE self -- @return #table The MissionTemplate @@ -2701,8 +2694,6 @@ function CONTROLLABLE:GetTaskRoute() return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template.route.points ) end - - --- Return the route of a controllable by using the @{Core.Database#DATABASE} class. -- @param #CONTROLLABLE self -- @param #number Begin The route point from where the copy will start. The base route point is 0. @@ -2736,7 +2727,7 @@ function CONTROLLABLE:CopyRoute( Begin, End, Randomize, Radius ) 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] ) + Points[#Points + 1] = routines.utils.deepCopy( Template.route.points[TPointID] ) if Randomize then if not Radius then Radius = 500 @@ -2754,7 +2745,6 @@ function CONTROLLABLE:CopyRoute( Begin, End, Randomize, Radius ) return nil end - --- Return the detected targets of the controllable. -- The optional parametes specify the detection methods that can be applied. -- If no detection method is given, the detection will use all the available methods by default. @@ -2773,35 +2763,33 @@ function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRad 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 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 Params = {} if DetectionVisual then - Params[#Params+1] = DetectionVisual + Params[#Params + 1] = DetectionVisual end if DetectionOptical then - Params[#Params+1] = DetectionOptical + Params[#Params + 1] = DetectionOptical end if DetectionRadar then - Params[#Params+1] = DetectionRadar + Params[#Params + 1] = DetectionRadar end if DetectionIRST then - Params[#Params+1] = DetectionIRST + Params[#Params + 1] = DetectionIRST end if DetectionRWR then - Params[#Params+1] = DetectionRWR + Params[#Params + 1] = DetectionRWR end if DetectionDLINK then - Params[#Params+1] = DetectionDLINK + Params[#Params + 1] = DetectionDLINK end - self:T2( { DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK } ) return self:_GetController():getDetectedTargets( Params[1], Params[2], Params[3], Params[4], Params[5], Params[6] ) @@ -2837,12 +2825,12 @@ function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical, 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 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() @@ -2878,7 +2866,7 @@ function CONTROLLABLE:IsUnitDetected( Unit, DetectVisual, DetectOptical, DetectR self:F2( self.ControllableName ) if Unit and Unit:IsAlive() then - return self:IsTargetDetected(Unit:GetDCSObject(), DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) + return self:IsTargetDetected( Unit:GetDCSObject(), DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) end return nil @@ -2901,11 +2889,11 @@ function CONTROLLABLE:IsGroupDetected( Group, DetectVisual, DetectOptical, Detec self:F2( self.ControllableName ) if Group and Group:IsAlive() then - for _,_unit in pairs(Group:GetUnits()) do - local unit=_unit --Wrapper.Unit#UNIT + for _, _unit in pairs( Group:GetUnits() ) do + local unit = _unit -- Wrapper.Unit#UNIT if unit and unit:IsAlive() then - local isdetected=self:IsUnitDetected(unit, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) + local isdetected = self:IsUnitDetected( unit, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) if isdetected then return true @@ -2918,7 +2906,6 @@ function CONTROLLABLE:IsGroupDetected( Group, DetectVisual, DetectOptical, Detec return nil end - --- Return the detected targets of the controllable. -- The optional parametes specify the detection methods that can be applied. -- If **no** detection method is given, the detection will use **all** the available methods by default. @@ -2931,23 +2918,23 @@ end -- @param #boolean DetectRWR (Optional) If *false*, do not include targets detected by RWR. -- @param #boolean DetectDLINK (Optional) If *false*, do not include targets detected by data link. -- @return Core.Set#SET_UNIT Set of detected units. -function CONTROLLABLE:GetDetectedUnitSet(DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) +function CONTROLLABLE:GetDetectedUnitSet( DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) -- Get detected DCS units. - local detectedtargets=self:GetDetectedTargets(DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) + local detectedtargets = self:GetDetectedTargets( DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) - local unitset=SET_UNIT:New() + local unitset = SET_UNIT:New() - for DetectionObjectID, Detection in pairs(detectedtargets or {}) do - local DetectedObject=Detection.object -- DCS#Object + for DetectionObjectID, Detection in pairs( detectedtargets or {} ) do + local DetectedObject = Detection.object -- DCS#Object - if DetectedObject and DetectedObject:isExist() and DetectedObject.id_<50000000 then - local unit=UNIT:Find(DetectedObject) + if DetectedObject and DetectedObject:isExist() and DetectedObject.id_ < 50000000 then + local unit = UNIT:Find( DetectedObject ) if unit and unit:IsAlive() then - if not unitset:FindUnit(unit:GetName()) then - unitset:AddUnit(unit) + if not unitset:FindUnit( unit:GetName() ) then + unitset:AddUnit( unit ) end end @@ -2968,24 +2955,24 @@ end -- @param #boolean DetectRWR (Optional) If *false*, do not include targets detected by RWR. -- @param #boolean DetectDLINK (Optional) If *false*, do not include targets detected by data link. -- @return Core.Set#SET_GROUP Set of detected groups. -function CONTROLLABLE:GetDetectedGroupSet(DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) +function CONTROLLABLE:GetDetectedGroupSet( DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) -- Get detected DCS units. - local detectedtargets=self:GetDetectedTargets(DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) + local detectedtargets = self:GetDetectedTargets( DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) - local groupset=SET_GROUP:New() + local groupset = SET_GROUP:New() - for DetectionObjectID, Detection in pairs(detectedtargets or {}) do - local DetectedObject=Detection.object -- DCS#Object + for DetectionObjectID, Detection in pairs( detectedtargets or {} ) do + local DetectedObject = Detection.object -- DCS#Object - if DetectedObject and DetectedObject:isExist() and DetectedObject.id_<50000000 then - local unit=UNIT:Find(DetectedObject) + if DetectedObject and DetectedObject:isExist() and DetectedObject.id_ < 50000000 then + local unit = UNIT:Find( DetectedObject ) if unit and unit:IsAlive() then - local group=unit:GetGroup() + local group = unit:GetGroup() - if group and not groupset:FindGroup(group:GetName()) then - groupset:AddGroup(group) + if group and not groupset:FindGroup( group:GetName() ) then + groupset:AddGroup( group ) end end @@ -2995,7 +2982,6 @@ function CONTROLLABLE:GetDetectedGroupSet(DetectVisual, DetectOptical, DetectRad return groupset end - -- Options --- Set option. @@ -3003,7 +2989,7 @@ end -- @param #number OptionID ID/Type of the option. -- @param #number OptionValue Value of the option -- @return #CONTROLLABLE self -function CONTROLLABLE:SetOption(OptionID, OptionValue) +function CONTROLLABLE:SetOption( OptionID, OptionValue ) local DCSControllable = self:GetDCSObject() if DCSControllable then @@ -3021,7 +3007,7 @@ end -- @param Wrapper.Controllable#CONTROLLABLE self -- @param #number ROEvalue ROE value. See ENUMS.ROE. -- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROE(ROEvalue) +function CONTROLLABLE:OptionROE( ROEvalue ) local DCSControllable = self:GetDCSObject() @@ -3030,11 +3016,11 @@ function CONTROLLABLE:OptionROE(ROEvalue) local Controller = self:_GetController() if self:IsAir() then - Controller:setOption(AI.Option.Air.id.ROE, ROEvalue ) + Controller:setOption( AI.Option.Air.id.ROE, ROEvalue ) elseif self:IsGround() then - Controller:setOption(AI.Option.Ground.id.ROE, ROEvalue ) + Controller:setOption( AI.Option.Ground.id.ROE, ROEvalue ) elseif self:IsShip() then - Controller:setOption(AI.Option.Naval.id.ROE, ROEvalue ) + Controller:setOption( AI.Option.Naval.id.ROE, ROEvalue ) end return self @@ -3264,7 +3250,6 @@ function CONTROLLABLE:OptionROTNoReactionPossible() return nil end - --- No evasion on enemy threats. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self @@ -3289,7 +3274,7 @@ end -- @param #CONTROLLABLE self -- @param #number ROTvalue ROT value. See ENUMS.ROT. -- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROT(ROTvalue) +function CONTROLLABLE:OptionROT( ROTvalue ) self:F2( { self.ControllableName } ) local DCSControllable = self:GetDCSObject() @@ -3362,7 +3347,6 @@ function CONTROLLABLE:OptionROTEvadeFirePossible() return nil end - --- Evade on fire. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self @@ -3401,7 +3385,6 @@ function CONTROLLABLE:OptionROTVerticalPossible() return nil end - --- Evade on fire using vertical manoeuvres. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self @@ -3433,10 +3416,10 @@ function CONTROLLABLE:OptionAlarmStateAuto() local Controller = self:_GetController() if self:IsGround() then - Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO) + Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO ) elseif self:IsShip() then - --Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.AUTO) - Controller:setOption(9, 0) + -- Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.AUTO) + Controller:setOption( 9, 0 ) end return self @@ -3459,8 +3442,8 @@ function CONTROLLABLE:OptionAlarmStateGreen() Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN ) elseif self:IsShip() then -- AI.Option.Naval.id.ALARM_STATE does not seem to exist! - --Controller:setOption( AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.GREEN ) - Controller:setOption(9, 1) + -- Controller:setOption( AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.GREEN ) + Controller:setOption( 9, 1 ) end return self @@ -3480,10 +3463,10 @@ function CONTROLLABLE:OptionAlarmStateRed() local Controller = self:_GetController() if self:IsGround() then - Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.RED) + Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.RED ) elseif self:IsShip() then - --Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.RED) - Controller:setOption(9, 2) + -- Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.RED) + Controller:setOption( 9, 2 ) end return self @@ -3492,18 +3475,17 @@ function CONTROLLABLE:OptionAlarmStateRed() return nil end - --- Set RTB on bingo fuel. -- @param #CONTROLLABLE self -- @param #boolean RTB true if RTB on bingo fuel (default), false if no RTB on bingo fuel. -- Warning! When you switch this option off, the airborne group will continue to fly until all fuel has been consumed, and will crash. -- @return #CONTROLLABLE self -function CONTROLLABLE:OptionRTBBingoFuel( RTB ) --R2.2 +function CONTROLLABLE:OptionRTBBingoFuel( RTB ) -- R2.2 self:F2( { self.ControllableName } ) - --RTB = RTB or true - if RTB==nil then - RTB=true + -- RTB = RTB or true + if RTB == nil then + RTB = true end local DCSControllable = self:GetDCSObject() @@ -3520,7 +3502,6 @@ function CONTROLLABLE:OptionRTBBingoFuel( RTB ) --R2.2 return nil end - --- Set RTB on ammo. -- @param #CONTROLLABLE self -- @param #boolean WeaponsFlag Weapons.flag enumerator. @@ -3542,7 +3523,6 @@ function CONTROLLABLE:OptionRTBAmmo( WeaponsFlag ) return nil end - --- Allow to Jettison of weapons upon threat. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self @@ -3563,7 +3543,6 @@ function CONTROLLABLE:OptionAllowJettisonWeaponsOnThreat() return nil end - --- Keep weapons upon threat. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self @@ -3588,15 +3567,15 @@ end -- @param #CONTROLLABLE self -- @param #boolean Prohibit If true or nil, prohibit. If false, do not prohibit. -- @return #CONTROLLABLE self -function CONTROLLABLE:OptionProhibitAfterburner(Prohibit) +function CONTROLLABLE:OptionProhibitAfterburner( Prohibit ) self:F2( { self.ControllableName } ) - if Prohibit==nil then - Prohibit=true + if Prohibit == nil then + Prohibit = true end if self:IsAir() then - self:SetOption(AI.Option.Air.id.PROHIBIT_AB, Prohibit) + self:SetOption( AI.Option.Air.id.PROHIBIT_AB, Prohibit ) end return self @@ -3609,7 +3588,7 @@ function CONTROLLABLE:OptionECM_Never() self:F2( { self.ControllableName } ) if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 0) + self:SetOption( AI.Option.Air.id.ECM_USING, 0 ) end return self @@ -3622,13 +3601,12 @@ function CONTROLLABLE:OptionECM_OnlyLockByRadar() self:F2( { self.ControllableName } ) if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 1) + self:SetOption( AI.Option.Air.id.ECM_USING, 1 ) end return self end - --- Defines the usage of Electronic Counter Measures by airborne forces. If the AI is being detected by a radar they will enable their ECM. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self @@ -3636,7 +3614,7 @@ function CONTROLLABLE:OptionECM_DetectedLockByRadar() self:F2( { self.ControllableName } ) if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 2) + self:SetOption( AI.Option.Air.id.ECM_USING, 2 ) end return self @@ -3649,7 +3627,7 @@ function CONTROLLABLE:OptionECM_AlwaysOn() self:F2( { self.ControllableName } ) if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 3) + self:SetOption( AI.Option.Air.id.ECM_USING, 3 ) end return self @@ -3678,7 +3656,7 @@ end -- @param #CONTROLLABLE self -- @return #table WayPoints If WayPoints is given, then return the WayPoints structure. function CONTROLLABLE:GetWayPoints() - self:F( ) + self:F() if self.WayPoints then return self.WayPoints @@ -3701,7 +3679,6 @@ function CONTROLLABLE:WayPointFunction( WayPoint, WayPointIndex, WayPointFunctio return self end - --- Executes the WayPoint plan. -- The function gets a WayPoint parameter, that you can use to restart the mission at a specific WayPoint. -- Note that when the WayPoint parameter is used, the new start mission waypoint of the controllable will be 1! @@ -3763,8 +3740,8 @@ end --- Sets Controllable Option for Restriction of Afterburner. -- @param #CONTROLLABLE self -- @param #boolean RestrictBurner If true, restrict burner. If false or nil, allow (unrestrict) burner. -function CONTROLLABLE:OptionRestrictBurner(RestrictBurner) - self:F2({self.ControllableName}) +function CONTROLLABLE:OptionRestrictBurner( RestrictBurner ) + self:F2( { self.ControllableName } ) local DCSControllable = self:GetDCSObject() @@ -3776,11 +3753,11 @@ function CONTROLLABLE:OptionRestrictBurner(RestrictBurner) -- Issue https://github.com/FlightControl-Master/MOOSE/issues/1216 if RestrictBurner == true then if self:IsAir() then - Controller:setOption(16, true) + Controller:setOption( 16, true ) end else if self:IsAir() then - Controller:setOption(16, false) + Controller:setOption( 16, false ) end end @@ -3794,20 +3771,20 @@ end -- @param #number range Defines the range -- @return #CONTROLLABLE self -- @usage Range can be one of MAX_RANGE = 0, NEZ_RANGE = 1, HALF_WAY_RMAX_NEZ = 2, TARGET_THREAT_EST = 3, RANDOM_RANGE = 4. Defaults to 3. See: https://wiki.hoggitworld.com/view/DCS_option_missileAttack -function CONTROLLABLE:OptionAAAttackRange(range) +function CONTROLLABLE:OptionAAAttackRange( range ) self:F2( { self.ControllableName } ) -- defaults to 3 local range = range or 3 - if range < 0 or range > 4 then + if range < 0 or range > 4 then range = 3 end local DCSControllable = self:GetDCSObject() if DCSControllable then local Controller = self:_GetController() if Controller then - if self:IsAir() then - self:SetOption(AI.Option.Air.val.MISSILE_ATTACK, range) - end + if self:IsAir() then + self:SetOption( AI.Option.Air.val.MISSILE_ATTACK, range ) + end end return self end @@ -3818,22 +3795,22 @@ end -- @param #CONTROLLABLE self -- @param #number EngageRange Engage range limit in percent (a number between 0 and 100). Default 100. -- @return #CONTROLLABLE self -function CONTROLLABLE:OptionEngageRange(EngageRange) +function CONTROLLABLE:OptionEngageRange( EngageRange ) self:F2( { self.ControllableName } ) -- Set default if not specified. - EngageRange=EngageRange or 100 - if EngageRange < 0 or EngageRange > 100 then + EngageRange = EngageRange or 100 + if EngageRange < 0 or EngageRange > 100 then EngageRange = 100 end local DCSControllable = self:GetDCSObject() if DCSControllable then local Controller = self:_GetController() if Controller then - if self:IsGround() then - self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, EngageRange) - end + if self:IsGround() then + self:SetOption( AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, EngageRange ) + end end - return self + return self end return nil end @@ -3846,26 +3823,26 @@ end -- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false -- @param #string formation Formation string as in the mission editor, e.g. "Vee", "Diamond", "Line abreast", etc. Defaults to "Off Road" -- @return #CONTROLLABLE self -function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut, formation) +function CONTROLLABLE:RelocateGroundRandomInRadius( speed, radius, onroad, shortcut, formation ) self:F2( { self.ControllableName } ) - local _coord = self:GetCoordinate() - local _radius = radius or 500 - local _speed = speed or 20 - local _tocoord = _coord:GetRandomCoordinateInRadius(_radius,100) - local _onroad = onroad or true - local _grptsk = {} - local _candoroad = false - local _shortcut = shortcut or false - local _formation = formation or "Off Road" + local _coord = self:GetCoordinate() + local _radius = radius or 500 + local _speed = speed or 20 + local _tocoord = _coord:GetRandomCoordinateInRadius( _radius, 100 ) + local _onroad = onroad or true + local _grptsk = {} + local _candoroad = false + local _shortcut = shortcut or false + local _formation = formation or "Off Road" - -- create a DCS Task an push it on the group - if onroad then - _grptsk, _candoroad = self:TaskGroundOnRoad(_tocoord,_speed,_formation,_shortcut) - self:Route(_grptsk,5) - else - self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,_formation) - end + -- create a DCS Task an push it on the group + if onroad then + _grptsk, _candoroad = self:TaskGroundOnRoad( _tocoord, _speed, _formation, _shortcut ) + self:Route( _grptsk, 5 ) + else + self:TaskRouteToVec2( _tocoord:GetVec2(), _speed, _formation ) + end return self end @@ -3874,7 +3851,7 @@ end -- @param #CONTROLLABLE self -- @param #number Seconds Any positive number: AI will disperse, but only for the specified time before continuing their route. 0: AI will not disperse. -- @return #CONTROLLABLE self -function CONTROLLABLE:OptionDisperseOnAttack(Seconds) +function CONTROLLABLE:OptionDisperseOnAttack( Seconds ) self:F2( { self.ControllableName } ) -- Set default if not specified. local seconds = Seconds or 0 @@ -3882,11 +3859,11 @@ function CONTROLLABLE:OptionDisperseOnAttack(Seconds) if DCSControllable then local Controller = self:_GetController() if Controller then - if self:IsGround() then - self:SetOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK, seconds) - end + if self:IsGround() then + self:SetOption( AI.Option.Ground.id.DISPERSE_ON_ATTACK, seconds ) + end end - return self + return self end return nil end @@ -3901,11 +3878,11 @@ function POSITIONABLE:IsSubmarine() if DCSUnit then local UnitDescriptor = DCSUnit:getDesc() - if UnitDescriptor.attributes["Submarines"] == true then - return true - else - return false - end + if UnitDescriptor.attributes["Submarines"] == true then + return true + else + return false + end end return nil diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 086b7f98e..7194afd61 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -571,7 +571,7 @@ function GROUP:GetSpeedMax() local Units=self:GetUnits() - local speedmax=nil + local speedmax=0 for _,unit in pairs(Units) do local unit=unit --Wrapper.Unit#UNIT @@ -725,6 +725,30 @@ function GROUP:GetUnit( UnitNumber ) end +--- Check if an (air) group is a client or player slot. Information is retrieved from the group template. +-- @param #GROUP self +-- @return #boolean If true, group is associated with a client or player slot. +function GROUP:IsPlayer() + + -- Get group. + -- local group=self:GetGroup() + + -- Units of template group. + local units=self:GetTemplate().units + + -- Get numbers. + for _,unit in pairs(units) do + + -- Check if unit name matach and skill is Client or Player. + if unit.name==self:GetName() and (unit.skill=="Client" or unit.skill=="Player") then + return true + end + + end + + return false +end + --- Returns the DCS Unit with number UnitNumber. -- If the underlying DCS Unit does not exist, the method will return nil. . -- @param #GROUP self @@ -732,11 +756,24 @@ end -- @return DCS#Unit The DCS Unit. function GROUP:GetDCSUnit( UnitNumber ) - local DCSGroup=self:GetDCSObject() + local DCSGroup = self:GetDCSObject() if DCSGroup then - local DCSUnitFound=DCSGroup:getUnit( UnitNumber ) - return DCSUnitFound + + if DCSGroup.getUnit and DCSGroup:getUnit( UnitNumber ) then + return DCSGroup:getUnit( UnitNumber ) + else + + local UnitFound = nil + -- 2.7.1 dead event bug, return the first alive unit instead + local units = DCSGroup:getUnits() or {} + + for _,_unit in pairs(units) do + if _unit and _unit:isExist() then + return _unit + end + end + end end return nil @@ -1083,8 +1120,7 @@ function GROUP:GetCoordinate() end end BASE:E( { "Cannot GetCoordinate", Group = self, Alive = self:IsAlive() } ) - - return nil + end @@ -1116,6 +1152,8 @@ end function GROUP:GetHeading() self:F2(self.GroupName) + self:F2(self.GroupName) + local GroupSize = self:GetSize() local HeadingAccumulator = 0 local n=0 diff --git a/Moose Development/Moose/Wrapper/Identifiable.lua b/Moose Development/Moose/Wrapper/Identifiable.lua index 66bac23c6..7b77c9512 100644 --- a/Moose Development/Moose/Wrapper/Identifiable.lua +++ b/Moose Development/Moose/Wrapper/Identifiable.lua @@ -171,7 +171,7 @@ function IDENTIFIABLE:GetCoalitionName() if DCSIdentifiable then - -- Get coaliton ID. + -- Get coalition ID. local IdentifiableCoalition = DCSIdentifiable:getCoalition() self:T3( IdentifiableCoalition ) diff --git a/Moose Development/Moose/Wrapper/Marker.lua b/Moose Development/Moose/Wrapper/Marker.lua index 462fde061..21936ae46 100644 --- a/Moose Development/Moose/Wrapper/Marker.lua +++ b/Moose Development/Moose/Wrapper/Marker.lua @@ -15,7 +15,6 @@ -- @module Wrapper.Marker -- @image MOOSE_Core.JPG - --- Marker class. -- @type MARKER -- @field #string ClassName Name of the class. @@ -24,7 +23,7 @@ -- @field #number mid Marker ID. -- @field Core.Point#COORDINATE coordinate Coordinate of the mark. -- @field #string text Text displayed in the mark panel. --- @field #string message Message dispayed when the mark is added. +-- @field #string message Message displayed when the mark is added. -- @field #boolean readonly Marker is read-only. -- @field #number coalition Coalition to which the marker is displayed. -- @extends Core.Fsm#FSM @@ -42,29 +41,29 @@ -- # Create a Marker -- -- -- Create a MARKER object at Batumi with a trivial text. --- local Coordinate=AIRBASE:FindByName("Batumi"):GetCoordinate() --- mymarker=MARKER:New(Coordinate, "I am Batumi Airfield") +-- local Coordinate = AIRBASE:FindByName( "Batumi" ):GetCoordinate() +-- mymarker = MARKER:New( Coordinate, "I am Batumi Airfield" ) -- --- Now this does **not** show the marker yet. We still need to specifiy to whom it is shown. There are several options, i.e. --- show the marker to everyone, to a speficic coaliton only, or only to a specific group. +-- Now this does **not** show the marker yet. We still need to specify to whom it is shown. There are several options, i.e. +-- show the marker to everyone, to a specific coalition only, or only to a specific group. -- -- ## For Everyone -- -- If the marker should be visible to everyone, you can use the :ToAll() function. -- --- mymarker=MARKER:New(Coordinate, "I am Batumi Airfield"):ToAll() +-- mymarker = MARKER:New( Coordinate, "I am Batumi Airfield" ):ToAll() -- --- ## For a Coaliton +-- ## For a Coalition -- -- If the maker should be visible to a specific coalition, you can use the :ToCoalition() function. -- --- mymarker=MARKER:New(Coordinate, "I am Batumi Airfield"):ToCoaliton(coaliton.side.BLUE) +-- mymarker = MARKER:New( Coordinate , "I am Batumi Airfield" ):ToCoalition( coalition.side.BLUE ) -- --- ### To Blue Coaliton +-- ### To Blue Coalition -- -- ### To Red Coalition -- --- This would show the marker only to the Blue coaliton. +-- This would show the marker only to the Blue coalition. -- -- ## For a Group -- @@ -76,28 +75,28 @@ -- -- The marker text and coordinate can be updated easily as shown below. -- --- However, note that **updateing involves to remove and recreate the marker if either text or its coordinate is changed**. +-- However, note that **updating involves to remove and recreate the marker if either text or its coordinate is changed**. -- *This is a DCS scripting engine limitation.* -- -- ## Update Text -- --- If you created a marker "mymarker" as shown above, you can update the dispayed test by +-- If you created a marker "mymarker" as shown above, you can update the displayed test by -- --- mymarker:UpdateText("I am the new text at Batumi") +-- mymarker:UpdateText( "I am the new text at Batumi" ) -- -- The update can also be delayed by, e.g. 90 seconds, using -- --- mymarker:UpdateText("I am the new text at Batumi", 90) +-- mymarker:UpdateText( "I am the new text at Batumi", 90 ) -- -- ## Update Coordinate -- -- If you created a marker "mymarker" as shown above, you can update its coordinate on the F10 map by -- --- mymarker:UpdateCoordinate(NewCoordinate) +-- mymarker:UpdateCoordinate( NewCoordinate ) -- -- The update can also be delayed by, e.g. 60 seconds, using -- --- mymarker:UpdateCoordinate(NewCoordinate, 60) +-- mymarker:UpdateCoordinate( NewCoordinate , 60 ) -- -- # Retrieve Data -- @@ -105,18 +104,18 @@ -- -- ## Text -- --- local text=mymarker:GetText() --- env.info("Marker Text = " .. text) +-- local text =mymarker:GetText() +-- env.info( "Marker Text = " .. text ) -- -- ## Coordinate -- --- local Coordinate=mymarker:GetCoordinate() --- env.info("Marker Coordinate LL DSM = " .. Coordinate:ToStringLLDMS()) +-- local Coordinate = mymarker:GetCoordinate() +-- env.info( "Marker Coordinate LL DSM = " .. Coordinate:ToStringLLDMS() ) -- -- -- # FSM Events -- --- Moose creates addditonal events, so called FSM event, when markers are added, changed, removed, and text or the coordianteis updated. +-- Moose creates additional events, so called FSM event, when markers are added, changed, removed, and text or the coordinate is updated. -- -- These events can be captured and used for processing via OnAfter functions as shown below. -- @@ -133,22 +132,21 @@ -- -- # Examples -- --- -- @field #MARKER MARKER = { - ClassName = "MARKER", - Debug = false, - lid = nil, - mid = nil, - coordinate = nil, - text = nil, - message = nil, - readonly = nil, - coalition = nil, + ClassName = "MARKER", + Debug = false, + lid = nil, + mid = nil, + coordinate = nil, + text = nil, + message = nil, + readonly = nil, + coalition = nil, } --- Marker ID. Running number. -_MARKERID=0 +_MARKERID = 0 --- Marker class version. -- @field #string version @@ -172,38 +170,40 @@ MARKER.version="0.1.1" -- @param Core.Point#COORDINATE Coordinate Coordinate where to place the marker. -- @param #string Text Text displayed on the mark panel. -- @return #MARKER self -function MARKER:New(Coordinate, Text) +function MARKER:New( Coordinate, Text ) -- Inherit everything from FSM class. + local self = BASE:Inherit( self, FSM:New() ) -- #MARKER + local self=BASE:Inherit(self, FSM:New()) -- #MARKER self.coordinate=UTILS.DeepCopy(Coordinate) - self.text=Text + self.text = Text -- Defaults - self.readonly=false - self.message="" + self.readonly = false + self.message = "" -- New marker ID. This is not the one of the actual marker. - _MARKERID=_MARKERID+1 + _MARKERID = _MARKERID + 1 - self.myid=_MARKERID + self.myid = _MARKERID -- Log ID. - self.lid=string.format("Marker #%d | ", self.myid) + self.lid = string.format( "Marker #%d | ", self.myid ) -- Start State. - self:SetStartState("Invisible") + self:SetStartState( "Invisible" ) -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("Invisible", "Added", "Visible") -- Marker was added. - self:AddTransition("Visible", "Removed", "Invisible") -- Marker was removed. - self:AddTransition("*", "Changed", "*") -- Marker was changed. + self:AddTransition( "Invisible", "Added", "Visible" ) -- Marker was added. + self:AddTransition( "Visible", "Removed", "Invisible" ) -- Marker was removed. + self:AddTransition( "*", "Changed", "*" ) -- Marker was changed. - self:AddTransition("*", "TextUpdate", "*") -- Text updated. - self:AddTransition("*", "CoordUpdate", "*") -- Coordinates updated. + self:AddTransition( "*", "TextUpdate", "*" ) -- Text updated. + self:AddTransition( "*", "CoordUpdate", "*" ) -- Coordinates updated. --- Triggers the FSM event "Added". -- @function [parent=#MARKER] Added @@ -223,7 +223,6 @@ function MARKER:New(Coordinate, Text) -- @param #string To To state. -- @param Core.Event#EVENTDATA EventData Event data table. - --- Triggers the FSM event "Removed". -- @function [parent=#MARKER] Removed -- @param #MARKER self @@ -242,7 +241,6 @@ function MARKER:New(Coordinate, Text) -- @param #string To To state. -- @param Core.Event#EVENTDATA EventData Event data table. - --- Triggers the FSM event "Changed". -- @function [parent=#MARKER] Changed -- @param #MARKER self @@ -261,7 +259,6 @@ function MARKER:New(Coordinate, Text) -- @param #string To To state. -- @param Core.Event#EVENTDATA EventData Event data table. - --- Triggers the FSM event "TextUpdate". -- @function [parent=#MARKER] TextUpdate -- @param #MARKER self @@ -280,7 +277,6 @@ function MARKER:New(Coordinate, Text) -- @param #string To To state. -- @param #string Text The new text. - --- Triggers the FSM event "CoordUpdate". -- @function [parent=#MARKER] CoordUpdate -- @param #MARKER self @@ -299,11 +295,10 @@ function MARKER:New(Coordinate, Text) -- @param #string To To state. -- @param Core.Point#COORDINATE Coordinate The updated Coordinate. - -- Handle events. - self:HandleEvent(EVENTS.MarkAdded) - self:HandleEvent(EVENTS.MarkRemoved) - self:HandleEvent(EVENTS.MarkChange) + self:HandleEvent( EVENTS.MarkAdded ) + self:HandleEvent( EVENTS.MarkRemoved ) + self:HandleEvent( EVENTS.MarkChange ) return self end @@ -317,7 +312,7 @@ end -- @return #MARKER self function MARKER:ReadOnly() - self.readonly=true + self.readonly = true return self end @@ -336,9 +331,9 @@ end -- @param #MARKER self -- @param #string Text Message displayed when the marker is added. -- @return #MARKER self -function MARKER:Message(Text) +function MARKER:Message( Text ) - self.message=Text or "" + self.message = Text or "" return self end @@ -347,28 +342,28 @@ end -- @param #MARKER self -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:ToAll(Delay) +function MARKER:ToAll( Delay ) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, MARKER.ToAll, self) + if Delay and Delay > 0 then + self:ScheduleOnce( Delay, MARKER.ToAll, self ) else - self.toall=true - self.tocoaliton=nil - self.coalition=nil - self.togroup=nil - self.groupname=nil - self.groupid=nil + self.toall = true + self.tocoalition = nil + self.coalition = nil + self.togroup = nil + self.groupname = nil + self.groupid = nil -- First remove an existing mark. if self.shown then self:Remove() end - self.mid=UTILS.GetMarkID() + self.mid = UTILS.GetMarkID() -- Call DCS function. - trigger.action.markToAll(self.mid, self.text, self.coordinate:GetVec3(), self.readonly, self.message) + trigger.action.markToAll( self.mid, self.text, self.coordinate:GetVec3(), self.readonly, self.message ) end @@ -377,32 +372,32 @@ end --- Place marker visible for a specific coalition only. -- @param #MARKER self --- @param #number Coalition Coalition 1=Red, 2=Blue, 0=Neutral. See `coaliton.side.RED`. +-- @param #number Coalition Coalition 1=Red, 2=Blue, 0=Neutral. See `coalition.side.RED`. -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:ToCoalition(Coalition, Delay) +function MARKER:ToCoalition( Coalition, Delay ) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, MARKER.ToCoalition, self, Coalition) + if Delay and Delay > 0 then + self:ScheduleOnce( Delay, MARKER.ToCoalition, self, Coalition ) else - self.coalition=Coalition + self.coalition = Coalition - self.tocoaliton=true - self.toall=false - self.togroup=false - self.groupname=nil - self.groupid=nil + self.tocoalition = true + self.toall = false + self.togroup = false + self.groupname = nil + self.groupid = nil -- First remove an existing mark. if self.shown then self:Remove() end - self.mid=UTILS.GetMarkID() + self.mid = UTILS.GetMarkID() -- Call DCS function. - trigger.action.markToCoalition(self.mid, self.text, self.coordinate:GetVec3(), self.coalition, self.readonly, self.message) + trigger.action.markToCoalition( self.mid, self.text, self.coordinate:GetVec3(), self.coalition, self.readonly, self.message ) end @@ -413,8 +408,8 @@ end -- @param #MARKER self -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:ToBlue(Delay) - self:ToCoalition(coalition.side.BLUE, Delay) +function MARKER:ToBlue( Delay ) + self:ToCoalition( coalition.side.BLUE, Delay ) return self end @@ -422,8 +417,8 @@ end -- @param #MARKER self -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:ToRed(Delay) - self:ToCoalition(coalition.side.RED, Delay) +function MARKER:ToRed( Delay ) + self:ToCoalition( coalition.side.RED, Delay ) return self end @@ -431,51 +426,50 @@ end -- @param #MARKER self -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:ToNeutral(Delay) - self:ToCoalition(coalition.side.NEUTRAL, Delay) +function MARKER:ToNeutral( Delay ) + self:ToCoalition( coalition.side.NEUTRAL, Delay ) return self end - --- Place marker visible for a specific group only. -- @param #MARKER self -- @param Wrapper.Group#GROUP Group The group to which the marker is displayed. -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:ToGroup(Group, Delay) +function MARKER:ToGroup( Group, Delay ) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, MARKER.ToGroup, self, Group) + if Delay and Delay > 0 then + self:ScheduleOnce( Delay, MARKER.ToGroup, self, Group ) else -- Check if group exists. - if Group and Group:IsAlive()~=nil then + if Group and Group:IsAlive() ~= nil then - self.groupid=Group:GetID() + self.groupid = Group:GetID() if self.groupid then - self.groupname=Group:GetName() + self.groupname = Group:GetName() - self.togroup=true - self.tocoaliton=nil - self.coalition=nil - self.toall=nil + self.togroup = true + self.tocoalition = nil + self.coalition = nil + self.toall = nil -- First remove an existing mark. if self.shown then self:Remove() end - self.mid=UTILS.GetMarkID() + self.mid = UTILS.GetMarkID() -- Call DCS function. - trigger.action.markToGroup(self.mid, self.text, self.coordinate:GetVec3(), self.groupid, self.readonly, self.message) + trigger.action.markToGroup( self.mid, self.text, self.coordinate:GetVec3(), self.groupid, self.readonly, self.message ) end else - --TODO: Warning! + -- TODO: Warning! end end @@ -488,17 +482,17 @@ end -- @param #string Text Updated text. -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:UpdateText(Text, Delay) +function MARKER:UpdateText( Text, Delay ) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, MARKER.UpdateText, self, Text) + if Delay and Delay > 0 then + self:ScheduleOnce( Delay, MARKER.UpdateText, self, Text ) else - self.text=tostring(Text) + self.text = tostring( Text ) self:Refresh() - self:TextUpdate(tostring(Text)) + self:TextUpdate( tostring( Text ) ) end @@ -510,17 +504,17 @@ end -- @param Core.Point#COORDINATE Coordinate The new coordinate. -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:UpdateCoordinate(Coordinate, Delay) +function MARKER:UpdateCoordinate( Coordinate, Delay ) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, MARKER.UpdateCoordinate, self, Coordinate) + if Delay and Delay > 0 then + self:ScheduleOnce( Delay, MARKER.UpdateCoordinate, self, Coordinate ) else - self.coordinate=Coordinate + self.coordinate = Coordinate self:Refresh() - self:CoordUpdate(Coordinate) + self:CoordUpdate( Coordinate ) end @@ -531,28 +525,28 @@ end -- @param #MARKER self -- @param #number Delay (Optional) Delay in seconds, before the marker is created. -- @return #MARKER self -function MARKER:Refresh(Delay) +function MARKER:Refresh( Delay ) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, MARKER.Refresh, self) + if Delay and Delay > 0 then + self:ScheduleOnce( Delay, MARKER.Refresh, self ) else if self.toall then self:ToAll() - elseif self.tocoaliton then + elseif self.tocoalition then - self:ToCoalition(self.coalition) + self:ToCoalition( self.coalition ) elseif self.togroup then - local group=GROUP:FindByName(self.groupname) + local group = GROUP:FindByName( self.groupname ) - self:ToGroup(group) + self:ToGroup( group ) else - self:E(self.lid.."ERROR: unknown To in :Refresh()!") + self:E( self.lid .. "ERROR: unknown To in :Refresh()!" ) end end @@ -564,16 +558,16 @@ end -- @param #MARKER self -- @param #number Delay (Optional) Delay in seconds, before the marker is removed. -- @return #MARKER self -function MARKER:Remove(Delay) +function MARKER:Remove( Delay ) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, MARKER.Remove, self) + if Delay and Delay > 0 then + self:ScheduleOnce( Delay, MARKER.Remove, self ) else if self.shown then -- Call DCS function. - trigger.action.removeMark(self.mid) + trigger.action.removeMark( self.mid ) end @@ -600,24 +594,23 @@ end -- @param #MARKER self -- @param #string Text Marker text. Default is an empty string "". -- @return #MARKER self -function MARKER:SetText(Text) - self.text=Text and tostring(Text) or "" +function MARKER:SetText( Text ) + self.text = Text and tostring( Text ) or "" return self end - --- Check if marker is currently visible on the F10 map. -- @param #MARKER self -- @return #boolean True if the marker is currently visible. function MARKER:IsVisible() - return self:Is("Visible") + return self:Is( "Visible" ) end --- Check if marker is currently invisible on the F10 map. -- @param #MARKER self -- @return function MARKER:IsInvisible() - return self:Is("Invisible") + return self:Is( "Invisible" ) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -627,19 +620,19 @@ end --- Event function when a MARKER is added. -- @param #MARKER self -- @param Core.Event#EVENTDATA EventData -function MARKER:OnEventMarkAdded(EventData) +function MARKER:OnEventMarkAdded( EventData ) if EventData and EventData.MarkID then - local MarkID=EventData.MarkID + local MarkID = EventData.MarkID - self:T3(self.lid..string.format("Captured event MarkAdded for Mark ID=%s", tostring(MarkID))) + self:T3( self.lid .. string.format( "Captured event MarkAdded for Mark ID=%s", tostring( MarkID ) ) ) - if MarkID==self.mid then + if MarkID == self.mid then - self.shown=true + self.shown = true - self:Added(EventData) + self:Added( EventData ) end @@ -650,19 +643,21 @@ end --- Event function when a MARKER is removed. -- @param #MARKER self -- @param Core.Event#EVENTDATA EventData -function MARKER:OnEventMarkRemoved(EventData) +function MARKER:OnEventMarkRemoved( EventData ) if EventData and EventData.MarkID then + local MarkID = EventData.MarkID + local MarkID=EventData.MarkID self:T3(self.lid..string.format("Captured event MarkRemoved for Mark ID=%s", tostring(MarkID))) - if MarkID==self.mid then + if MarkID == self.mid then - self.shown=false + self.shown = false - self:Removed(EventData) + self:Removed( EventData ) end @@ -673,10 +668,16 @@ end --- Event function when a MARKER changed. -- @param #MARKER self -- @param Core.Event#EVENTDATA EventData -function MARKER:OnEventMarkChange(EventData) +function MARKER:OnEventMarkChange( EventData ) if EventData and EventData.MarkID then + local MarkID = EventData.MarkID + + self:T3( self.lid .. string.format( "Captured event MarkChange for Mark ID=%s", tostring( MarkID ) ) ) + + if MarkID == self.mid then + local MarkID=EventData.MarkID self:T3(self.lid..string.format("Captured event MarkChange for Mark ID=%s", tostring(MarkID))) @@ -692,7 +693,7 @@ function MARKER:OnEventMarkChange(EventData) end end - +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Event Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -703,17 +704,17 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Core.Event#EVENTDATA EventData Event data table. -function MARKER:onafterAdded(From, Event, To, EventData) +function MARKER:onafterAdded( From, Event, To, EventData ) -- Debug info. - local text=string.format("Captured event MarkAdded for myself:\n") - text=text..string.format("Marker ID = %s\n", tostring(EventData.MarkID)) - text=text..string.format("Coalition = %s\n", tostring(EventData.MarkCoalition)) - text=text..string.format("Group ID = %s\n", tostring(EventData.MarkGroupID)) - text=text..string.format("Initiator = %s\n", EventData.IniUnit and EventData.IniUnit:GetName() or "Nobody") - text=text..string.format("Coordinate = %s\n", EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS() or "Nowhere") - text=text..string.format("Text: \n%s", tostring(EventData.MarkText)) - self:T2(self.lid..text) + local text = string.format( "Captured event MarkAdded for myself:\n" ) + text = text .. string.format( "Marker ID = %s\n", tostring( EventData.MarkID ) ) + text = text .. string.format( "Coalition = %s\n", tostring( EventData.MarkCoalition ) ) + text = text .. string.format( "Group ID = %s\n", tostring( EventData.MarkGroupID ) ) + text = text .. string.format( "Initiator = %s\n", EventData.IniUnit and EventData.IniUnit:GetName() or "Nobody" ) + text = text .. string.format( "Coordinate = %s\n", EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS() or "Nowhere" ) + text = text .. string.format( "Text: \n%s", tostring( EventData.MarkText ) ) + self:T2( self.lid .. text ) end @@ -723,17 +724,17 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Core.Event#EVENTDATA EventData Event data table. -function MARKER:onafterRemoved(From, Event, To, EventData) +function MARKER:onafterRemoved( From, Event, To, EventData ) -- Debug info. - local text=string.format("Captured event MarkRemoved for myself:\n") - text=text..string.format("Marker ID = %s\n", tostring(EventData.MarkID)) - text=text..string.format("Coalition = %s\n", tostring(EventData.MarkCoalition)) - text=text..string.format("Group ID = %s\n", tostring(EventData.MarkGroupID)) - text=text..string.format("Initiator = %s\n", EventData.IniUnit and EventData.IniUnit:GetName() or "Nobody") - text=text..string.format("Coordinate = %s\n", EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS() or "Nowhere") - text=text..string.format("Text: \n%s", tostring(EventData.MarkText)) - self:T2(self.lid..text) + local text = string.format( "Captured event MarkRemoved for myself:\n" ) + text = text .. string.format( "Marker ID = %s\n", tostring( EventData.MarkID ) ) + text = text .. string.format( "Coalition = %s\n", tostring( EventData.MarkCoalition ) ) + text = text .. string.format( "Group ID = %s\n", tostring( EventData.MarkGroupID ) ) + text = text .. string.format( "Initiator = %s\n", EventData.IniUnit and EventData.IniUnit:GetName() or "Nobody" ) + text = text .. string.format( "Coordinate = %s\n", EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS() or "Nowhere" ) + text = text .. string.format( "Text: \n%s", tostring( EventData.MarkText ) ) + self:T2( self.lid .. text ) end @@ -743,17 +744,17 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Core.Event#EVENTDATA EventData Event data table. -function MARKER:onafterChanged(From, Event, To, EventData) +function MARKER:onafterChanged( From, Event, To, EventData ) -- Debug info. - local text=string.format("Captured event MarkChange for myself:\n") - text=text..string.format("Marker ID = %s\n", tostring(EventData.MarkID)) - text=text..string.format("Coalition = %s\n", tostring(EventData.MarkCoalition)) - text=text..string.format("Group ID = %s\n", tostring(EventData.MarkGroupID)) - text=text..string.format("Initiator = %s\n", EventData.IniUnit and EventData.IniUnit:GetName() or "Nobody") - text=text..string.format("Coordinate = %s\n", EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS() or "Nowhere") - text=text..string.format("Text: \n%s", tostring(EventData.MarkText)) - self:T2(self.lid..text) + local text = string.format( "Captured event MarkChange for myself:\n" ) + text = text .. string.format( "Marker ID = %s\n", tostring( EventData.MarkID ) ) + text = text .. string.format( "Coalition = %s\n", tostring( EventData.MarkCoalition ) ) + text = text .. string.format( "Group ID = %s\n", tostring( EventData.MarkGroupID ) ) + text = text .. string.format( "Initiator = %s\n", EventData.IniUnit and EventData.IniUnit:GetName() or "Nobody" ) + text = text .. string.format( "Coordinate = %s\n", EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS() or "Nowhere" ) + text = text .. string.format( "Text: \n%s", tostring( EventData.MarkText ) ) + self:T2( self.lid .. text ) end @@ -763,9 +764,9 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #string Text The updated text, displayed in the mark panel. -function MARKER:onafterTextUpdate(From, Event, To, Text) +function MARKER:onafterTextUpdate( From, Event, To, Text ) - self:T(self.lid..string.format("New Marker Text:\n%s", Text)) + self:T( self.lid .. string.format( "New Marker Text:\n%s", Text ) ) end @@ -775,12 +776,8 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Core.Point#COORDINATE Coordinate The updated coordinates. -function MARKER:onafterCoordUpdate(From, Event, To, Coordinate) +function MARKER:onafterCoordUpdate( From, Event, To, Coordinate ) - self:T(self.lid..string.format("New Marker Coordinate in LL DMS: %s", Coordinate:ToStringLLDMS())) + self:T( self.lid .. string.format( "New Marker Coordinate in LL DMS: %s", Coordinate:ToStringLLDMS() ) ) end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 01628d1b0..87e199c9a 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -57,7 +57,6 @@ POSITIONABLE.__ = {} --- @field #POSITIONABLE.__.Cargo POSITIONABLE.__.Cargo = {} - --- A DCSPositionable -- @type DCSPositionable -- @field id_ The ID of the controllable in DCS @@ -75,16 +74,19 @@ end --- Destroys the POSITIONABLE. -- @param #POSITIONABLE self --- @param #boolean GenerateEvent (Optional) true if you want to generate a crash or dead event for the unit. +-- @param #boolean GenerateEvent (Optional) If true, generates a crash or dead event for the unit. If false, no event generated. If nil, a remove event is generated. -- @return #nil The DCS Unit is not existing or alive. -- @usage --- -- Air unit example: destroy the Helicopter and generate a S_EVENT_CRASH for each unit in the Helicopter group. +-- +-- Air unit example: destroy the Helicopter and generate a S_EVENT_CRASH for each unit in the Helicopter group. -- Helicopter = UNIT:FindByName( "Helicopter" ) -- Helicopter:Destroy( true ) +-- -- @usage -- -- Ground unit example: destroy the Tanks and generate a S_EVENT_DEAD for each unit in the Tanks group. -- Tanks = UNIT:FindByName( "Tanks" ) -- Tanks:Destroy( true ) +-- -- @usage -- -- Ship unit example: destroy the Ship silently. -- Ship = STATIC:FindByName( "Ship" ) @@ -147,7 +149,7 @@ function POSITIONABLE:GetPosition() return PositionablePosition end - BASE:E( { "Cannot GetPositionVec3", Positionable = self, Alive = self:IsAlive() } ) + BASE:E( { "Cannot GetPosition", Positionable = self, Alive = self:IsAlive() } ) return nil end @@ -157,8 +159,9 @@ end -- @return DCS#Vec3 X orientation, i.e. parallel to the direction of movement. -- @return DCS#Vec3 Y orientation, i.e. vertical. -- @return DCS#Vec3 Z orientation, i.e. perpendicular to the direction of movement. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetOrientation() - local position=self:GetPosition() + local position = self:GetPosition() if position then return position.x, position.y, position.z else @@ -170,8 +173,9 @@ end --- Returns a {@DCS#Vec3} table of the objects current X orientation in 3D space, i.e. along the direction of movement. -- @param Wrapper.Positionable#POSITIONABLE self -- @return DCS#Vec3 X orientation, i.e. parallel to the direction of movement. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetOrientationX() - local position=self:GetPosition() + local position = self:GetPosition() if position then return position.x else @@ -183,8 +187,9 @@ end --- Returns a {@DCS#Vec3} table of the objects current Y orientation in 3D space, i.e. vertical orientation. -- @param Wrapper.Positionable#POSITIONABLE self -- @return DCS#Vec3 Y orientation, i.e. vertical. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetOrientationY() - local position=self:GetPosition() + local position = self:GetPosition() if position then return position.y else @@ -196,8 +201,9 @@ end --- Returns a {@DCS#Vec3} table of the objects current Z orientation in 3D space, i.e. perpendicular to direction of movement. -- @param Wrapper.Positionable#POSITIONABLE self -- @return DCS#Vec3 Z orientation, i.e. perpendicular to movement. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetOrientationZ() - local position=self:GetPosition() + local position = self:GetPosition() if position then return position.z else @@ -228,19 +234,20 @@ end --- Returns the @{DCS#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self --- @return DCS#Vec3 The 3D point vector of the POSITIONABLE or `nil` if it is not existing or alive. +-- @return DCS#Vec3 The 3D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetVec3() local DCSPositionable = self:GetDCSObject() if DCSPositionable then - local vec3=DCSPositionable:getPoint() + local vec3 = DCSPositionable:getPoint() if vec3 then return vec3 else - self:E("ERROR: Cannot get vec3!") + self:E( "ERROR: Cannot get vec3!" ) end end @@ -251,16 +258,17 @@ end --- Returns the @{DCS#Vec2} vector indicating the point in 2D of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self --- @return DCS#Vec2 The 2D point vector of the POSITIONABLE or #nil if it is not existing or alive. +-- @return DCS#Vec2 The 2D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetVec2() local DCSPositionable = self:GetDCSObject() if DCSPositionable then - local Vec3=DCSPositionable:getPoint() --DCS#Vec3 + local Vec3 = DCSPositionable:getPoint() -- DCS#Vec3 - return {x=Vec3.x, y=Vec3.z} + return { x = Vec3.x, y = Vec3.z } end self:E( { "Cannot GetVec2", Positionable = self, Alive = self:IsAlive() } ) @@ -282,7 +290,7 @@ function POSITIONABLE:GetPointVec2() local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 ) - --self:F( PositionablePointVec2 ) + -- self:F( PositionablePointVec2 ) return PositionablePointVec2 end @@ -307,14 +315,14 @@ function POSITIONABLE:GetPointVec3() if false and self.pointvec3 then -- Update vector. - self.pointvec3.x=PositionableVec3.x - self.pointvec3.y=PositionableVec3.y - self.pointvec3.z=PositionableVec3.z + self.pointvec3.x = PositionableVec3.x + self.pointvec3.y = PositionableVec3.y + self.pointvec3.z = PositionableVec3.z else -- Create a new POINT_VEC3 object. - self.pointvec3=POINT_VEC3:NewFromVec3(PositionableVec3) + self.pointvec3 = POINT_VEC3:NewFromVec3( PositionableVec3 ) end @@ -326,9 +334,13 @@ function POSITIONABLE:GetPointVec3() return nil end ---- Returns a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. +--- Returns a reference to a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. +-- This function works similar to POSITIONABLE.GetCoordinate(), however, this function caches, updates and re-uses the same COORDINATE object stored +-- within the POSITIONABLE. This has higher performance, but comes with all considerations associated with the possible referencing to the same COORDINATE object. +-- This should only be used when performance is critical and there is sufficient awareness of the possible pitfalls. However, in most instances, GetCoordinate() is +-- preferred as it will return a fresh new COORDINATE and thus avoid potentially unexpected issues. -- @param Wrapper.Positionable#POSITIONABLE self --- @return Core.Point#COORDINATE The COORDINATE of the POSITIONABLE. +-- @return Core.Point#COORDINATE A reference to the COORDINATE object of the POSITIONABLE. function POSITIONABLE:GetCoord() -- Get DCS object. @@ -337,20 +349,14 @@ function POSITIONABLE:GetCoord() if DCSPositionable then -- Get the current position. - local Vec3 = self:GetVec3() + local PositionableVec3 = self:GetVec3() if self.coordinate then - - -- Update vector. - self.coordinate.x=Vec3.x - self.coordinate.y=Vec3.y - self.coordinate.z=Vec3.z - + -- Update COORDINATE from 3D vector. + self.coordinate:UpdateFromVec3( PositionableVec3 ) else - -- New COORDINATE. - self.coordinate=COORDINATE:NewFromVec3(Vec3) - + self.coordinate = COORDINATE:NewFromVec3( PositionableVec3 ) end return self.coordinate @@ -362,9 +368,9 @@ function POSITIONABLE:GetCoord() return nil end ---- Returns a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. +--- Returns a new COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self --- @return Core.Point#COORDINATE The COORDINATE of the POSITIONABLE. +-- @return Core.Point#COORDINATE A new COORDINATE object of the POSITIONABLE. function POSITIONABLE:GetCoordinate() -- Get DCS object. @@ -394,36 +400,36 @@ end -- @param #number y Offset "above" the unit in meters. Default 0 m. -- @param #number z Offset in the direction "the wing" of the unit is pointing in meters. z>0 starboard, z<0 port. Default 0 m. -- @return Core.Point#COORDINATE The COORDINATE of the offset with respect to the orientation of the POSITIONABLE. -function POSITIONABLE:GetOffsetCoordinate(x,y,z) +function POSITIONABLE:GetOffsetCoordinate( x, y, z ) -- Default if nil. - x=x or 0 - y=y or 0 - z=z or 0 + x = x or 0 + y = y or 0 + z = z or 0 -- Vectors making up the coordinate system. - local X=self:GetOrientationX() - local Y=self:GetOrientationY() - local Z=self:GetOrientationZ() + local X = self:GetOrientationX() + local Y = self:GetOrientationY() + local Z = self:GetOrientationZ() -- Offset vector: x meters ahead, z meters starboard, y meters above. - local A={x=x, y=y, z=z} + local A = { x = x, y = y, z = z } -- Scale components of orthonormal coordinate vectors. - local x={x=X.x*A.x, y=X.y*A.x, z=X.z*A.x} - local y={x=Y.x*A.y, y=Y.y*A.y, z=Y.z*A.y} - local z={x=Z.x*A.z, y=Z.y*A.z, z=Z.z*A.z} + local x = { x = X.x * A.x, y = X.y * A.x, z = X.z * A.x } + local y = { x = Y.x * A.y, y = Y.y * A.y, z = Y.z * A.y } + local z = { x = Z.x * A.z, y = Z.y * A.z, z = Z.z * A.z } -- Add up vectors in the unit coordinate system ==> this gives the offset vector relative the the origin of the map. - local a={x=x.x+y.x+z.x, y=x.y+y.y+z.y, z=x.z+y.z+z.z} + local a = { x = x.x + y.x + z.x, y = x.y + y.y + z.y, z = x.z + y.z + z.z } -- Vector from the origin of the map to the unit. - local u=self:GetVec3() + local u = self:GetVec3() -- Translate offset vector from map origin to the unit: v=u+a. - local v={x=a.x+u.x, y=a.y+u.y, z=a.z+u.z} + local v = { x = a.x + u.x, y = a.y + u.y, z = a.z + u.z } - local coord=COORDINATE:NewFromVec3(v) + local coord = COORDINATE:NewFromVec3( v ) -- Return the offset coordinate. return coord @@ -446,7 +452,7 @@ function POSITIONABLE:GetRandomVec3( Radius ) if Radius then local PositionableRandomVec3 = {} - local angle = math.random() * math.pi*2; + 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; @@ -454,7 +460,7 @@ function POSITIONABLE:GetRandomVec3( Radius ) self:T3( PositionableRandomVec3 ) return PositionableRandomVec3 else - self:F("Radius is nil, returning the PointVec3 of the POSITIONABLE", PositionablePointVec3) + self:F( "Radius is nil, returning the PointVec3 of the POSITIONABLE", PositionablePointVec3 ) return PositionablePointVec3 end end @@ -464,18 +470,17 @@ function POSITIONABLE:GetRandomVec3( Radius ) return nil end - --- Get the bounding box of the underlying POSITIONABLE DCS Object. -- @param #POSITIONABLE self -- @return DCS#Box3 The bounding box of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetBoundingBox() --R2.1 +function POSITIONABLE:GetBoundingBox() self:F2() local DCSPositionable = self:GetDCSObject() if DCSPositionable then - local PositionableDesc = DCSPositionable:getDesc() --DCS#Desc + local PositionableDesc = DCSPositionable:getDesc() -- DCS#Desc if PositionableDesc then local PositionableBox = PositionableDesc.box return PositionableBox @@ -487,7 +492,6 @@ function POSITIONABLE:GetBoundingBox() --R2.1 return nil end - --- Get the object size. -- @param #POSITIONABLE self -- @return DCS#Distance Max size of object in x, z or 0 if bounding box could not be obtained. @@ -497,28 +501,29 @@ end function POSITIONABLE:GetObjectSize() -- Get bounding box. - local box=self:GetBoundingBox() + local box = self:GetBoundingBox() if box then - local x=box.max.x+math.abs(box.min.x) --length - local y=box.max.y+math.abs(box.min.y) --height - local z=box.max.z+math.abs(box.min.z) --width - return math.max(x,z), x , y, z + local x = box.max.x + math.abs( box.min.x ) -- length + local y = box.max.y + math.abs( box.min.y ) -- height + local z = box.max.z + math.abs( box.min.z ) -- width + return math.max( x, z ), x, y, z end - return 0,0,0,0 + return 0, 0, 0, 0 end --- Get the bounding radius of the underlying POSITIONABLE DCS Object. -- @param #POSITIONABLE self --- @param #number mindist (Optional) If bounding box is smaller than this value, mindist is returned. --- @return DCS#Distance The bounding radius of the POSITIONABLE or #nil if the POSITIONABLE is not existing or alive. -function POSITIONABLE:GetBoundingRadius(mindist) +-- @param #number MinDist (Optional) If bounding box is smaller than this value, MinDist is returned. +-- @return DCS#Distance The bounding radius of the POSITIONABLE +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetBoundingRadius( MinDist ) self:F2() local Box = self:GetBoundingBox() - local boxmin=mindist or 0 + local boxmin = MinDist or 0 if Box then local X = Box.max.x - Box.min.x local Z = Box.max.z - Box.min.z @@ -542,7 +547,7 @@ function POSITIONABLE:GetAltitude() local DCSPositionable = self:GetDCSObject() if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPoint() --DCS#Vec3 + local PositionablePointVec3 = DCSPositionable:getPoint() -- DCS#Vec3 return PositionablePointVec3.y end @@ -575,7 +580,6 @@ function POSITIONABLE:IsAboveRunway() return nil end - function POSITIONABLE:GetSize() local DCSObject = self:GetDCSObject() @@ -587,11 +591,10 @@ function POSITIONABLE:GetSize() end end - - --- Returns the POSITIONABLE heading in degrees. -- @param Wrapper.Positionable#POSITIONABLE self --- @return #number The POSITIONABLE heading in degrees or `nil` if not existing or alive. +-- @return #number The POSITIONABLE heading in degrees. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetHeading() local DCSPositionable = self:GetDCSObject() @@ -613,8 +616,7 @@ function POSITIONABLE:GetHeading() end end - self:E({"Cannot GetHeading", Positionable = self, Alive = self:IsAlive()}) - + self:E( { "Cannot GetHeading", Positionable = self, Alive = self:IsAlive() } ) return nil end @@ -624,6 +626,7 @@ end -- If the unit is a helicopter or a plane, then this method will return true, otherwise false. -- @param #POSITIONABLE self -- @return #boolean Air category evaluation result. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:IsAir() self:F2() @@ -639,6 +642,7 @@ function POSITIONABLE:IsAir() return IsAirResult end + self:E( { "Cannot check IsAir", Positionable = self, Alive = self:IsAlive() } ) return nil end @@ -646,6 +650,7 @@ end -- If the unit is a ground vehicle or infantry, this method will return true, otherwise false. -- @param #POSITIONABLE self -- @return #boolean Ground category evaluation result. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:IsGround() self:F2() @@ -655,19 +660,20 @@ function POSITIONABLE:IsGround() local UnitDescriptor = DCSUnit:getDesc() self:T3( { UnitDescriptor.category, Unit.Category.GROUND_UNIT } ) - local IsGroundResult = ( UnitDescriptor.category == Unit.Category.GROUND_UNIT ) + local IsGroundResult = (UnitDescriptor.category == Unit.Category.GROUND_UNIT) self:T3( IsGroundResult ) return IsGroundResult end + self:E( { "Cannot check IsGround", Positionable = self, Alive = self:IsAlive() } ) return nil end - --- Returns if the unit is of ship category. -- @param #POSITIONABLE self -- @return #boolean Ship category evaluation result. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:IsShip() self:F2() @@ -675,16 +681,18 @@ function POSITIONABLE:IsShip() if DCSUnit then local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) - local IsShip = ( UnitDescriptor.category == Unit.Category.SHIP ) + local IsShipResult = (UnitDescriptor.category == Unit.Category.SHIP) - return IsShip + self:T3( IsShipResult ) + return IsShipResult end + self:E( { "Cannot check IsShip", Positionable = self, Alive = self:IsAlive() } ) return nil end - --- Returns if the unit is a submarine. -- @param #POSITIONABLE self -- @return #boolean Submarines attributes result. @@ -702,10 +710,10 @@ function POSITIONABLE:IsSubmarine() end end + self:E( { "Cannot check IsSubmarine", Positionable = self, Alive = self:IsAlive() } ) return nil end - --- Returns true if the POSITIONABLE is in the air. -- Polymorphic, is overridden in GROUP and UNIT. -- @param Wrapper.Positionable#POSITIONABLE self @@ -717,8 +725,7 @@ function POSITIONABLE:InAir() return nil end - ---- Returns the a @{Velocity} object from the positionable. +--- Returns the a @{Velocity} object from the POSITIONABLE. -- @param Wrapper.Positionable#POSITIONABLE self -- @return Core.Velocity#VELOCITY Velocity The Velocity object. -- @return #nil The POSITIONABLE is not existing or alive. @@ -737,8 +744,6 @@ function POSITIONABLE:GetVelocity() return nil end - - --- Returns the POSITIONABLE velocity Vec3 vector. -- @param Wrapper.Positionable#POSITIONABLE self -- @return DCS#Vec3 The velocity Vec3 vector @@ -761,17 +766,17 @@ end --- Get relative velocity with respect to another POSITIONABLE. -- @param #POSITIONABLE self --- @param #POSITIONABLE positionable Other positionable. +-- @param #POSITIONABLE Positionable Other POSITIONABLE. -- @return #number Relative velocity in m/s. -function POSITIONABLE:GetRelativeVelocity(positionable) +function POSITIONABLE:GetRelativeVelocity( Positionable ) self:F2( self.PositionableName ) - local v1=self:GetVelocityVec3() - local v2=positionable:GetVelocityVec3() + local v1 = self:GetVelocityVec3() + local v2 = Positionable:GetVelocityVec3() - local vtot=UTILS.VecAdd(v1,v2) + local vtot = UTILS.VecAdd( v1, v2 ) - return UTILS.VecNorm(vtot) + return UTILS.VecNorm( vtot ) end @@ -783,7 +788,7 @@ function POSITIONABLE:GetHeight() --R2.1 local DCSPositionable = self:GetDCSObject() - if DCSPositionable then + if DCSPositionable and DCSPositionable:isExist() then local PositionablePosition = DCSPositionable:getPosition() if PositionablePosition then local PositionableHeight = PositionablePosition.p.y @@ -795,10 +800,9 @@ function POSITIONABLE:GetHeight() --R2.1 return nil end - --- Returns the POSITIONABLE velocity in km/h. -- @param Wrapper.Positionable#POSITIONABLE self --- @return #number The velocity in km/h +-- @return #number The velocity in km/h. function POSITIONABLE:GetVelocityKMH() self:F2( self.PositionableName ) @@ -838,12 +842,13 @@ end -- @return #number The velocity in knots. function POSITIONABLE:GetVelocityKNOTS() self:F2( self.PositionableName ) - return UTILS.MpsToKnots(self:GetVelocityMPS()) + return UTILS.MpsToKnots( self:GetVelocityMPS() ) end ---- Returns the Angle of Attack of a positionable. +--- Returns the Angle of Attack of a POSITIONABLE. -- @param Wrapper.Positionable#POSITIONABLE self -- @return #number Angle of attack in degrees. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetAoA() -- Get position of the unit. @@ -854,34 +859,34 @@ function POSITIONABLE:GetAoA() -- Get velocity vector of the unit. local unitvel = self:GetVelocityVec3() - if unitvel and UTILS.VecNorm(unitvel)~=0 then + if unitvel and UTILS.VecNorm( unitvel ) ~= 0 then -- Get wind vector including turbulences. - local wind=self:GetCoordinate():GetWindWithTurbulenceVec3() + local wind = self:GetCoordinate():GetWindWithTurbulenceVec3() -- Include wind vector. - unitvel.x=unitvel.x-wind.x - unitvel.y=unitvel.y-wind.y - unitvel.z=unitvel.z-wind.z + unitvel.x = unitvel.x - wind.x + unitvel.y = unitvel.y - wind.y + unitvel.z = unitvel.z - wind.z -- Unit velocity transformed into aircraft axes directions. local AxialVel = {} -- Transform velocity components in direction of aircraft axes. - AxialVel.x = UTILS.VecDot(unitpos.x, unitvel) - AxialVel.y = UTILS.VecDot(unitpos.y, unitvel) - AxialVel.z = UTILS.VecDot(unitpos.z, unitvel) + AxialVel.x = UTILS.VecDot( unitpos.x, unitvel ) + AxialVel.y = UTILS.VecDot( unitpos.y, unitvel ) + AxialVel.z = UTILS.VecDot( unitpos.z, unitvel ) -- AoA is angle between unitpos.x and the x and y velocities. - local AoA = math.acos(UTILS.VecDot({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = AxialVel.y, z = 0})/UTILS.VecNorm({x = AxialVel.x, y = AxialVel.y, z = 0})) + local AoA = math.acos( UTILS.VecDot( { x = 1, y = 0, z = 0 }, { x = AxialVel.x, y = AxialVel.y, z = 0 } ) / UTILS.VecNorm( { x = AxialVel.x, y = AxialVel.y, z = 0 } ) ) - --Set correct direction: + -- Set correct direction: if AxialVel.y > 0 then AoA = -AoA end -- Return AoA value in degrees. - return math.deg(AoA) + return math.deg( AoA ) end end @@ -889,9 +894,10 @@ function POSITIONABLE:GetAoA() return nil end ---- Returns the unit's climb or descent angle. +--- Returns the climb or descent angle of the POSITIONABLE. -- @param Wrapper.Positionable#POSITIONABLE self --- @return #number Climb or descent angle in degrees. Or 0 if velocity vector norm is zero (or nil). Or nil, if the position of the POSITIONABLE returns nil. +-- @return #number Climb or descent angle in degrees. Or 0 if velocity vector norm is zero. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetClimbAngle() -- Get position of the unit. @@ -902,31 +908,33 @@ function POSITIONABLE:GetClimbAngle() -- Get velocity vector of the unit. local unitvel = self:GetVelocityVec3() - if unitvel and UTILS.VecNorm(unitvel)~=0 then + if unitvel and UTILS.VecNorm( unitvel ) ~= 0 then -- Calculate climb angle. - local angle=math.asin(unitvel.y/UTILS.VecNorm(unitvel)) + local angle = math.asin( unitvel.y / UTILS.VecNorm( unitvel ) ) -- Return angle in degrees. - return math.deg(angle) + return math.deg( angle ) else return 0 end + end return nil end ---- Returns the pitch angle of a unit. +--- Returns the pitch angle of a POSITIONABLE. -- @param Wrapper.Positionable#POSITIONABLE self --- @return #number Pitch ange in degrees. +-- @return #number Pitch angle in degrees. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetPitch() -- Get position of the unit. local unitpos = self:GetPosition() if unitpos then - return math.deg(math.asin(unitpos.x.y)) + return math.deg( math.asin( unitpos.x.y ) ) end return nil @@ -934,7 +942,8 @@ end --- Returns the roll angle of a unit. -- @param Wrapper.Positionable#POSITIONABLE self --- @return #number Pitch ange in degrees. +-- @return #number Pitch angle in degrees. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetRoll() -- Get position of the unit. @@ -942,87 +951,98 @@ function POSITIONABLE:GetRoll() if unitpos then - --first, make a vector that is perpendicular to y and unitpos.x with cross product - local cp = UTILS.VecCross(unitpos.x, {x = 0, y = 1, z = 0}) + -- First, make a vector that is perpendicular to y and unitpos.x with cross product + local cp = UTILS.VecCross( unitpos.x, { x = 0, y = 1, z = 0 } ) - --now, get dot product of of this cross product with unitpos.z - local dp = UTILS.VecDot(cp, unitpos.z) + -- Now, get dot product of of this cross product with unitpos.z + local dp = UTILS.VecDot( cp, unitpos.z ) - --now get the magnitude of the roll (magnitude of the angle between two vectors is acos(vec1.vec2/|vec1||vec2|) - local Roll = math.acos(dp/(UTILS.VecNorm(cp)*UTILS.VecNorm(unitpos.z))) + -- Now get the magnitude of the roll (magnitude of the angle between two vectors is acos(vec1.vec2/|vec1||vec2|) + local Roll = math.acos( dp / (UTILS.VecNorm( cp ) * UTILS.VecNorm( unitpos.z )) ) - --now, have to get sign of roll. - -- by convention, making right roll positive - -- to get sign of roll, use the y component of unitpos.z. For right roll, y component is negative. + -- Now, have to get sign of roll. By convention, making right roll positive + -- To get sign of roll, use the y component of unitpos.z. For right roll, y component is negative. if unitpos.z.y > 0 then -- left roll, flip the sign of the roll Roll = -Roll end - return math.deg(Roll) - end -end + return math.deg( Roll ) ---- Returns the yaw angle of a unit. --- @param Wrapper.Positionable#POSITIONABLE self --- @return #number Yaw ange in degrees. -function POSITIONABLE:GetYaw() - - local unitpos = self:GetPosition() - if unitpos then - -- get unit velocity - local unitvel = self:GetVelocityVec3() - - if unitvel and UTILS.VecNorm(unitvel) ~= 0 then --must have non-zero velocity! - local AxialVel = {} --unit velocity transformed into aircraft axes directions - - --transform velocity components in direction of aircraft axes. - AxialVel.x = UTILS.VecDot(unitpos.x, unitvel) - AxialVel.y = UTILS.VecDot(unitpos.y, unitvel) - AxialVel.z = UTILS.VecDot(unitpos.z, unitvel) - - --Yaw is the angle between unitpos.x and the x and z velocities - --define right yaw as positive - local Yaw = math.acos(UTILS.VecDot({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = 0, z = AxialVel.z})/UTILS.VecNorm({x = AxialVel.x, y = 0, z = AxialVel.z})) - - --now set correct direction: - if AxialVel.z > 0 then - Yaw = -Yaw - end - return Yaw - end - end - -end - - ---- Returns the message text with the callsign embedded (if there is one). --- @param #POSITIONABLE self --- @param #string Message The message text --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. --- @return #string The message text -function POSITIONABLE:GetMessageText( Message, Name ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - local Callsign = string.format( "%s", ( ( Name ~= "" and Name ) or self:GetCallsign() ~= "" and self:GetCallsign() ) or self:GetName() ) - local MessageText = string.format("%s - %s", Callsign, Message ) - return MessageText end return nil end +--- Returns the yaw angle of a POSITIONABLE. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #number Yaw angle in degrees. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetYaw() + + -- Get position of the unit. + local unitpos = self:GetPosition() + + if unitpos then + + -- get unit velocity + local unitvel = self:GetVelocityVec3() + + if unitvel and UTILS.VecNorm( unitvel ) ~= 0 then -- must have non-zero velocity! + local AxialVel = {} -- unit velocity transformed into aircraft axes directions + + -- transform velocity components in direction of aircraft axes. + AxialVel.x = UTILS.VecDot( unitpos.x, unitvel ) + AxialVel.y = UTILS.VecDot( unitpos.y, unitvel ) + AxialVel.z = UTILS.VecDot( unitpos.z, unitvel ) + + -- Yaw is the angle between unitpos.x and the x and z velocities + -- define right yaw as positive + local Yaw = math.acos( UTILS.VecDot( { x = 1, y = 0, z = 0 }, { x = AxialVel.x, y = 0, z = AxialVel.z } ) / UTILS.VecNorm( { x = AxialVel.x, y = 0, z = AxialVel.z } ) ) + + -- now set correct direction: + if AxialVel.z > 0 then + Yaw = -Yaw + end + return Yaw + end + + end + + return nil +end + +--- Returns the message text with the callsign embedded (if there is one). +-- @param #POSITIONABLE self +-- @param #string Message The message text. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. +-- @return #string The message text. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetMessageText( Message, Name ) + + local DCSObject = self:GetDCSObject() + + if DCSObject then + + local Callsign = string.format( "%s", ((Name ~= "" and Name) or self:GetCallsign() ~= "" and self:GetCallsign()) or self:GetName() ) + local MessageText = string.format( "%s - %s", Callsign, Message ) + return MessageText + + end + + return nil +end --- Returns a message with the callsign embedded (if there is one). -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. -- @return Core.Message#MESSAGE -function POSITIONABLE:GetMessage( Message, Duration, Name ) --R2.1 changed callsign and name and using GetMessageText +function POSITIONABLE:GetMessage( Message, Duration, Name ) local DCSObject = self:GetDCSObject() + if DCSObject then local MessageText = self:GetMessageText( Message, Name ) return MESSAGE:New( MessageText, Duration ) @@ -1035,7 +1055,7 @@ end -- @param #POSITIONABLE self -- @param #string Message The message text -- @param Core.Message#MESSAGE MessageType MessageType The message type. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. -- @return Core.Message#MESSAGE function POSITIONABLE:GetMessageType( Message, MessageType, Name ) -- R2.2 changed callsign and name and using GetMessageText @@ -1053,7 +1073,7 @@ end -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToAll( Message, Duration, Name ) self:F2( { Message, Duration } ) @@ -1071,7 +1091,7 @@ end -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param DCS#coalition MessageCoalition The Coalition receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition, Name ) self:F2( { Message, Duration } ) @@ -1085,14 +1105,13 @@ function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition, N return nil end - --- Send a message to a coalition. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param Core.Message#MESSAGE.Type MessageType The message type that determines the duration. -- @param DCS#coalition MessageCoalition The Coalition receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageTypeToCoalition( Message, MessageType, MessageCoalition, Name ) self:F2( { Message, MessageType } ) @@ -1106,13 +1125,12 @@ function POSITIONABLE:MessageTypeToCoalition( Message, MessageType, MessageCoali return nil end - --- Send a message to the red coalition. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToRed( Message, Duration, Name ) self:F2( { Message, Duration } ) @@ -1129,7 +1147,7 @@ end -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToBlue( Message, Duration, Name ) self:F2( { Message, Duration } ) @@ -1147,7 +1165,7 @@ end -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param Wrapper.Client#CLIENT Client The client object receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToClient( Message, Duration, Client, Name ) self:F2( { Message, Duration } ) @@ -1159,13 +1177,37 @@ function POSITIONABLE:MessageToClient( Message, Duration, Client, Name ) return nil end +--- Send a message to a @{Wrapper.Unit}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param DCS#Duration Duration The duration of the message. +-- @param Wrapper.Unit#UNIT MessageUnit The UNIT object receiving the message. +-- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +function POSITIONABLE:MessageToUnit( Message, Duration, MessageUnit, Name ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + if DCSObject:isExist() then + if MessageUnit:IsAlive() then + self:GetMessage( Message, Duration, Name ):ToUnit( MessageUnit ) + else + BASE:E( { "Message not sent to Unit; Unit is not alive...", Message = Message, MessageUnit = MessageUnit } ) + end + else + BASE:E( { "Message not sent to Unit; Positionable is not alive ...", Message = Message, Positionable = self, MessageUnit = MessageUnit } ) + end + end +end + --- Send a message to a @{Wrapper.Group}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param Wrapper.Group#GROUP MessageGroup The GROUP object receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToGroup( Message, Duration, MessageGroup, Name ) self:F2( { Message, Duration } ) @@ -1178,11 +1220,15 @@ function POSITIONABLE:MessageToGroup( Message, Duration, MessageGroup, Name ) BASE:E( { "Message not sent to Group; Group is not alive...", Message = Message, MessageGroup = MessageGroup } ) end else - BASE:E( { "Message not sent to Group; Positionable is not alive ...", Message = Message, Positionable = self, MessageGroup = MessageGroup } ) + BASE:E( { + "Message not sent to Group; Positionable is not alive ...", + Message = Message, + Positionable = self, + MessageGroup = MessageGroup + } ) end end - return nil end @@ -1219,7 +1265,7 @@ end -- @param #string Message The message text -- @param Core.Message#MESSAGE.Type MessageType The message type that determines the duration. -- @param Wrapper.Group#GROUP MessageGroup The GROUP object receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, the Name is the type of the POSITIONABLE. function POSITIONABLE:MessageTypeToGroup( Message, MessageType, MessageGroup, Name ) self:F2( { Message, MessageType } ) @@ -1239,16 +1285,38 @@ end -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param Core.Set#SET_GROUP MessageSetGroup The SET_GROUP collection receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -function POSITIONABLE:MessageToSetGroup( Message, Duration, MessageSetGroup, Name ) --R2.1 +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. +function POSITIONABLE:MessageToSetGroup( Message, Duration, MessageSetGroup, Name ) self:F2( { Message, Duration } ) local DCSObject = self:GetDCSObject() if DCSObject then if DCSObject:isExist() then - MessageSetGroup:ForEachGroupAlive( + MessageSetGroup:ForEachGroupAlive( function( MessageGroup ) + self:GetMessage( Message, Duration, Name ):ToGroup( MessageGroup ) + end ) + end + end + + return nil +end + +--- Send a message to a @{Core.Set#SET_UNIT}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param DCS#Duration Duration The duration of the message. +-- @param Core.Set#SET_UNIT MessageSetUnit The SET_UNIT collection receiving the message. +-- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +function POSITIONABLE:MessageToSetUnit( Message, Duration, MessageSetUnit, Name ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + if DCSObject:isExist() then + MessageSetUnit:ForEachUnit( function( MessageGroup ) - self:GetMessage( Message, Duration, Name ):ToGroup( MessageGroup ) + self:GetMessage( Message, Duration, Name ):ToUnit( MessageGroup ) end ) end @@ -1286,7 +1354,7 @@ end -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +-- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:Message( Message, Duration, Name ) self:F2( { Message, Duration } ) @@ -1302,17 +1370,17 @@ end -- Set parameters with the methods provided, then use RADIO:Broadcast() to actually broadcast the message -- @param #POSITIONABLE self -- @return Core.Radio#RADIO Radio -function POSITIONABLE:GetRadio() --R2.1 - self:F2(self) - return RADIO:New(self) +function POSITIONABLE:GetRadio() + self:F2( self ) + return RADIO:New( self ) end --- Create a @{Core.Radio#BEACON}, to allow this POSITIONABLE to broadcast beacon signals -- @param #POSITIONABLE self -- @return Core.Radio#RADIO Radio -function POSITIONABLE:GetBeacon() --R2.1 - self:F2(self) - return BEACON:New(self) +function POSITIONABLE:GetBeacon() + self:F2( self ) + return BEACON:New( self ) end --- Start Lasing a POSITIONABLE @@ -1321,7 +1389,7 @@ end -- @param #number LaserCode Laser code or random number in [1000, 9999]. -- @param #number Duration Duration of lasing in seconds. -- @return Core.Spot#SPOT -function POSITIONABLE:LaseUnit( Target, LaserCode, Duration ) --R2.1 +function POSITIONABLE:LaseUnit( Target, LaserCode, Duration ) self:F2() LaserCode = LaserCode or math.random( 1000, 9999 ) @@ -1329,9 +1397,9 @@ function POSITIONABLE:LaseUnit( Target, LaserCode, Duration ) --R2.1 local RecceDcsUnit = self:GetDCSObject() local TargetVec3 = Target:GetVec3() - self:F("bulding spot") + self:F( "building spot" ) self.Spot = SPOT:New( self ) -- Core.Spot#SPOT - self.Spot:LaseOn( Target, LaserCode, Duration) + self.Spot:LaseOn( Target, LaserCode, Duration ) self.LaserCode = LaserCode return self.Spot @@ -1340,17 +1408,17 @@ end --- Start Lasing a COORDINATE. -- @param #POSITIONABLE self --- @param Core.Point#COORDIUNATE Coordinate The coordinate where the lase is pointing at. +-- @param Core.Point#COORDINATE Coordinate The coordinate where the lase is pointing at. -- @param #number LaserCode Laser code or random number in [1000, 9999]. -- @param #number Duration Duration of lasing in seconds. -- @return Core.Spot#SPOT -function POSITIONABLE:LaseCoordinate(Coordinate, LaserCode, Duration) +function POSITIONABLE:LaseCoordinate( Coordinate, LaserCode, Duration ) self:F2() - LaserCode = LaserCode or math.random(1000, 9999) + LaserCode = LaserCode or math.random( 1000, 9999 ) - self.Spot = SPOT:New(self) -- Core.Spot#SPOT - self.Spot:LaseOnCoordinate(Coordinate, LaserCode, Duration) + self.Spot = SPOT:New( self ) -- Core.Spot#SPOT + self.Spot:LaseOnCoordinate( Coordinate, LaserCode, Duration ) self.LaserCode = LaserCode return self.Spot @@ -1359,7 +1427,7 @@ end --- Stop Lasing a POSITIONABLE -- @param #POSITIONABLE self -- @return #POSITIONABLE -function POSITIONABLE:LaseOff() --R2.1 +function POSITIONABLE:LaseOff() self:F2() if self.Spot then @@ -1373,7 +1441,7 @@ end --- Check if the POSITIONABLE is lasing a target -- @param #POSITIONABLE self -- @return #boolean true if it is lasing a target -function POSITIONABLE:IsLasing() --R2.1 +function POSITIONABLE:IsLasing() self:F2() local Lasing = false @@ -1388,7 +1456,7 @@ end --- Get the Spot -- @param #POSITIONABLE self -- @return Core.Spot#SPOT The Spot -function POSITIONABLE:GetSpot() --R2.1 +function POSITIONABLE:GetSpot() return self.Spot end @@ -1396,7 +1464,7 @@ end --- Get the last assigned laser code -- @param #POSITIONABLE self -- @return #number The laser code -function POSITIONABLE:GetLaserCode() --R2.1 +function POSITIONABLE:GetLaserCode() return self.LaserCode end @@ -1478,7 +1546,7 @@ do -- Cargo -- @return #number CargoBayFreeWeight function POSITIONABLE:GetCargoBayFreeWeight() - -- When there is no cargo bay weight limit set, then calculate this for this positionable! + -- When there is no cargo bay weight limit set, then calculate this for this POSITIONABLE! if not self.__.CargoBayWeightLimit then self:SetCargoBayWeightLimit() end @@ -1500,7 +1568,7 @@ do -- Cargo -- User defined value --- self.__.CargoBayWeightLimit = WeightLimit - elseif self.__.CargoBayWeightLimit~=nil then + elseif self.__.CargoBayWeightLimit ~= nil then -- Value already set ==> Do nothing! else --- @@ -1514,7 +1582,7 @@ do -- Cargo -- Unit type name. local TypeName=Desc.typeName or "Unknown Type" - -- When an airplane or helicopter, we calculate the weightlimit based on the descriptor. + -- When an airplane or helicopter, we calculate the WeightLimit based on the descriptor. if self:IsAir() then -- Max takeoff weight if DCS descriptors have unrealstic values. @@ -1585,14 +1653,14 @@ do -- Cargo ["AAV7"] = 25, ["Bedford_MWD"] = 8, -- new by kappa ["Blitz_36-6700A"] = 10, -- new by kappa - ["BMD-1"] = 9, -- IRL should be 4 passengers + ["BMD-1"] = 9, -- IRL should be 4 passengers ["BMP-1"] = 8, ["BMP-2"] = 7, - ["BMP-3"] = 8, -- IRL should be 7+2 passengers + ["BMP-3"] = 8, -- IRL should be 7+2 passengers ["Boman"] = 25, ["BTR-80"] = 9, -- IRL should be 7 passengers ["BTR-82A"] = 9, -- new by kappa -- IRL should be 7 passengers - ["BTR_D"] = 12, -- IRL should be 10 passengers + ["BTR_D"] = 12, -- IRL should be 10 passengers ["Cobra"] = 8, ["Land_Rover_101_FC"] = 11, -- new by kappa ["Land_Rover_109_S3"] = 7, -- new by kappa @@ -1603,11 +1671,11 @@ do -- Cargo ["M1126 Stryker ICV"] = 9, ["M1134 Stryker ATGM"] = 9, ["M2A1_halftrack"] = 9, - ["M-113"] = 9, -- IRL should be 11 passengers + ["M-113"] = 9, -- IRL should be 11 passengers ["Marder"] = 6, ["MCV-80"] = 9, -- IRL should be 7 passengers ["MLRS FDDM"] = 4, - ["MTLB"] = 25, -- IRL should be 11 passengers + ["MTLB"] = 25, -- IRL should be 11 passengers ["GAZ-66"] = 8, ["GAZ-3307"] = 12, ["GAZ-3308"] = 14, @@ -1659,28 +1727,28 @@ end --- Cargo -- @param Utilities.Utils#FLARECOLOR FlareColor function POSITIONABLE:Flare( FlareColor ) self:F2() - trigger.action.signalFlare( self:GetVec3(), FlareColor , 0 ) + trigger.action.signalFlare( self:GetVec3(), FlareColor, 0 ) end --- Signal a white flare at the position of the POSITIONABLE. -- @param #POSITIONABLE self function POSITIONABLE:FlareWhite() self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.White , 0 ) + trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.White, 0 ) end --- Signal a yellow flare at the position of the POSITIONABLE. -- @param #POSITIONABLE self function POSITIONABLE:FlareYellow() self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Yellow , 0 ) + trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Yellow, 0 ) end --- Signal a green flare at the position of the POSITIONABLE. -- @param #POSITIONABLE self function POSITIONABLE:FlareGreen() self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Green , 0 ) + trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Green, 0 ) end --- Signal a red flare at the position of the POSITIONABLE. @@ -1695,9 +1763,9 @@ end --- Smoke the POSITIONABLE. -- @param #POSITIONABLE self --- @param Utilities.Utils#SMOKECOLOR SmokeColor The color to smoke to positionable. --- @param #number Range The range in meters to randomize the smoking around the positionable. --- @param #number AddHeight The height in meters to add to the altitude of the positionable. +-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. +-- @param #number Range The range in meters to randomize the smoking around the POSITIONABLE. +-- @param #number AddHeight The height in meters to add to the altitude of the POSITIONABLE. function POSITIONABLE:Smoke( SmokeColor, Range, AddHeight ) self:F2() if Range then @@ -1747,7 +1815,6 @@ function POSITIONABLE:SmokeBlue() trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Blue ) end - --- Returns true if the unit is within a @{Zone}. -- @param #POSITIONABLE self -- @param Core.Zone#ZONE_BASE Zone The zone to test. diff --git a/Moose Development/Moose/Wrapper/Static.lua b/Moose Development/Moose/Wrapper/Static.lua index 8377d9803..08c9a0bce 100644 --- a/Moose Development/Moose/Wrapper/Static.lua +++ b/Moose Development/Moose/Wrapper/Static.lua @@ -44,7 +44,7 @@ -- -- @field #STATIC STATIC = { - ClassName = "STATIC", + ClassName = "STATIC", } @@ -85,7 +85,7 @@ function STATIC:FindByName( StaticName, RaiseError ) self.StaticName = StaticName if StaticFound then - return StaticFound + return StaticFound end if RaiseError == nil or RaiseError == true then @@ -256,4 +256,3 @@ function STATIC:ReSpawnAt(Coordinate, Heading, Delay) return self end - diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 0aa5fb9a4..d72c0f96a 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -104,7 +104,7 @@ UNIT = { -- Registration. - + --- Create a new UNIT from DCSUnit. -- @param #UNIT self -- @param #string UnitName The name of the DCS unit. @@ -650,14 +650,14 @@ end -- @return #string The name of the DCS Unit. -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetPrefix() - self:F2( self.UnitName ) + 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 + local UnitPrefix = string.match( self.UnitName, ".*#" ):sub( 1, -2 ) + self:T3( UnitPrefix ) + return UnitPrefix end return nil @@ -1220,24 +1220,24 @@ end -- @return true If the other DCS Unit is within the radius of the 2D point of the DCS Unit. -- @return #nil The DCS Unit is not existing or alive. function UNIT:OtherUnitInRadius( AwaitUnit, Radius ) - self:F2( { self.UnitName, AwaitUnit.UnitName, Radius } ) + self:F2( { self.UnitName, AwaitUnit.UnitName, Radius } ) local DCSUnit = self:GetDCSObject() if DCSUnit then - local UnitVec3 = self:GetVec3() - local AwaitUnitVec3 = AwaitUnit:GetVec3() + 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 + 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 + return nil end