From de78f4130b8346b21ada551692dd38c7f265a493 Mon Sep 17 00:00:00 2001 From: JovialFeline Date: Tue, 14 Oct 2025 15:12:55 -0400 Subject: [PATCH] Add Proving Grounds (RA) --- mods/ra/fluent/lua.ftl | 22 +- mods/ra/maps/proving-grounds/map.bin | Bin 0 -> 81937 bytes mods/ra/maps/proving-grounds/map.ftl | 2 + mods/ra/maps/proving-grounds/map.png | Bin 0 -> 23343 bytes mods/ra/maps/proving-grounds/map.yaml | 1275 ++++++++++++ .../maps/proving-grounds/notifications.yaml | 6 + .../maps/proving-grounds/proving-grounds.lua | 1813 +++++++++++++++++ mods/ra/maps/proving-grounds/rules.yaml | 239 +++ mods/ra/maps/proving-grounds/sequences.yaml | 3 + mods/ra/maps/proving-grounds/weapons.yaml | 18 + mods/ra/missions.yaml | 1 + 11 files changed, 3378 insertions(+), 1 deletion(-) create mode 100644 mods/ra/maps/proving-grounds/map.bin create mode 100644 mods/ra/maps/proving-grounds/map.ftl create mode 100644 mods/ra/maps/proving-grounds/map.png create mode 100644 mods/ra/maps/proving-grounds/map.yaml create mode 100644 mods/ra/maps/proving-grounds/notifications.yaml create mode 100644 mods/ra/maps/proving-grounds/proving-grounds.lua create mode 100644 mods/ra/maps/proving-grounds/rules.yaml create mode 100644 mods/ra/maps/proving-grounds/sequences.yaml create mode 100644 mods/ra/maps/proving-grounds/weapons.yaml diff --git a/mods/ra/fluent/lua.ftl b/mods/ra/fluent/lua.ftl index 292691a491..d24623dd3f 100644 --- a/mods/ra/fluent/lua.ftl +++ b/mods/ra/fluent/lua.ftl @@ -48,10 +48,12 @@ destroy-convoys = Eliminate all passing Soviet convoys. ## allies-05a extract-transport = Get all your forces aboard the transport. +## allies-05abc, proving-grounds +battlefield-control = Battlefield Control + # allies-05abc spy = Spy disguise-spy = Commander! You have to disguise me in order to get through the enemy patrols. -battlefield-control = Battlefield Control skip-heroics = Good work! But next time, skip the heroics! rescue-tanya = Rescue Tanya. eliminate-soviet-units = Eliminate all Soviet units in this area. @@ -256,6 +258,24 @@ submarine-construction-complete-in = Submarine construction complete in { $time destroy-soviet-sub-pen = Destroy the Soviet Submarine Pen. power-down-tesla-coils = Cut power to the Tesla Coils. +# proving-grounds +tech-center-survive = The Tech Center must survive. +regain-control-of-our-naval-base = Regain control of our naval base. +capture-airfield-before-cruisers = Capture an Airfield before Cruisers arrive. +wipe-out-intruders-with-new-submarines = Wipe out the Allied intruders with the + help of our new submarines. +naval-base-destroyed = The naval base has been destroyed! +our-tech-center-destroyed = Our Tech Center has been destroyed! +our-tech-center-captured = Our Tech Center has been captured! +cruisers-arrive-in = Cruisers arrive in { $time } +cruisers-arrived = Cruisers have arrived! +scouts-mark-the-way-to-bases = Scouts will mark the way to our bases. Find their flares. +mining-outpost-reached = Mining outpost reached. +vehicle-depot-reached = Vehicle depot reached. Airfields marked. +airfields-reached = Airfields reached. Proceed to the final flare. +unauthorized-naval-units = Unauthorized naval units have entered the area! +protect-our-tech-center-at-all-costs = Protect our Tech Center at all costs! + ## sarin-gas-1-crackdown, soviet09 convoy-truck-escaping = Convoy truck attempting to escape! diff --git a/mods/ra/maps/proving-grounds/map.bin b/mods/ra/maps/proving-grounds/map.bin new file mode 100644 index 0000000000000000000000000000000000000000..16104ef5000d94102b3c7b224eec466fac75dbc6 GIT binary patch literal 81937 zcmeI*+mB>fbtdq2;*yn_m06XQ&Z`CH{+=-#%ZQ9VauZVdl|Aw6{0wti9H^ zzV+=LCn6&vvlf3^e_Gcn|6TiERd??m-o3lHdv|sB?$J2dJp8A3@9y2bdu6mQ-@SV> z#xSg!uiw3Ub#yWkv%GtExO;a!#H^eF2|U^2;rAF> zf`v&`U4uMIOBb14unIv+G;|4yM@#}NrJW#PXfG`3O9~q?OAJ-aDv}F`fTVDOg`+fQ zN?1b1C1>JQ6d5HWh$SBQoU+Ubkcc4;5*0N>s%Qw~6d;&io6-%52Eqn#=>iyNh_P#` z^B@_XmVg*Q*t!qXyh)KavJSRW_!mO&ulPobbfe&0T zzk!%wDRSo=1n>)hzy>7}0kClR&DjDrKzurRP;#+SPL?o3qA*!z0fwS-!zWu{cBzUO zh5#Nhs^h>8RZxbCxB*Ngk4uq>ksC_T7-$Hv)WsKxFej1>F>I^iddE>8|2r%NrvVMCB;PG1qklDc8VBTRY(5ez3o3nKAUks;F1U64^y#btIp z2288aq6tiXP4Q!;P*!1brv0cPUO?Ix%qcN~qajl1L~;Vb)1@p_%?dH3GYm^&!e6es zVz9Lo8FM7iaH%eR#2Y-wr%+^+81*6)OvKE>l#^~X0n!z+B$1zyx=|+0BF~^$6p$EI zp@kk0@m94X3ZU@d`7E*p8U>V}B`h7_L8N0sO!608&?<>oR>c(Q}YBOsn37@A1@d#$dj7z3JF zT~$I4Th)QUw8VgH35Z%&)eSgjwwOnZp^Ds4(ghC!Vnj*=$f+*@%B53&g9m2hg{Cg@ zvKTP=LGd&2iKGg-mbAA&#WDFQ-~%m)1Y)9mVl%eDd8WUSp_a$ zIwy}_(hd%4r9p<(%vRa>q)@cN#AQ@c!fJX1+c%WWsXHV=p%fY}RWTsL!4{4HAW0F% z$+jAgRkjo$kw{FsF!R6^sU4TPj#j;|(Ce5$aE>@hw*(&zP9SMkiR^4Nfv<}0Qdgu) z0Re|eKnT*=0T+O|7*NtBMkNLUU^xZjDa(0A1DarA5CFz$Rb&o417KPf%n+}TAs#Gn zMI&#J`O#*Bl>*HwPnb)A;LuMW!^5T&pg%k49HJg{+DZptN?xs4k3?N8>!GsvFKCISq+smkgza zsjGeIg41sV0V`5EKmiHUj!U^N+0yY5hNr#^AeMMQSrse*AT+jFBWej?zzKOVE~T3x zn1KsZ9ZnGV#j`D$twt`rNaRrtnt1+zb4DcrT1a}91apdq7PV0lAeS>OM+ae?!XRr+ zIYV`qC}9X$p+LE02urZsNX;n)AgC;jX|INJ7W>R)J)o5j3-4S@G+OZ8cpeXK(@$kVyD0Il*Em z(g*@4RyhX3;P4A(z(MkOQNyB^FELwct7D{i+~7k{NIP(}07?l^YNdLEogT<32f+{~ z(Hu1ZYQc=Hcu)zW438L2Gfc=jyppBF2*xSvti5PLxSs=~@+AAYP()VQ4e-A~=Rb?Yp#uKu&@b z&RoJzq;zUvm79Rls)&yh0#<#N9{yq==OjP~TgbSXfe;)%KU=yi@dyjh3mmX)I9Ndt z&uIwbmxw^)Hr`==^({ytm~d3VT)IRae$`>($-|F88dOC^ws=|LAI_E#mSR|yD8N7! zCIk(_G_wUal4A-dn_ndTRcC`zHWRqFKPvbpC(EEeO7NnD8O!C7kJ> zVXKkw6yS3CSgjtb{X126YIUdJ=ohSba%m8tNKWAVt6TLu5@*N=h*VLeu)s2gAV1OC z6vJ<*PTO_>Mv|-7RNXFHfTR>*>YAaYG+R;lILkuMl1u&&qqao0RW_0mflyv|YPsak z?zl880Xh7S%Bqy(qpG7?AJyTceD43b7j2&l9;)G?T0K;kep16vn#iECr)p(4^)ctsbvSy}{ch9-mfG3y;>(V@0L)V|8$+hC8*s(+o?SjT`0Ci{96_>e8(` zzE#(6)$hIpGI=43Kb<2#QNt6pexir~Ct_ajBwy3SU)*jaNoq(ZvM`d`tZYPfja+S8 zrxPx~>9rSTVX~AkTmlivz$fP+Tkq0jvjvuSV zW3~5K9eUi|sl7XOc&Dy(dB>xx7s%Ic)&8xza;tvpR{h>fBiBXbCu;FT?LE1aYmvgl4}PCdGG(zI+QU=-FbTf7bG@ivi1WP*vDJ-Ym_?UCp*hcG8r zmRzGj9o5lMT|24=&hCPr2y8)n6kz+`+8F+^4cyd%1{NO(Xs_3`x2}VAc{b@!<;~FG z?K-_(zx`+}9<9Ac>+sRK@>qMhyHP;Qow|Idu6B9*(Y;m6TXk@&uHLE}GuX|KD!&ey z{+FI;$lHh5(^XH`^6Ano!jxBli}Uc7aM7bbx8dPP~|Y=6MKWvv{Jj zU@EzUeDdL{4{vT7vILy|yz1xeE|fiW{@sX9kLtIN>Y=gCrujG`(c)$u-K^7_?GBtm zHqij3gk9cW*J@pt*4z8ocJIb5xPH5S=h0d|S{?!(YuTV|ZtTn(Vf)d&Rc`wi?~Mt@ z(B}RMr4NuqV>AKX9p9|$H|uvtG6!7 ztQ$A$!Ra9;1)z<<%HCbf?MkM{xgc{pI`m^dS8Sg6QYrJ8xukQu$8@7}6V{y5QKS-K zbwn8gMtQdLZ?;NFPNG@WyKNF25jX{qg(pt>P#d5uYUe>@zVe|OKGYr#{2n9O1!s7% zUv|MgBs@3VZr18%9p0>yoAtoWdT2Tcd7vSa$4<{p<9%jqaQdxyw!dA69L_{0<=5U% zdP%k4FSajXa3t z+6YUz9a$y*;zR8xDJgKE(eUt6)umcqYWJTlH!nMH*7|1q2?Y~;46t73hs?G7G~{QQ z*KFJDs_Je&=KijpTD@GMT;^U#WJW<%o? zdT+CB!w2fBr_>%c0S30mp99}A8D*puG2JmDAaN*TIG<($GAKzmIOD9z)wL00}*%Hv^8?}Fwm!JO$I)WpuV9^cO&3 znhU10Tuy`GGVvlE>1-Cia$ZCBpPsg?947?WpsrnS;%~w_;P_}tM_X=lp0P)LbmxHOsB2c&=G=4WMpdghx;eLzl;0Kz<23AG zBsJ4nJn0|@N+iD+noF_2Ji~>bp0-q9R?C;Q`wub-!M{3twUfT+-{ZTnc%)X3)czxN zbW)3x+B>Pkle*H+=Z#w2sJ$C?c%!b|sMBlh7PNn@ZP4Zbp9>f{BLHfP=DFJV^46_tUnw@{aU1Y}k zpBgb+V9w?W2y)x;vp+pW((U}RTD`2@e~__{hL@V>UhQ=KWo;85sr4gu@JJp1pH^L> zThP^hK5w*}4_M@{c^h^#+=Xn=<^U()kK2;7m-EBcINM`?3C)3jpuLbzFAj-j6{3SS z+x-FH{L%Mh)swY+a{C&?X~_^ASRg3b&D=(4o(<*9rkF*BIol9pRmG$rkXuvyBJr^# z%bqfthGPm3!pEyRT-DW8{npusYCFjh%zE*xFBQ+a`rV;}LG{YV>oj}++? zMbZhOp6YnA)zv8`I3SamA)c60v_J%5vP9~F#|!}tgAF#f1p)SjCv%X+1El}<@$zI1 zPuA+mGp{i=XJ)J-y*-Y`x|tE+R7EZLh?(h$IVMW*xD+EEtB^!M3cpK$a?RnaQ?{%G z=n$hgtVeAucKEJ%ZtM9!~z4Hsp2CduyJYJrx#gnyua{C%1 z$EM9AprYs@gMf4+=?no%GzCUjF_F;{Lm_@tAt0$K!3of&RWquV;9C;FBmT<&u3FX6 zs;;fhy!nYz^zU!`D1FcOd2;64+@Ct%Q1ymdzM(y9C`u@5m@%ZSj#Q5)4+QGz*DAue zWf#Pt%{C6E9~d>OK|{J={L|Fs{^s+`+q?RkPgTYb9e_h_du>zto1gQeGTGS$^i;$L zd07ZYOokT74vU={DZ`i!Q(MpVkbfhA}F415hiIvg!h>cqgcC^J- zNBh$cjBEoAH2qdS=bFE*?G@3v4;?(Y^xf%ieoi;c?fK6xjC-mm4k6Zde=J9HL){#n zG{rVVIx5i?ZA%zxih-?;F2A-tiy72_EC%@|!m3uQa{sw96MrM=g|~eucCc5&Uaj`F z?-9B4x1D-r4X*{mswm%=h!Qc?z4PQ#Vv|NVLPFGOAg>)WK=h1=%xRz({jG|(3WpzCA6;!e-*l5P`X^{U zM4Ob~>N!3TE`Bu72%r6gXmj4C^N&m(r;aNo`0%Li(o8o^F*wmPLSBlFtR~(ik)5%0 zefZj1ytdY_Z7*YTz$CW4IH=*ERtKB&obzLDT}*HLfQ3noR`;Io5%t%ZB0tz3XcWU7 z0p)VqodFsW6BJf>vPD|O1SN(go@38PV=Z;z2xiD-_+)eQ`DWDi3EK1_8V$E6XWEM{ zY}0(^jJ@5ddG5%EXo}KM7kE+|w_KuOARNJSHV~!{PyBk3XmAGS>g?MK`c30afrDBc z)cRm^o>9v1J%|2nhip0m#>wnGqHLDKZ{&5R$b%2Ildmp?@!`QXN5yuBq`kn3jqU2$ zs%LBY&Z>9T;<>8l+U-gVY_3m2yQQMJIH=GO4p5ZW8er~&^esTZEeFmgO7kcTsmJu-B`!w_#Tv}|yS)T7=pr}O3h?S(PeDwQB#o;{z2;fBmC zUKmYOA*1(5`+G!MXk9$ghSlSbRDGnDAKZMF2S3W^RM>R%5Cej(%hTtpp0CBTH9T9Z zch>OET0U39bIoph5&yeucy}%TsD?kPr5nz#efFGmJ|pRL_rkQ@6%-$Vcf24+wwAVasFGiC18`9BeM}0gSc~olT>hm=`U(08k)SFKjpKA|+ zb@pQJk1}pGzxLVl!hugyQ9E|`^gWsz^dutzsnj0tBW-6XJA%B9TqYoUp7ST(r4AHo`{ia|qoT|2*>Cl7MaQs1JHh zGOCoMXXNF{V?i%j%`s*RBdLBFwjkPC%{e4E@rMU%^9;`>b|Vg0hBF}l6k$B{Y0NFt0ss%eT`HbP%oV35&vh)iEvbD4h0 zD=ddlKs$R6*5bihKiFRXILWyiSR(##T_zl8za?*vFR+`d?K|Sq|mxk#pH=;C7d`mpGOz-wf3IxhrCRDyXHSSSX z#fw=U*WPg*9@mxQ&5yv^*~rW2-@`fk3D-uem(?(PcT_pG5Wv|1P`#=C7!F#rBrf!Gqi|N4924 z<`_HbIZnFMmpKhG2xv)|PIc!Y(DWQH`J+6eH9mVoCRr&2utgr1arL;aPv@bF&`x;1 zhcmU`b!cp7?~b;++(aftc`kpTy&QE~R?{13_9a_$@J!V+?fjqlxNWM^mB~q%E8pyX zu+M#nGk>dj{<%#5!Tc39yyDD*WxkIMd2GR~7yNVcLAYOP&Ec`6uN*V_lbza(_AEJO z5t&DpO>z7d(;G6u=Y#`64Dh4ZSG~TLuRn7u2(fwN@kEf#`JcTz+KI)$1P8N<%AHJa zpmqQCXKHxn%v(vR)dh*6P_NjReBiSA!13H?H1oHbr_W__ARSjU&VAWf#d(9x9tk_K z;!%}CZFbWzXLUUPLZw#+W9Uth!PYxVl` zAFXago*opTdd4~XQe4WlTH~IotBTE41zX>E$u};d1{zn-{^-%A*or(VztyLInm<3- z(}y^^JNR1F*J}B#s&7^MXvXI?o;ejTdM+$vJl0{gyz-k>Q_cq7-@`W!OM|s3Zvxx%FCsjRfT*$xc#VT_WgmV(KWFJ-W@XLUy73sN;&k9$BIWGl+;ql z5`)vKa^A%5Z{f*79rZa{aAZe5=;qsNoxD zJ>RS0d+m5DedX+zjW-oX=5#|cv@y>{=mqDHw?t~4I{Dck>bVq<8;dL@47rAWZCO&B zN#bxac%)=cr4R>Nw6begJ%0Q52l}Tfvq!|mcSn;BA46ycVq{mHMAU zO}p$d=9k$5KkM~xBEh`!{TjYMDLj?~j=82ZC?M@qMpG(Y{c_cp+xgdclXv!I5KvOzA<5Ntre(-4_$6(b|MYyVPMM%^LYC9yGxucA$Dc|%vVNOx}DoyyPSO!NwX)P ztomfrlbBjaJ7z^Sg+QunKNVfBzg){Nx1Z3hUVUTL8#gaR?RC4+jY45-6{a~I5lweF z>O7b$rv(9#*Afv3NO?xT1eLwWc8Ir>Eckg`Fo5t&7p5KH5;@t>uFt=a&>s=ASF`A8 zO}Z>RC7Sh;^ALu<`krsU!{k>~@Zs!B6glnDyq|xXv>V-cJBt3BpS8zBb@a&^KH2Uk zpunaO@@Z3dAww~P4#xg>tG-){JQ2LHhBuC~Pq#ZQK#PF(5zow=20NRgN>#Ard7 zSruVu`>t>Qx@i>d=bsVr`xdmN*<0dZ4?bSQ$7}iVc0a)u0iEKkN(gXb7y?l*$M58? zs{X3|=-qz$p7d{pMi42O18F739f|%t6wpYZlwb>Jjg0ZaaIgzzOUOhtiI80i7DNBd zM@apRo$;e#F>;;%yh;~|zZ6g!WTg%#FkHW>>P@wHQ>~^g z2tCNO5-AIs!3Ki4nH0h_m~FKqY(t7j6?rTP6U=W5*nPP4e#_>UT>2Y3vyX=Rt8`W9 z2>}i1#6%2o`0%@gX7_m*I@Ps-r!bs3Wo2CcpY7R=$1E2&S$l_7hwYr_@H4UnncEh8 zwCba6ZoepJ%${^Wp?B>wHGHO>+i1&)04d<3jbU1F$wmoDeF^5j`lcG*RLeJQK6+^7 z#;|M_8PkZ~8J6T`N2GJ6eP|kNy^O7~H7JbV98Ew;4`Fg`f5}Bhd1Ghx(Xe2-x)7fd zkU_P?k-1Al7;rJf)GrSXs*7O=)+{4xAlxYhO;!V$UV%9CIsaSb-&Xauru^{HT79%G zy}jz~)!#yamb#E}P&fGO$V&wHV$eV)ew>|8kr`;$AC$4Z^^NIrEkO<}kTfD!cXAn4 z11pjthE{AkTVkjv3x}*KodZA+zh2}mFT0R;y;{q&Z!ahz%Yh+pwh|P;Y-kDzPza5* znO#;+mPo6rhXe-xp$3oP@oChGA-zA>P zNlZlwFwD_euy%L?A`Fz78q2ewUf70y`>g!IdlJq1{uR_Rtm|2#-j=k8W5B)L;~jo zD@GORKwM&C);Oma75xy@UWCtHb>#3n|Lxlgf6{Kz;vvA70v}YWC;>3Tl1O$3$IxYH zNI_L;IO$rvfi{N12nb)Ummh|kPwoJWcK+W{i+8k_lp>E_tl`DB3kNT@-%ppAHsv~X zHc<)m1S_o;I}$}bG2(fInGtXqs+LaQQo~y|ho26u^vDxQhWKnWWCLK@K_hw!OKHM3 zZO2RCVdWBn7+G*c8i)B6)Y+?!R`iiJ;P3rOd!rsPwp<+amDZrSG_10-HEg>8-(|8a zg*+Mto?5na_&?gtMt?K)uK#(}pO?=c&wu{=jvC(4wm=LHs_(s6#^H-45iw|xRaDDZ z{Iu$)O}Ep(Z?`5w$x@3=2O#V)$(FekV^!|;sX7t5{t7t5u-_&N2JT_FDO z(^~y~4S(NuLCZRYkC<9HPPv3>HzmRWBGO0C^G~1KtzZmLMZbIvXs1 zV!#4NOs;s%hN>`yR80F)#CzAVzw6tI=0oN@?-6rFv}m8vG?Ql_hi;TtL4v{Gn|WW>n3P3B`|?nT>`<>}y{Y|$8op4=FEx>yZU}s?Jp|VA zf3b!yZddUHr3FxD-pT))8vbVU5IFvas(+~AV{NQHR+oNU!;fqA<7OQGtcIV}>Syhl zs~v->eF~swkSkK-?2;717+CS|UwHA8=U<}PdNHyvjDS2k<#7qtR-&B-#S3OL;3LDx z#tDXC)s`h4N#kVpu5T+k)4y#;{+3ApiEMh`5)`3;t8U~lv|3-S#aG+Q6_na`jMW9G zOA1c3`L*B@CERduX-Qa?7T-VfPJYz0Dg`p-7ayzj$7=t_?G_CIro+GZS=(j7xbbLT zyus$gE#U0PqPo&%o@;sWF^U?TmuGMOWD9cs6QfhGIYLOH0ZlEdq304HN>+gjR+I-5Sv+Qi&P`8P#_=y|_{*xl+`L=~fi#$+s@tjPBg>M$cnFT6 z!NcELp1qSPJ{_sjG<>0k7i#Z?_SX+|0T~CKN{q+}2I3MjO_UJB7Q?2ALap&>3DuL? zn?KvG#~38#npIITyBuv%LckmX!oaGiL^hq_QdXF#i+~h{ESTA##q0|J5q$Fi9K~;Z z`j_QCTf=A1K8?;_*!*SNf^2Jqr9+gVMwE{-x(FGBErl~af(_d4f2!(Jn^%Ck6NWI% zt1r~zg|Z8R^+;{2(4?&L)c@_OZ?{C4I@P#}(&eg%1E*Y?C?u>Uj^k*0^JjY#7`X*J ztkRSerAf!I3eEn4g3PZjK!(!jB~q|eGXnAF*huF$E}i`XKtKE&WA;gh#LM%i(fJFT zA65Nm`Z#IiCRo=fQ6T+l0X-GTP8kf@(MJoG*qn!-s^L>@D-&ahUoWc{YWYHY{~=vK z15Qk4Zpuri#+G#vTbs7u1#_(h_=N;*sv}yq)`NWBnL3WA;gh+@Ps~zY;3$XGeZi!;hwq zlg2R21&ayQ^nwq+SvpS)JQLQ)e7fq>wfJ;<9?zlW#8%5EFO)5iqI%--$vDna|MbAI zDhon%L(-)zN>Tsf-9MTIK{60p{ zHyQ9m5@%f)R=xi=e+v!*{tb3|>figjs=wR5La<7cbSgMXX5W(-dC$H6@A@k$MSPk? zGBTRMB4$9a>JmO#!WMIlX9+#FRbiS1!sdjG1C{ujP_y5>%WXe*!Zy!75dftG0$3^9 zj`Y8@+eN=)41ZDe7w!C8Oo0-G~g_5QAz!kYrObGY(c^ z;)$MG8Sr_enArl@1PP#-KOhjac{uycyMF%L&G3;nHh(u`A|oDiOd#lDJsp0R`+rfk zAefeTl(yr_nGLyUNXL^7Vg%@ghd7L@pQ!po+d;VoC|QXV?9J!RTX1>rDDPwB9i)+- z;8mWF@^(i8{;cZHHg`q6uWq-uHPFxk^-i7{zj{4;)VEVNQZfq=jCKTciB^Q}{FP7C@QKzwPb)Ek2-c3DKH3i;zpVP@^xje4 z$H+TKBMv}ltx$F$0e{xsvNQaScKZ|4XWw8v&R4I;A9-5K`Kf1Y2W@PTVHlc%?<_GI z)k*DWs4j`LlQwY{Mq3ET3$_t*b;K{yk{$s<-kZG<(T@G}^B3{me?XBUeQmzu`t_=> zPv5uv+szx2N*D*z_sYP^!c#2_qa+XvU0O2dFo;w_o5oS@;nOSmaMptKb$$T(WevaF zzIOx=j=uE5Fo0x#83)f-J-_*t)$N~{KKB<1o}T^$Zj?{&qdv$RIABXCB-wgQ3ysoP zQE7gw3>~Sfqh#?wz{;g8HrWXx7@&>7ANa+;c9=)Oa(dW`m^DPkEFU*(YC6V;XK5HDkf|*R@km{51(GiOTiP##6yFVu2I5Y3a4~_$il4J z4k?;Fd4Bx0%KXaeW7D6Qj?VoJ#@X+&&d)zsfIvWn2DWWaCo!$6E5N|StU3|c2xtcr zy<-?|p(h=34xU*GHIDo5T%1mS*UmdXv+sB`WzN1ch6!TOQbi&7tx$h99NF?HCta;} z51Eb%hL+-l14NIUJZ))uC9fSNv}V?k5{#TBOD-dh5*jgL#6N#w(;n1ke>=W;@&@DV z_gJ@&{ZYD{4LmVdR7K@5q_Cx&B1O@2nKmuqX~uz5ibR}U>PwGwGwW9j8;Eh*f9K+K z`lBV+oBsK`B?7!G?=Nyxgg|hBIS)KYZi?o6mERhF@nNhkSalr4ixF(t2s}9@y3~R& zs|dH%>6N?y{?%VMwDy}6BM1?q43IDpW>7$1(qXE#p1rWyysDY*t8|XkeeOno{X;jj z!<^>&#f_na0bfO{!m>fF;yB2KCz9ehF%%P_7;r3ME(~FSdU1I@zjM)lH|ly5pDg0R z`hhe*7WIDHri{7f)B@7@zY38ji|=iI`o^Te`@byX&+a7cZr2Jj&QmwZLMIB*KwV zQGPR#4PiLgi}+~*gkPd1gz?l%6LUniDu!PnSv`OgeveH2(R4!yhz8TCFszKoXeKQD zll0Lb*N z&RljcZ?-r2*^cxFh~O4K7Nw^_PvtNK?lQ*=D^5$QM|K`+Q^qXV9D|;g^hI#o^e#() zM02{xwz?Ffv|uScksv424IzzZ`}DhWaxrnJH(6B1zWmcxmeL$?^Z2kJInzW$thB&oO%(@;E}bQ)Q}F! zY*j5O2~$oX@o1R&Ly3*282-id(scN@_7f0TYKITz>c%Nv^~=)_!f8+do>y{n%fl*n zq;FLF-L7dbI?SB8qTt@(*8IchyC8kPK-z@(G4bBt`xM{5(UH$LE`D8M*bj=066>#B|tne0fqn^k05oyMi2x} zriXx~9CZj11q%yR2ytG`4+8Vg>*pUg0H6PE*M(;XC5~qwtmGjuzsa!s`Ol_^>Dz=k zhPt!q1V;%&Eu+d~RZ~_~L7Odr;ZjTzqX2mY6V|FF;uxfmBDkZ$%&Ar@g#fY*Xh2S9 zPly1i6xeW+Yw#G-!Q!TbTI#xFNU>}Er?bcoStMc|O}a3b*YktG{PX(x#|_zw>BltZ zzx|%w^z4AVa(ib#Snnr4PXY5Y-)%9QEf^aik-k7_!Q76}D78yVcxs=GtxG*dd@-^} z%Bf8=KEe!_3G15`SW7w5cB-j8mJ<{4#ax*^A-4U^+b?GiBzlR5CIuO#d0-+#SeKV) zZ_p`Z$PzEWoQ;qh1bt})0zY~pEp0vk%3e%ArrAWRr_^gtk2xq|(Savh09+Ly{L)iG3jSHL z7rv9we)7%xJo%*W;um|Sqo}Sb%E2Tr^&l$|hTfga8*~I0kcUrZk#Y@&;82nX3KM>| ztn3Azocm|ra8~Hr^b{lO=$Y*sYn0y{>^Sh9k3<@aGTOd9grs`}o*aAW+O1iX! zXSU3&FCMdCvn-cv%%SDLi)R?p1!zHx<0K4+6!p%-;I})+_j!EDs(s04`eKjLzF5`1 z*fSkPS^W`&Z7}hKEO?RM_ZefX!dFWQwvinTrQj&ev z=|;5uXU4y2FiJ|~`QbnJh&Z=3=T`AV3BA zYSvz`0h)nWGE0Z15Cv3OP45ZA*_*!PBY?KjomIJe(+>#Zv-L0ytJ!XKqh!d70j`jE zMe|VQ6e9&^SriWEwxM|pPCtKPF5PTw-s;RV#q?e%DDUXb-_*@*KXVRwZa(w5Z^Y(N zki8o_ibQ6O&_P|y`-x@XgSNV6VHy#cW5X$!u>6`Lm&VNrXsV7~yA%UTSkSVY-jmOJ z!k7CeeIp>DBuWA9OAGnXJlv?)lo??Ka7He+kz>f@%Mi5|- zmyt~&QKVo*@Zkeh|I}WA8zKz>ksUHSC4%O&#s1FE-t^w4ehtCNTKW|1?5$WWOAwH! zJQW8(MZZGhWgc-s6BS=Tm;V}GaPyg+n ze(dLhpWBYypyE8IKjt|bSdkijnle+n?e*x?msL(iBxPsIq!2$mmmjF%pPT+X0_Y$M z0vkR+BNbgY(T#M%mU+3DTX9|&<)!=dybM!0g-E9&hDKO{QqGJ6 zwqVUIEN4D{z2S#7{IHhO?L~|L;>!}82a6JrBaZp>VEgA}bGEzS=R|&~O|c6(|L6f% z6n?m&s%zC1q6T5L!;>=^O~hwk2%E=+T#@|Ifuzz6y+}-a&Qxz{Lt;u`p512jrnpZ=3yvI7Bn4k zvgJl4?L)Y7u>Etg&Nd~x?EQFm<@-`MvK0Nn>Q^m|6WQQVlt)vX?6k(O~%RvIOZi$untYLsk^j9~$BiN8pz9vzy66B%Uzu}Td~{3(>Z zF!PJ&v_zt$M>Mjm^Ko$AZJyqcIhp<7CRYkF(p7hfHl8jO<;N<0!L)R#?_GXs%hSPx zEZ}k~<_Y3#U8|>W-2PRKe*XK(??#t89+5_zJk2?h$&s+RkRx)A85@62vWPK9VScTd z=_bpZHuFG;C%YgRJ|g+r_$QC@Zgbv{i6+wr z^f{U4C=W+Pwah$e6I597_knjxCAIyT``CutPm(1BBn6!Ftlf%AJyQ?rTGDT3=^*egm6X=vP-#e z_VQEq+4nzln7go>vpqf{x--Vb^RJ4E0XLHiXY>fAoa#9_{5*p7j!!Q2f~s^LxycfS zZ4!ID-lJ#Qmz7%lY+>RJHV_0z-I^V%XOEI@6}SM2pv;DfV#M1%8;1;%X-Y52zcGSku;SnzYrU3oQ5&)2gp)W<#i(LD{ncXp4(sF0w(vSjQI=@Rc z-5^M3qqLXbc7J~ST3Y@+h37W^&H^&~&sIHK%Xd}1s}}FAdhh13Zz+xI&c7Fbbh|x3 z)UpNmE1?#TLhT=%8hKhmZ~nc$e%eVIyL7;hIiHSy*0dd^V(ZoOpg1%TXg%(KPjaa zhFA05&X?D4r|M3v?zG>OnZE^;vrW%v+ciIT=6je&fyj0_cA*5hqBH3@)4S^0v>Fi{ zGpn2o!9+mbrFcT+L9G`~Hje<8R02Xkxjc~B0D;8G5XKf6L$0BO*{T%rE)^2C&Ie?9 z3n=sa9gOK|v32*{`0p%aFP@$K`wM?q^@r`7rM@xSzBD`kSEbH&NO~ zXyV&~^_@DnQ^!wlzuOtvGe_jP)Y*=BqI~W`Eq>I*nb#@Nw5dURK`~N94ej$7@#MC^ zNiOj;@u-Q0JfQ4XqytEB0jluB)UrY@4Z)zSavIrCDV7wny2!_2(-*uYF7hqqd_b1> zhMlo zdAj{GC99{~pH-)DFVv0hjN1I?mfGc{IRQyT)eL?zYMH|PgrbQh9tOd!#_2L)1nfH~ z#Xh=Byj_4V#U`2)w(!RCh~FPmppwYXxSEf{#xHn7fPa#2DNi4ep%LV3zi3dl%oclF z#=rHGf9Ohs7f)vMyt;;0w|~;e7VORbNvVF{pxEXMoDpsMdgn&NJ9YU^U7dZmbAGCU z^vhHaEIXHd7uw}05e1MZ3vu!kpfAIvnUcZ?LPMJ8wDVY!;!>^{vjF=ghR)=or%ND= zXf8~Qa^&I>=HyERMLK>S1A@rSFTRbRCgfpaH$yB63Y9Ul(!EUWX&*yd)8+R`fW(nRBicIcpA9&P4!L^?`p z)68y%Z43}BhR)DH$+F5x*vKMYq}6PVMEf#)aPvw-q7*Y^VT2{`diJ)YbVIHf$ZDwq z0Z*710<@}@^!YoJ8t=-`Ds~q+tI?rH3d{HviCw^R=bXQ1or}q3TOE0#jk7j!)^Aj@ z1)Q3SRxZh6PP19kb#_V^!ye_(x@6~AiZ{d)$|_sA*aBihzV^X(Xw|YL-5@edeA?yo zaTuIPO=oCQ47F@7Kfvs1w$J6ozxLc!ia32EXmW0d?Bkq({O8iU)E$tgJ7WN01ZuN7 z@<(2$nCp%dTEm+ee!m(3qIAY{l zvM-Qz2L!RS7WHx%vO6kTjnttb#>gr|MF<8oRn!vC@81;eYW#|uLt?N34pTZx!7)d% z=wMchGqbL)ERI+u|pg_Izgc3fubdw1gb10H1|>=nCdFX$OSD-zmQq0R8~j!ckyiWG z=sKrMUrCIr!N**@*=qPeK-Hx%u$Ii2@JA*6id5q2w8iAju$$eUdp27$V#2|>rH{lk z4pv35=oyH)j3AcK)28k^Rjr{EGFHijs(7r-1`zz=z>GwS8oB_IT7uJ{$mkzIqMJk| z!m|fJ2!|*p(txR=B~Bz|MLHfO0+L#0PALrJ%_3#-@T;XL{A{`zD@*BAq!>eMIub^} zlkrxTJ6IkC-9Be@{q-20%7FqNVQFGX6>~JVq&mO&MBC^0hwAe)~JnXM2Rc{1a} zrUo=nSyjtrW}Yy?mXR!xkp0Wc{T7##Kfj#Y9t$mC>PRj$Gn_a>I5hBN@N)_iqbQp* z@zG7!S;CM?M$~W#fo(8Uv1CS`VU{jNIy-=7TflE*@8k-QPIr;UBcuyN01244@WJ*U|sSX z@qsfg(o%xF_BUzW3YL`%_={73{dC+QN;}3rCNf`I7=~* zv;>D-QWy#)00)UM0FWJq@-)z8G13RVptMjq>(fj^&l_+ za;98nmrApN&;dd}20U;q#m~Sk0kTrjtTka`;2Y2+a^kPSAgIp6&m%WX0J?;rVj2vB z5(-HNr57>$flmSNui8OB>+mtiXoj74a87V(WOXXDJtD+x)2OjfGQBO9jaIr z3{4h)&_qTVeteWmX{$WSxs)qLq)YyUi3Gv}U!-!m6zMXDBwdRMe=!k93xXl1yHtTA zSBzXd%{(qO%b!)YC5`ZyAtV5-qGAN76;@(^@VJykB4Jil*O8^n5Co*1>;fp@**IMr zkxuVYq9G?+JRFgbqo-MG;R9J1j~E_dY=vCnfV|nIFsrguVS{BLFPP1*1%9=}ThfB1 zfJ;ovR(S-Z3Ms?@QAJF27GTwgAgSmw4GLpO6l}JtQ=S++X_f)jl0=d4wL`IZI5Ff; z6&&~z0}4Iqp(G4*gA)S3NQtUoVz@LQ3vg++loa4Zkb+xS@vxOME2Ps%#K6`NV3w{r zRkNCwr3ixts-GP3Vg#fpe?WLpGNUSyo!-F$hr@}sjv$ytnpI3M(2@&WMO{PzYAC^p zya3IDwPfa$&Yu<(lIYTml1RwXLnb@IW{80qCUPSqO!4Uj8AAXXoKnPSK`?N+E|m*& zOu&ldVN2x4&EQ9X2Twzy(r^OBZYJyp;ni!9)l(z}+$K;Ta=R)wKzi6M($`-T`$Ji4$7#NdhAxN+Jz zVPY(?<#OWSQb2Yg>iKIuyZ?3y>=f83uv1{Cz)pdk0y_nE3hWfvDX>#ur@&5uodP=r zb_(ni*eS46V5h)Nft><71$GMT6xb=SQ(&jSPJx{QI|X(M>=f83uv1{Cz)pdk0y_nE z3hWfvDX>#ur@&5uodP=rb_(ni*eS46V5h)Nft><71$GMT6xb=SQ(&jSPJx{QI|Y7o z6c`r%WZ=2~2LIy3{rz8Sa9Fm^)9bKIztmX9f1mfS&({0UR9Kn^o!5BffA9DI&UZNW zzd7`jIKO-!$}zR5+41C$XJGRfxDVUC(9-!mZfE~D>A&fIH}(7*ap6XS0WbCR?it`WKmjjyUt*V?|Cd-fe^UPIUi-%j?DD-&fZgA` z7Wl8*->+l7@Tzy=|Lz^vUIpHJ<$myvulL6jK0N})N4!`6Q+E!=eb>Je-wwRK+q~^H zJvYyG*YCeOz+%x(d)sfFjz8^9dws9}rA0pr_geq2=gSQ}_q%cb#}@9pL;hKe%|;0*WDXm58ub<|K2)GJKr80_xJg~ zSO4yKoBLr~z1KbAURn2(uig8{-pA*Ld;dImKlS@ky|nva?u9QEvGeZ~*eS46V5h)N zft><71$GMT6xb=SQ(&jSPJx{QI|X(M>=f83uv1{Cz)pdk0y_nE3hWfvDX>#ur@&5u RodP=rb_(ni`2SVl{{r6|AG-hm literal 0 HcmV?d00001 diff --git a/mods/ra/maps/proving-grounds/map.ftl b/mods/ra/maps/proving-grounds/map.ftl new file mode 100644 index 0000000000..38471e6b89 --- /dev/null +++ b/mods/ra/maps/proving-grounds/map.ftl @@ -0,0 +1,2 @@ +## rules.yaml +actor-ats-name = Advanced Tactical Submarine diff --git a/mods/ra/maps/proving-grounds/map.png b/mods/ra/maps/proving-grounds/map.png new file mode 100644 index 0000000000000000000000000000000000000000..786ddc6ae89ba356243c81b5f4496b6ea36e2810 GIT binary patch literal 23343 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 0 then + Trigger.AfterDelay(delay, function() + actor.IsInWorld = true + end) + end + + if duration and duration > 0 then + RemoveActor(actor, delay + duration) + end + + return actor +end + +---@param location cpos +---@return actor +local function SpawnSignalFlare(location) + return SpawnMiscActor("flare", England, location, -1) +end + +---@param location cpos Cell to use as the camera's center. +---@param duration? integer Ticks for the camera to linger (after delay, if any). +---@param type? string Camera's actor type. +---@param delay? integer Ticks to delay the camera's creation. +---@return actor +local function SpawnRevealCamera(location, duration, type, delay) + duration = duration or DateTime.Seconds(6) + type = type or "camera" + return SpawnMiscActor(type, USSR, location, duration, delay) +end + +--- Drop some Soviets near France's storage area. Non-original. +local function ReinforceCrateParatroopers() + local crates = { MoneyCrate1, MoneyCrate2, HealCrate } + local cratesAcquired = Utils.All(crates, function(c) + return not c.IsInWorld end) + + if cratesAcquired then + return + end + + local revealed = false + local proxy = Actor.Create("powerproxy.flametroopers", false, { Owner = USSR }) + local targetPos = Map.CenterOfCell(CPos.New(30, 43)) + local planes = proxy.TargetParatroopers(targetPos, Angle.New(384)) + + Utils.Do(planes, function(plane) + Trigger.OnPassengerExited(plane, function() + if revealed then + return + end + + revealed = true + SpawnRevealCamera(HealCrateRevealMark.Location, 85, "camera.paradrop") + end) + end) +end + +--- Send some non-original units to support the starting base. This +--- is intended to compensate for the inability to build defenses there. +local function SendTechGuards() + local types = + { + default = { "apc.techguard", "apc.techguard" }, + easy = { "3tnk", "e4", "3tnk", "e4", "apc.techguard" } + } + local path = { TechGuardEntry.Location, Waypoint67.Location } + local guards = Reinforcements.ReinforceWithTransport(USSR, "lst.reinforcement", types[Difficulty] or types.default, path, { path[1] })[2] + local speechPlayed = false + local flipped = false + local goals = { [true] = TechGuardGoalWest.Location, [false] = TechGuardGoalEast.Location } + + Utils.Do(guards, function(guard) + Trigger.OnAddedToWorld(guard, function() + guard.Move(goals[flipped], 2) + flipped = not flipped + + if guard.HasProperty("UnloadPassengers") then + guard.UnloadPassengers() + end + + if speechPlayed then + return + end + + speechPlayed = true + Media.PlaySpeechNotification(USSR, "ReinforcementsArrived") + Beacon.New(England, TechGuardGoalEast.CenterPosition) + end) + end) +end + +---@param types string[] +---@param path cpos[] +local function ReinforceBasicBoatTeam(types, path) + local boatTeam = Reinforcements.ReinforceWithTransport(Greece, "lst", types, path, { path[1] }) + local boat, passengers = boatTeam[1], boatTeam[2] + Utils.Do(passengers, IdleHunt) + + return boat, passengers +end + +---@param transport actor Transport to reveal. +---@param location cpos Spawn location for the camera and trigger. +---@param hold? integer Ticks to continue revealing once the unload is done. +---@param type? string Type of camera to spawn. +---@param range? wdist Distance at which the transport triggers the reveal. +local function PrepareTransportReveal(transport, location, hold, type, range) + range = range or WDist.FromCells(6) + local pos = Map.CenterOfCell(location) + local activated = false + + Trigger.OnEnteredProximityTrigger(pos, range, function(actor, id) + if activated or actor ~= transport then + return + end + + activated = true + Trigger.RemoveProximityTrigger(id) + local camera = SpawnRevealCamera(location, -1, type) + + Trigger.OnPassengerExited(transport, function() + if transport.HasPassengers then + return + end + + RemoveActor(camera, hold) + end) + + Trigger.OnKilled(transport, function() + RemoveActor(camera, hold) + end) + end) +end + +---@param actors actor[] +---@param action fun() +local function OnAllAddedToWorld(actors, action) + local i = 0 + + Utils.Do(actors, function(actor) + Trigger.OnAddedToWorld(actor, function() + i = i + 1 + + if i == #actors then + action() + end + end) + end) +end + +---@param actor actor +---@return boolean +local function IsEastOfCenterRidge(actor) + return actor.Location.X > Waypoint99.Location.X +end + +---@param actor actor +---@return boolean +local function IsWall(actor) + return actor.Type == "brik" or actor.Type == "cycl" or actor.Type == "fenc" +end + +---@param actor actor +local function IsMobileSoviet(actor) + return actor.Owner == USSR and actor.HasProperty("Move") +end + +---@param actor actor +local function IsCruiser(actor) + return actor.Owner == Greece and actor.Type == "ca" +end + +---@param posA wpos +---@param posB wpos +---@return integer +local function SquaredDistance(posA, posB) + local diffX = posA.X - posB.X + local diffY = posA.Y - posB.Y + return diffX * diffX + diffY * diffY +end + +---@param origin wpos Point from which distances are measured. +---@param types string[] Target types to find. +---@param enemyPlayers player[] Players who will have their actors targeted. +---@param filter? fun(actor: actor):boolean Filter applied to actors after type and owner. +---@return actor[] +local function TargetsByDistance(origin, types, enemyPlayers, filter) + local targets = { } + local sort = table.sort + + Utils.Do(enemyPlayers, function(player) + targets = Utils.Concat(targets, player.GetActorsByTypes(types)) + end) + + if filter then + targets = Utils.Where(targets, filter) + end + + sort(targets, function(a, b) + return SquaredDistance(origin, a.CenterPosition) < SquaredDistance(origin, b.CenterPosition) + end) + + return targets +end + +---@param target actor +---@param harasser actor +---@return boolean +local function IsValidHarassTarget(target, harasser) + return target and not target.IsDead and not target.Owner.IsAlliedWith(harasser.Owner) +end + +---@param harasser actor Unit finished with harassment. +---@param onFinished? fun(harasser: actor) Called after OnIdle is clear. +local function ClearIdleHarass(harasser, onFinished) + Trigger.Clear(harasser, "OnIdle") + + if onFinished then + -- Delay in case the next function adds another OnIdle. + Trigger.AfterDelay(1, function() + if harasser.IsDead then + return + end + + onFinished(harasser) + end) + end +end + +--- Give a list of targets to a unit, and orders for interacting with each. +--- When idle, the unit performs these orders until no valid targets remain. +---@param harasser actor Unit taking orders. +---@param types string[] Target types to find. +---@param enemyPlayers player[] Players who will have their actors targeted. +---@param filter? fun(a: actor):boolean Filter applied to actors after type and owner. +---@param onNewTarget fun(harasser: actor, target: actor) Called when a new target is selected. +---@param onFinished? fun(harasser: actor) Called once no more targets can be found. +local function IdleHarass(harasser, types, enemyPlayers, filter, onNewTarget, onFinished) + if harasser.IsDead then + return + end + + Trigger.OnIdle(harasser, function() + local tbd = TargetsByDistance(harasser.CenterPosition, types, enemyPlayers, filter) + -- Cap the list size to limit how outdated it may become. + local targets = Utils.Take(5, tbd) + + if #targets == 0 then + ClearIdleHarass(harasser, onFinished) + return + end + + Utils.Do(targets, function(target) + harasser.CallFunc(function() + -- Target outdated? Assume a list refresh is needed. + if not IsValidHarassTarget(target, harasser) then + targets = { } + return + end + + onNewTarget(harasser, target) + end) + end) + end) +end + +--- Order units to harass when idle, each with their own initial target. +--- The intent is to reduce or stagger TargetsByDistance calls from groups. +---@param harassers actor[] Units taking orders. +---@param types string[] Target types to find. +---@param enemyPlayers player[] Players who will have their actors targeted. +---@param filter? fun(a: actor):boolean Filter applied to actors after type and owner. +---@param onNewTarget fun(harasser: actor, target: actor) Called when a new target is selected. +---@param onFinished? fun(harasser: actor) Called once no more targets can be found. +local function GroupHarass(harassers, types, enemyPlayers, filter, onNewTarget, onFinished) + local targets + local targetId = 1 + + Utils.Do(harassers, function(harasser) + IdleHarass(harasser, types, enemyPlayers, filter, onNewTarget, onFinished) + + if not harasser.IsInWorld then + return + end + + targets = targets or TargetsByDistance(harasser.CenterPosition, types, enemyPlayers, filter) + + if targets[targetId] then + onNewTarget(harasser, targets[targetId]) + targetId = targetId + 1 + end + end) +end + +---@param plane actor Plane that will attack. +---@param types string[] Enemy types to attack, ordered by high to low priority. +---@param enemyPlayer player Owner of the enemy types. +---@return actor +local function NewPlaneTarget(plane, types, enemyPlayer) + local enemies = enemyPlayer.GetActorsByTypes(types) + local target + + Utils.Do(types, function(type) + if target then + return + end + + local matches = Utils.Where(enemies, function(enemy) + return enemy.Type == type and plane.CanTarget(enemy) end) + + if #matches > 0 then + target = Utils.Random(matches) + end + end) + + return target +end + +---@param plane actor Plane that will attack. +---@param types string[] Enemy types to attack, ordered by high to low priority. +---@param enemyPlayer player Owner of the enemy types. +local function IdlePlaneHarass(plane, types, enemyPlayer) + local target + + Trigger.OnIdle(plane, function() + local ammoStarved = plane.AmmoCount() == 0 and not plane.Owner.HasPrerequisites({ "afld" }) + + if ammoStarved then + plane.Move(Waypoint64.Location) + plane.Destroy() + return + end + + target = target or NewPlaneTarget(plane, types, enemyPlayer) + + if not target or target.IsDead then + plane.ReturnToBase() + target = nil + return + end + + -- Ensure vision for targeting. + SpawnMiscActor("camera.spotter", plane.Owner, target.Location, 3) + + Trigger.AfterDelay(1, function() + if not target or target.IsDead or plane.IsDead or not plane.CanTarget(target) then + target = nil + return + end + + plane.Attack(target) + end) + end) +end + +---@param types string[] Plane types to spawn. +---@param targetTypes string[] Enemy types to attack, ordered by high to low priority. +local function SendAlliedPlanes(types, targetTypes) + if not Greece.HasPrerequisites({ "afld"}) then + return + end + + -- The RA1 planes were set to reinforce from Waypoint 95 but because that + -- sat on a land tile, the edge directly south was (and is) used instead. + local path = { AlliedPlaneEntry.Location } + local planes = Reinforcements.Reinforce(Greece, types, path) + + Utils.Do(planes, function(plane) + IdlePlaneHarass(plane, targetTypes, USSR) + Trigger.OnCapture(plane, Trigger.ClearAll) + end) + + if Difficulty ~= "hard" then + return + end + + local delay = Actor.BuildTime(planes[1].Type) * 2 * #types + + Trigger.OnAllKilled(planes, function() + Trigger.AfterDelay(delay, function() + SendAlliedPlanes(types, targetTypes) + end) + end) +end + +--- Reinforce a pair of Yak planes, based on RA1 team "air1". +local function SendYaks() + local priorityList = { "4tnk", "3tnk", "silo", "harv" } + SendAlliedPlanes({ "yak", "yak" }, priorityList) +end + +--- Reinforce a MiG bomber, based on RA1 team "air2". +local function SendJet() + local priorityList = { "4tnk", "3tnk", "powr", "silo", "v2rl", "mnly", "harv" } + SendAlliedPlanes({ "mig" }, priorityList) +end + +local function ReinforceLongbows() + local types = { "heli", "heli", "heli", "heli" } + -- This offset arranges them in a spread formation before the attack order. + local offset = CVec.New(-5, 10) + local path = { Waypoint94.Location, Waypoint94.Location + offset } + + return Reinforcements.Reinforce(Greece, types, path, 20) +end + +--- Prepare an area trigger that will dispose of retreating Longbows. +--- Their repulsion trait makes the usual Move + Destroy orders unreliable. +---@param units actor[] +local function PrepareLongbowDisposal(units) + local disposal = Trigger.OnEnteredProximityTrigger(AlliedHeliDisposal.CenterPosition, WDist.FromCells(2), function(actor) + if actor.Type ~= "heli" then + return + end + + actor.Stop() + actor.Destroy() + end) + + Trigger.OnAllRemovedFromWorld(units, function() + Trigger.RemoveProximityTrigger(disposal) + end) +end + +--- Send a Longbow team, based on the RA1 trigger "ent1". +--- The starting base is the normal target, but the depot is possible. +local function SendLongbows() + if CruisersKilled then + return + end + + local targetTypes = { "ftur", "tsla", "apwr", "sam" } + local longbows = ReinforceLongbows() + PrepareLongbowDisposal(longbows) + + OnAllAddedToWorld(longbows, function() + local targets = TargetsByDistance(Waypoint94.CenterPosition, targetTypes, SovietPlayers, IsEastOfCenterRidge) + + -- Ensure vision for targeting. + SpawnMiscActor("camera", Greece, Waypoint67.Location, DateTime.Seconds(1)) + + Utils.Do(longbows, function(longbow) + if longbow.IsDead then + return + end + + longbow.Stop() + local samFound = false + + Utils.Do(targets, function(target) + if samFound or target.Type == "sam" then + samFound = true + return + end + + longbow.Attack(target) + end) + + Trigger.OnIdle(longbow, function() + longbow.Move(AlliedHeliDisposal.Location) + end) + end) + end) +end + +--- Reveal the tech center to nearby Cruisers to ensure targeting. +---@param cruiser actor +local function OnCruiserReachedTech(cruiser) + if TechCenter.IsDead then + return + end + + local camera = SpawnMiscActor("camera", Greece, Waypoint89.Location) + Trigger.OnKilled(cruiser, camera.Destroy) +end + +local function OnAllCruisersKilled() + CruisersKilled = true + local attackers = Greece.GetGroundAttackers() + local navyPath = { EndBoatPatrol1.Location, EndBoatPatrol2.Location, EndBoatPatrol3.Location, EndBoatPatrol4.Location } + + if #attackers == 0 then + -- Done in RA1 when Greece lost all current infantry & ground vehicles. + StartFireSale(Greece) + return + end + + Utils.Do(attackers, function(a) + if a.Type == "dd" or a.Type == "dd.escort" then + a.Patrol(navyPath, true) + return + end + + -- Mostly for any remaining east edge tanks. + IdleHunt(a) + end) + + Trigger.OnAllKilled(attackers, function() + StartFireSale(Greece) + end) +end + +--- Send ships ahead of the Cruisers, based on RA1 trigger "des2". +local function SendCruiserEscorts() + local types = { "dd.escort", "dd.escort" } + local path = { Waypoint90.Location, Waypoint83.Location } + + Reinforcements.Reinforce(Greece, types, path, DateTime.Seconds(1), function(escort) + Trigger.OnIdle(escort, function() + escort.Stance = "AttackAnything" + local cruisers = Greece.GetActorsByType("ca") + + if #cruisers == 0 then + escort.Hunt() + return + end + + Utils.Do(cruisers, escort.Guard) + end) + end) +end + +--- Send in the Allies' Cruisers, based on trigger "crus". +local function SendCruisers() + if not FirstAirfieldCaptured then + USSR.MarkFailedObjective(AirfieldObjective) + end + + Trigger.AfterDelay(DateTime.Seconds(9), SendCruiserEscorts) + PlayDelayedSpeech("AlliedForcesApproaching", 20) + Media.DisplayMessage(UserInterface.GetFluentMessage("unauthorized-naval-units"), BattlefieldControl, USSR.Color) + + Trigger.AfterDelay(DateTime.Seconds(4), function() + Media.DisplayMessage(UserInterface.GetFluentMessage("protect-our-tech-center-at-all-costs"), BattlefieldControl, USSR.Color) + end) + + local path = + { + Waypoint90.Location, + Waypoint85.Location, + Waypoint86.Location, + Waypoint84.Location, + Waypoint73.Location, + Waypoint72.Location, + Waypoint71.Location + } + + local cruisers = Reinforcements.Reinforce(Greece, { "ca", "ca" }, path, DateTime.Seconds(2)) + Trigger.OnAllKilled(cruisers, OnAllCruisersKilled) + SpawnRevealCamera(cruisers[1].Location) + + Utils.Do(cruisers, function(cruiser) + Trigger.OnIdle(cruiser, function() + if cruiser.Location == path[#path] then + Trigger.Clear(cruiser, "OnIdle") + return + end + + cruiser.Move(path[#path]) + end) + + Trigger.OnEnteredProximityTrigger(Waypoint72.CenterPosition, WDist.FromCells(3), function(actor, id) + if actor ~= cruiser then + return + end + + Trigger.RemoveProximityTrigger(id) + OnCruiserReachedTech(cruiser) + end) + end) + + Trigger.AfterDelay(DateTime.Seconds(6), function() + CruisersArrived = true + end) +end + +--- Send tanks to guard a path between the refineries and vehicle depot. +--- The original team was "end4" from trigger "des3". +local function SendEastEdgeTanks() + local path = { Waypoint68.Location, Waypoint69.Location } + local types = { "2tnk.widescan", "2tnk.widescan", "2tnk.widescan" } + local passengers = Reinforcements.ReinforceWithTransport(Greece, "lst", types, path, { path[1] })[2] + + Utils.Do(passengers, function(p) + Trigger.OnAddedToWorld(p, function() + p.AttackMove(Waypoint63.Location, 2) + end) + end) +end + +---@return actor[] +local function NavalWallTargets() + local sort = table.sort + local nw, se = NavalRocketScanWest.CenterPosition, NavalRocketScanEast.CenterPosition + local walls = Map.ActorsInBox(nw, se, IsWall) + + -- Ensure our target list is ordered from west to east. + sort(walls, function(a, b) + return a.Location.X < b.Location.X + end) + + return walls +end + +--- Unload rockets to attack the Soviet pen, based on RA1's "ent4" trigger. +--- They can't outrange towers like RA1; a wall breach is attempted on Hard. +local function SendNavalRockets(types, path) + if not NavalBaseFound then + -- Cruisers are halfway down the river and the player seems + -- to be running late. Delay this attack as a small courtesy. + Trigger.OnObjectiveCompleted(USSR, function(_, id) + if id ~= PenObjective then + return + end + + SendNavalRockets(types, path) + end) + + return + end + + local rockets = Reinforcements.ReinforceWithTransport(Greece, "lst", types, path, { path[1] })[2] + + if Difficulty ~= "hard" then + Utils.Do(rockets, IdleHunt) + return + end + + local walls = NavalWallTargets() + + Utils.Do(rockets, function(rocket) + Trigger.OnAddedToWorld(rocket, function() + rocket.AttackMove(NavalRocketScanWest.Location, 4) + + Utils.Do(walls, function(wall) + rocket.Attack(wall) + rocket.AttackMove(wall.Location, 1) + end) + + rocket.AttackMove(Waypoint52.Location, 2) + IdleHunt(rocket) + end) + end) +end + +local function ScheduleEndTransports() + local events = + { + { + teams = { EndTransportTeams.sea7, EndTransportTeams.sea8 }, + delay = EndTeamDelays[Difficulty].sea7_sea8 + }, + { + teams = { EndTransportTeams.end1 }, + delay = EndTeamDelays[Difficulty].end1, + teamFunc = SendNavalRockets, + }, + { + teams = { EndTransportTeams.end2, EndTransportTeams.end5 }, + delay = EndTeamDelays[Difficulty].end2_end5 + } + } + + Utils.Do(events, function(event) + local teamFunc = event.teamFunc or ReinforceBasicBoatTeam + + Trigger.AfterDelay(event.delay, function() + if CruisersKilled then + return + end + + Utils.Do(event.teams, function(team) + teamFunc(team.types, team.path) + end) + end) + end) +end + +--- The Soviet naval base has died, possibly while still in shroud. +--- Under normal circumstances, it should remain intact until regained. +local function OnNavalBaseKilled() + if VictoryStarted or NavalBaseFound then + return + end + + SpawnRevealCamera(Waypoint52.Location, -1) + AnnounceSovietDefeat(PenObjective, DateTime.Seconds(2), "ObjectiveNotMet", DateTime.Seconds(1)) + Media.DisplayMessage(UserInterface.GetFluentMessage("naval-base-destroyed"), BattlefieldControl, USSR.Color) + Media.PlaySoundNotification(USSR, "AlertBleep") +end + +local function OnStartAttackersRemoved() + if DefeatStarted then + return + end + + Trigger.AfterDelay(DateTime.Seconds(4), function() + Media.DisplayMessage(UserInterface.GetFluentMessage("scouts-mark-the-way-to-bases"), BattlefieldControl, USSR.Color) + end) + + Trigger.AfterDelay(DateTime.Seconds(8), function() + Media.PlaySpeechNotification(USSR, "SignalFlare") + Beacon.New(USSR, Waypoint0.CenterPosition) + end) +end + +local function OnTechCenterLost() + SpawnRevealCamera(Waypoint89.Location, -1) + AnnounceSovietDefeat(TechObjective, DateTime.Seconds(2), "ObjectiveNotMet", DateTime.Seconds(1)) + Media.PlaySoundNotification(USSR, "AlertBleep") + + if TechCenter.IsDead then + Media.DisplayMessage(UserInterface.GetFluentMessage("our-tech-center-destroyed"), BattlefieldControl, USSR.Color) + return + end + + Media.DisplayMessage(UserInterface.GetFluentMessage("our-tech-center-captured"), BattlefieldControl, USSR.Color) + + Trigger.AfterDelay(DateTime.Seconds(2), function() + if TechCenter.IsInWorld then + TechCenter.Sell() + end + end) +end + +---@param actors actor[] +local function RegainBase(actors) + Utils.Do(actors, function(actor) + if actor.IsDead then + return + end + + actor.Owner = USSR + + if actor.Type == "harv" then + actor.FindResources() + end + end) +end + +local function OnMineBaseReached() + local trucks = { MineTruck1, MineTruck2, MineTruck3 } + local structures = { MineRefinery1, MineRefinery2, MineRefinery3, MinePower1, MinePower2, MinePower3, MinePower4, MineSilo1, MineSilo2, MineSilo3, MineSilo4, MineSilo5, MineSilo6, MineSilo7, MineSilo8 } + RegainBase(structures) + RegainBase(trucks) + + Media.DisplayMessage(UserInterface.GetFluentMessage("mining-outpost-reached"), BattlefieldControl, USSR.Color) + + Trigger.AfterDelay(DateTime.Seconds(4), function() + Media.PlaySpeechNotification(USSR, "SignalFlareNorth") + Beacon.New(USSR, Waypoint1.CenterPosition) + end) + + if Difficulty == "hard" then + Trigger.AfterDelay(DateTime.Seconds(60), SendYaks) + Trigger.AfterDelay(DateTime.Seconds(120), SendJet) + return + end + + if Difficulty == "easy" then + local proxy = Actor.Create("powerproxy.paratroopers", false, { Owner = USSR }) + proxy.TargetParatroopers(Waypoint55.CenterPosition, Angle.New(896)) + end +end + +local function OnServiceBaseReached() + local base = { ServiceDepot, ServiceFactory, ServiceFlame1, ServiceFlame2, ServiceFlame3, ServiceFlame4, ServicePower1, ServicePower2, ServiceCommand, ServiceEngineer1, ServiceEngineer2 } + RegainBase(base) + Media.DisplayMessage(UserInterface.GetFluentMessage("vehicle-depot-reached"), BattlefieldControl, USSR.Color) + SpawnRevealCamera(Waypoint1.Location) + + Trigger.AfterDelay(DateTime.Seconds(4), function() + Media.PlaySpeechNotification(USSR, "SignalFlareWest") + Beacon.New(USSR, Waypoint2.CenterPosition) + AirfieldObjective = AirfieldObjective or AddSecondaryObjective(USSR, "capture-airfield-before-cruisers") + end) + + ScheduleEndTransports() + Trigger.AfterDelay(EndTeamDelays[Difficulty].cruisers, SendCruisers) + Trigger.AfterDelay(EndTeamDelays[Difficulty].longbows, SendLongbows) + Trigger.AfterDelay(DateTime.Seconds(60), SendTechGuards) + + Trigger.AfterDelay(DateTime.Seconds(20), function() + StartCountdown(EndTeamDelays[Difficulty].cruisers - DateTime.Seconds(20)) + end) + + -- On Hard, planes will already be triggered from the mining base. + if Difficulty == "hard" then + return + end + + Trigger.AfterDelay(DateTime.Seconds(60), SendYaks) + Trigger.AfterDelay(DateTime.Seconds(120), SendJet) + + if Difficulty == "easy" and not ServiceFactory.IsDead then + local tankType = "4tnk" + ServiceFactory.Produce(tankType) + + Trigger.AfterDelay(1, function() + RegainBase(BadGuy.GetActorsByType(tankType)) + end) + end +end + +local function OnAirbaseReached() + SpawnRevealCamera(Waypoint2.Location) + + if NavalBaseFound then + return + end + + Media.DisplayMessage(UserInterface.GetFluentMessage("airfields-reached"), BattlefieldControl, USSR.Color) + + Trigger.AfterDelay(DateTime.Seconds(4), function() + Media.PlaySpeechNotification(USSR, "SignalFlareSouth") + Beacon.New(USSR, Waypoint3.CenterPosition) + end) +end + +local function OnAirfieldCaptured() + if FirstAirfieldCaptured or DateTime.TimeLimit == 0 then + return + end + + FirstAirfieldCaptured = true + AirfieldObjective = AirfieldObjective or AddSecondaryObjective(USSR, "capture-airfield-before-cruisers") + USSR.MarkCompletedObjective(AirfieldObjective) + Trigger.AfterDelay(DateTime.Seconds(1), ReinforceCrateParatroopers) +end + +--- The player has regained the Sub Pen, by approaching the +--- gate or otherwise discovering the production structures. +local function OnNavalBaseFound() + if VictoryStarted or NavalBaseFound then + return + end + + NavalBaseFound = true + USSR.MarkCompletedObjective(PenObjective) + Media.PlaySpeechNotification(USSR, "NewOptions") + local structures = { NavalPower1, NavalPower2, NavalFlame1, NavalFlame2, NavalConstructionYard, NavalPen } + RegainBase(structures) + SpawnRevealCamera(Waypoint52.Location) + + Trigger.AfterDelay(DateTime.Seconds(4), function() + if not VictoryStarted then + IntruderObjective = AddPrimaryObjective(USSR, "wipe-out-intruders-with-new-submarines") + end + end) +end + +--- Prepare the chain of Soviet signal flares and their triggers. +--- Each trigger spawns the next flare and schedules the current one's removal. +local function PrepareSovietSignals() + local signals = + { + { + action = OnMineBaseReached, + location = Waypoint0.Location, + radius = WDist.FromCells(3) + }, + { + action = OnServiceBaseReached, + location = Waypoint1.Location, + radius = WDist.FromCells(11) + }, + { + action = OnAirbaseReached, + location = Waypoint2.Location, + radius = WDist.FromCells(6) + }, + { + location = Waypoint3.Location, + radius = WDist.FromCells(3) + } + } + + for i = 1, #signals do + local activated = false + + Trigger.OnEnteredProximityTrigger(Map.CenterOfCell(signals[i].location), signals[i].radius, function(actor, id) + if activated or not IsMobileSoviet(actor) then + return + end + + activated = true + Trigger.RemoveProximityTrigger(id) + RemoveActor(signals[i].smoke, DateTime.Minutes(2)) + + if signals[i].action then + signals[i].action() + end + + if signals[i + 1] and not NavalBaseFound then + signals[i + 1].smoke = SpawnSignalFlare(signals[i + 1].location) + end + end) + end + + signals[1].smoke = SpawnSignalFlare(Waypoint0.Location) +end + +--- Send some engineers to capture the Tech Center if playing Hard. +--- This is a nod to the "end3" team unused in the RA1 mission. +---@return actor[] +local function SendSaboteurs() + if Difficulty ~= "hard" then + return { } + end + + local cargoTypes = { "e6", "e6", "e1", "e3", "e3" } + local entryPath = { Waypoint66.Location, CPos.New(56, 92), Waypoint87.Location } + local apc = Reinforcements.ReinforceWithTransport(Greece, "apc", cargoTypes, entryPath)[1] + local captureTypes = { "stek", "iron" } + + Utils.Do(apc.Passengers, function(passenger) + Trigger.OnAddedToWorld(passenger, function() + if not passenger.HasProperty("Capture") then + IdleHunt(passenger) + return + end + + passenger.Move(Waypoint89.Location, 2) + + IdleHarass(passenger, captureTypes, SovietPlayers, nil, function(harasser, target) + harasser.Capture(target) + end) + end) + end) + + IdleHunt(apc) + return apc.Passengers +end + +local function OrderStartAttackers() + local saboteurs = SendSaboteurs() + local rockets = { StartAttacker1, StartAttacker2, StartAttacker3 } + + Utils.Do(rockets, function(a) + -- Maintain their spread formation as they approach. + local goal = a.Location + CVec.New(0, 6) + a.AttackMove(goal, 2) + IdleHunt(a) + end) + + local attackers = Utils.Concat(saboteurs, rockets) + Trigger.OnAllRemovedFromWorld(attackers, OnStartAttackersRemoved) +end + +---@param actor actor Actor that must survive for the speech to play. +---@param sound string Sound notification to play. +---@param delay integer Ticks until the speech plays. +---@param camera? string Camera type to reveal Tanya as she speaks. +local function TauntWithTanya(actor, sound, delay, camera) + Trigger.AfterDelay(delay, function() + if actor.IsDead then + return + end + + if camera then + SpawnRevealCamera(actor.Location, DateTime.Seconds(3), camera) + end + + Media.PlaySpeechNotification(USSR, sound) + end) +end + +--- Arrange extraction for Tanya once her sabotage is complete. +---@param tanya actor +local function ExtractTanya(tanya) + local path = { Waypoint93.Location, TanyaBackupUnload.Location } + local boat = Reinforcements.Reinforce(Greece, { "lst" }, path)[1] + tanya.Move(TanyaBackupUnload.Location) + + Trigger.OnIdle(boat, function() + if tanya.IsDead or boat.HasPassengers then + boat.Move(path[1]) + boat.Destroy() + return + end + + if tanya.IsIdle then + tanya.EnterTransport(boat) + end + end) + + Trigger.OnPassengerEntered(boat, function() + TauntWithTanya(boat, "TanyaLetsRock", 15, "camera.small") + end) + + Trigger.OnIdle(tanya, function() + if boat.IsDead then + tanya.Hunt() + end + end) +end + +local function ReinforceTanyaBackup() + local path = { Waypoint68.Location, TanyaBackupUnload.Location } + local exit = { Waypoint94.Location } + local types = { "e3.vehiclehunter", "e3.vehiclehunter", "e3.vehiclehunter", "e1", "2tnk.widescan" } + local boatTeam = Reinforcements.ReinforceWithTransport(Greece, "lst", types, path, exit) + local boat, cargo = boatTeam[1], boatTeam[2] + PrepareTransportReveal(boat, path[#path], DateTime.Seconds(1), "camera.small") + + return cargo +end + +local function SendTanyaBackup() + if Difficulty ~= "hard" then + return + end + + local units = ReinforceTanyaBackup() + local attacking = false + local guardGoal = Waypoint55.Location + CVec.New(0, 2) + + OnAnyDamaged(units, function() + attacking = true + end) + + Utils.Do(units, function(unit) + Trigger.OnAddedToWorld(unit, function() + -- Ignore structures for now and leave them to Tanya. + unit.Stance = "Defend" + unit.AttackMove(guardGoal, 2) + end) + + Trigger.OnIdle(unit, function() + if not attacking then + return + end + + if unit.Location ~= Waypoint0.Location then + unit.AttackMove(Waypoint0.Location) + return + end + + unit.Stance = "AttackAnything" + unit.Hunt() + end) + end) +end + +--- Reinforce Tanya by boat, along with any backup she may have. +---@return actor +local function ReinforceTanya() + local path = { Waypoint93.Location, Waypoint92.Location } + local cargo = { "e7" } + local boatTeam = Reinforcements.ReinforceWithTransport(Greece, "lst", cargo, path, { path[1] }) + local boat, tanya = boatTeam[1], boatTeam[2][1] + PrepareTransportReveal(boat, path[#path], DateTime.Seconds(3), "camera", WDist.FromCells(4)) + Trigger.OnAddedToWorld(tanya, SendTanyaBackup) + + return tanya +end + +--- Begin Tanya's demolition spree. She will attempt extraction when done. +local function SendTanya() + if TanyaReinforced then + return + end + + TanyaReinforced = true + local tanya = ReinforceTanya() + local demolitionTypes = { "proc", "powr", "silo" } + local lastTarget + + local onNewTarget = function(demolisher, target) + -- Guard against a re-demolish, which may happen if the target + -- list refreshes as a bomb timer is still ticking down. + if target == lastTarget then + return + end + + demolisher.Demolish(target) + lastTarget = target + end + + Trigger.OnAddedToWorld(tanya, function() + TauntWithTanya(tanya, "TanyaLaugh", 1) + TauntWithTanya(tanya, "TanyaGiveItToMe", DateTime.Seconds(6)) + tanya.Move(Waypoint77.Location) + tanya.Move(TanyaDemolishStart.Location) + IdleHarass(tanya, demolitionTypes, SovietPlayers, IsEastOfCenterRidge, onNewTarget, ExtractTanya) + end) + + -- Tanya's unload cell from the boat is randomized. Since that affects + -- her travel time, this will be more consistent than a delay. + Trigger.OnEnteredProximityTrigger(TanyaDemolishStart.CenterPosition, WDist.New(1536), function(actor, id) + if actor ~= tanya then + return + end + + Trigger.RemoveProximityTrigger(id) + TauntWithTanya(actor, "TanyaKissItByeBye", 1, "camera.small") + end) +end + +--- Airlift thieves to the mining base, based on RA1 trigger "ent3". +local function SendThieves() + local thiefTypes = { "thf", "thf" } + if Difficulty == "hard" then + thiefTypes = { "thf", "thf", "thf", "thf" } + end + + local path = { Waypoint68.Location, Waypoint55.Location } + local thieves = Reinforcements.ReinforceWithTransport(Greece, "tran", thiefTypes, path, { path[1] })[2] + local stealTypes = { "proc", "silo" } + + local onNewTarget = function(thief, target) + thief.Infiltrate(target) + end + + OnAllAddedToWorld(thieves, function() + GroupHarass(thieves, stealTypes, SovietPlayers, IsEastOfCenterRidge, onNewTarget) + end) +end + +--- Send vehicles to help the beach runner, and prepare Tanya's arrival. +--- The original team was "sea4" from trigger "atk2". +local function SendBeachRescue() + local path = { Waypoint94.Location, Waypoint95.Location } + local cargoTypes = { "1tnk", "arty", "arty" } + if Difficulty == "hard" then + cargoTypes = { "2tnk", "2tnk", "e3", "e3", "arty" } + end + + local boat, cargo = ReinforceBasicBoatTeam(cargoTypes, path) + PrepareTransportReveal(boat, Waypoint74.Location, DateTime.Seconds(1), "camera.small", WDist.FromCells(15)) + + Trigger.OnAllKilled(cargo, function() + SendTanya() + IdleHunt(BeachRunner) + end) +end + +--- Order a soldier to run for help, based on RA1 trigger "atk6". +local function OrderBeachRunner() + if BeachRunner.IsDead then + return + end + + SpawnRevealCamera(BeachRunner.Location) + BeachRunner.Stop() + BeachRunner.Move(RidgeRunPoint1.Location) + BeachRunner.Move(RidgeRunPoint2.Location) + BeachRunner.Move(Waypoint74.Location) + + Trigger.OnIdle(BeachRunner, function() + if BeachRunner.Location == Waypoint74.Location then + Trigger.Clear(BeachRunner, "OnIdle") + BeachRunner.Stance = "AttackAnything" + return + end + + BeachRunner.Move(Waypoint74.Location) + end) +end + +local function PrepareBeachRunner() + local team = { BeachRunnerRocket1, BeachRunnerRocket2, BeachRunner } + local alerted = false + + Trigger.OnEnteredFootprint(Footprints.atk6, function(actor, id) + if alerted or not IsMobileSoviet(actor) then + return + end + + alerted = true + OrderBeachRunner() + Trigger.RemoveFootprintTrigger(id) + end) + + OnAnyDamaged(team, function() + if alerted then + return + end + + alerted = true + OrderBeachRunner() + end) + + Trigger.OnEnteredFootprint(Footprints.atk2, function(actor, id) + if actor ~= BeachRunner then + return + end + + Trigger.RemoveFootprintTrigger(id) + SendBeachRescue() + end) +end + +--- Send some APC troops for a flank attack at the north edge, based on "ent3". +local function SendLakeAmbushers() + local path = { Waypoint57.Location, Waypoint56.Location } + local cargoTypes = { "e3", "e3", "e3", "e3", "e3" } + local apc = Reinforcements.ReinforceWithTransport(Greece, "apc", cargoTypes, path)[1] + + Trigger.OnPassengerExited(apc, function(_, passenger) + IdleHunt(passenger) + end) + + IdleHunt(apc) +end + +--- Unload some mining base harassers, based on RA1 trigger "atk8". +--- Hard adds the intended but bugged "sea6" team, plus an Artillery unit. +local function SendEastEdgeRockets() + local path = { Waypoint68.Location, Waypoint70.Location } + local cargoTypes = { "e3.vehiclehunter", "e3.vehiclehunter", "e3.vehiclehunter", "e3.vehiclehunter", "e3.vehiclehunter" } + local boat = ReinforceBasicBoatTeam(cargoTypes, path) + PrepareTransportReveal(boat, Waypoint70.Location, DateTime.Seconds(1), "camera.small") + + if Difficulty ~= "hard" then + return + end + + local extras = { "e3.vehiclehunter", "e3.vehiclehunter", "e3.vehiclehunter", "arty" } + local extraPath = { Waypoint68.Location, Waypoint69.Location } + + Trigger.AfterDelay(10, function() + ReinforceBasicBoatTeam(extras, extraPath) + end) +end + +local function PrepareBasicFootprints() + local triggers = + { + { + cells = Footprints.atk7, + actions = { SendEastEdgeRockets } + }, + { + cells = Footprints.des3, + actions = { SendEastEdgeTanks } + }, + { + cells = Footprints.ent3, + actions = { SendLakeAmbushers, SendThieves } + }, + { + cells = Footprints.tanya, + actions = { SendTanya } + } + } + + Utils.Do(triggers, function(trigger) + local activated = false + + Trigger.OnEnteredFootprint(trigger.cells, function(actor, id) + if activated or not IsMobileSoviet(actor) then + return + end + + activated = true + Trigger.RemoveFootprintTrigger(id) + + Utils.Do(trigger.actions, function(action) + action() + end) + end) + end) +end + +local function PrepareCruiserReveals() + local triggers = + { + { cells = Footprints.rev4, reveal = Waypoint84.Location }, + { cells = Footprints.rev3, reveal = Waypoint73.Location }, + { cells = Footprints.ent4, reveal = Waypoint76.Location }, + { cells = Footprints.ent5, reveal = Waypoint53.Location }, + { cells = Footprints.ent6, reveal = Waypoint72.Location } + } + + Utils.Do(triggers, function(trigger) + Trigger.OnEnteredFootprint(trigger.cells, function(actor, id) + if not IsCruiser(actor) then + return + end + + Trigger.RemoveFootprintTrigger(id) + SpawnRevealCamera(trigger.reveal) + end) + end) +end + +--- Easy will swap out the end teams' artillery. In RA1, Tesla Coils +--- outranged Allied artillery with ease, but the same does not apply here. +local function SwapEndArtillery() + if Difficulty ~= "easy" then + return + end + + Utils.Do(EndTransportTeams, function(team) + for i = 1, #team.types do + if team.types[i] == "arty" then + team.types[i] = "1tnk" + end + end + end) +end + +WorldLoaded = function() + PrepareSovietSignals() + PrepareCruiserReveals() + PrepareBasicFootprints() + PrepareBeachRunner() + OrderStartAttackers() + SwapEndArtillery() + + Camera.Position = TechCoil1.CenterPosition + USSR.Cash = 500 + BadGuy.Cash = 5000 + + InitObjectives(USSR) + TechObjective = AddPrimaryObjective(USSR, "tech-center-survive") + PenObjective = AddPrimaryObjective(USSR, "regain-control-of-our-naval-base") + + Trigger.OnKilledOrCaptured(TechCenter, OnTechCenterLost) + Trigger.OnAllKilled({ NavalConstructionYard, NavalPen }, OnNavalBaseKilled) + Trigger.OnDiscovered(NavalConstructionYard, OnNavalBaseFound) + Trigger.OnDiscovered(NavalPen, OnNavalBaseFound) + + -- Fallback trigger that lets the player continue if using the + -- visibility cheat; OnDiscovered events will be blocked. + Trigger.OnEnteredProximityTrigger(NavalConstructionYard.CenterPosition, WDist.FromCells(4), function(actor, id) + if not IsMobileSoviet(actor) then + return + end + + Trigger.RemoveProximityTrigger(id) + OnNavalBaseFound() + end) + + Trigger.AfterDelay(DateTime.Seconds(6), function() + -- Ownership is delayed to help avoid the speech muddling others. + RegainBase({ IronCurtain }) + end) + + Utils.Do(Greece.GetActorsByType("afld"), function(field) + Trigger.OnCapture(field, OnAirfieldCaptured) + end) +end + +Tick = function() + if CruisersArrived and TechCenter.IsInWorld and Greece.HasNoRequiredUnits() then + AnnounceSovietVictory() + end +end diff --git a/mods/ra/maps/proving-grounds/rules.yaml b/mods/ra/maps/proving-grounds/rules.yaml new file mode 100644 index 0000000000..9fa7bf6649 --- /dev/null +++ b/mods/ra/maps/proving-grounds/rules.yaml @@ -0,0 +1,239 @@ +World: + LuaScript: + Scripts: campaign.lua, utils.lua, proving-grounds.lua + MissionData: + WinVideo: sovtstar.vqa + LossVideo: allymorf.vqa + Briefing: Information on our new Advanced Tactical Submarine was leaked to the Allies and they have invaded our territory, determined to stop us. You must destroy them.\n\nRegain control of our bases as they are revealed by the signal flares. Do not let any Allied attacks stop you.\n\nWe have received word that a detachment of cruisers is on its way here. Once you recover the sub pen, use the new ATS to prevent the cruisers from destroying our operation. + ScriptLobbyDropdown@difficulty: + ID: difficulty + Label: dropdown-difficulty.label + Description: dropdown-difficulty.description + Values: + easy: options-difficulty.easy + normal: options-difficulty.normal + hard: options-difficulty.hard + Default: normal + TimeLimitManager: + SkipTimerExpiredNotification: true + +CAMERA.Small: + Inherits: CAMERA + RevealsShroud: + Range: 4c0 + +CAMERA.Spotter: + Inherits: CAMERA + RevealsShroud: + Range: 0c512 + +powerproxy.paratroopers: + ParatroopersPower: + DropItems: E1,E1,E1,E2,E2 + DisplayBeacon: false + ReinforcementsArrivedSpeechNotification: ReinforcementsArrived + ReinforcementsArrivedTextNotification: notification-reinforcements-have-arrived + KillsSelf: + RemoveInstead: true + Delay: 5 + +powerproxy.flametroopers: + Inherits: powerproxy.paratroopers + ParatroopersPower: + DropItems: E1,E1,E1,E4,E1,E1,E1,E4 + SquadSize: 2 + +# Prevents accidental crate collection by Allied soldiers. +crateblocker: + Immobile: + OccupiesSpace: true + Crushable: + WarnProbability: 0 + CrushClasses: crate + CrushedByFriendlies: false + +HEALCRATE: + FreeActor: + Actor: crateblocker + +MONEYCRATE: + FreeActor: + Actor: crateblocker + +E1.WideScan: + Inherits: E1 + Buildable: + Prerequisites: ~disabled + AutoTarget: + ScanRadius: 7 + RenderSprites: + Image: E1 + +E3.WideScan: + Inherits: E3 + RevealsShroud: + Range: 7c0 + AutoTarget: + ScanRadius: 7 + RenderSprites: + Image: E3 + +E3.VehicleHunter: + Inherits: E3 + AutoTargetPriority@VEHICLES: + Priority: 5 + ValidTargets: Vehicle + RenderSprites: + Image: E3 + +E6: + Captures@QUICKAFLD: + CaptureTypes: airfield + PlayerExperience: 10 + CaptureDelay: 100 + +E7: + AutoTarget: + InitialStanceAI: ReturnFire + +APC.TechGuard: + Inherits: APC + Cargo: + InitialUnits: e1,e1,e2,e4,e4 + RenderSprites: + Image: APC + +1TNK.WideScan: + Inherits: 1TNK + AutoTarget: + ScanRadius: 7 + RenderSprites: + Image: 1TNK + +2TNK.WideScan: + Inherits: 2TNK + AutoTarget: + ScanRadius: 7 + RenderSprites: + Image: 2TNK + +AFLD: + AirstrikePower@parabombs: + Prerequisites: ~disabled + ParatroopersPower@paratroopers: + DropItems: E1,E1,E1,E2,E2 + Capturable@QUICKAFLD: + Types: airfield + Buildable: + Prerequisites: ~disabled + +FACT: + AnnounceOnSeen: + +IRON: + Buildable: + Prerequisites: ~disabled + GrantExternalConditionPower@IRONCURTAIN: + ChargeInterval: 6000 + +SPEN: + AnnounceOnSeen: + +STEK: + Buildable: + Prerequisites: ~disabled + GrantConditionOnBotOwner@BOTCAPTURED: + Condition: bot-captured + Bots: campaign + Sellable: + RequiresCondition: bot-captured && !being-demolished && !being-captured + +HARV: + GrantConditionOnBotOwner@BADGUY: + Condition: unclaimed + Bots: campaign + Targetable: + RequiresCondition: !unclaimed + Mobile: + PauseOnCondition: unclaimed || being-captured + Harvester: + RequiresCondition: !unclaimed + +CA: + RevealsShroud: + Range: 11c0 + Buildable: + Prerequisites: ~disabled + Mobile: + Speed: 44 + +DD.Escort: + Inherits: DD + Buildable: + Prerequisites: ~disabled + RevealsShroud: + Range: 9c0 + AutoTarget: + ScanRadius: 8 + InitialStanceAI: Defend + RenderSprites: + Image: DD + +LST.Reinforcement: + Inherits: LST + RejectsOrders: + -Buildable: + -Selectable: + RenderSprites: + Image: lst + Interactable: + +SS: + Armament: + Weapon: TorpTubeNuclear + AutoTarget: + InitialStance: HoldFire + Buildable: + Prerequisites: ~spen, ~stek + Tooltip: + Name: actor-ats-name + WithProductionIconOverlay: + Types: Veterancy + FirepowerMultiplier: + Modifier: 15 + +HELI: + Aircraft: + NumberOfTicksToVerifyAvailableAirport: 25 + +MSLO: + Buildable: + Prerequisites: ~disabled + +E3: + Buildable: + Prerequisites: ~disabled + +THF: + Buildable: + Prerequisites: ~disabled + +APC: + Buildable: + Prerequisites: ~disabled + +FTRK: + Buildable: + Prerequisites: ~disabled + +MCV: + Buildable: + Prerequisites: ~disabled + +MSUB: + Buildable: + Prerequisites: ~disabled + +QTNK: + Buildable: + Prerequisites: ~disabled diff --git a/mods/ra/maps/proving-grounds/sequences.yaml b/mods/ra/maps/proving-grounds/sequences.yaml new file mode 100644 index 0000000000..e492144068 --- /dev/null +++ b/mods/ra/maps/proving-grounds/sequences.yaml @@ -0,0 +1,3 @@ +iconchevrons: + veteran: + Start: 1 diff --git a/mods/ra/maps/proving-grounds/weapons.yaml b/mods/ra/maps/proving-grounds/weapons.yaml new file mode 100644 index 0000000000..a950b4ea9f --- /dev/null +++ b/mods/ra/maps/proving-grounds/weapons.yaml @@ -0,0 +1,18 @@ +TorpTubeNuclear: + Inherits: MiniNuke + ReloadDelay: 100 + Range: 10c512 + Report: torpedo1.aud + ValidTargets: WaterActor, Underwater + Burst: 1 + Projectile: Missile + Image: torpedo + Arm: 3 + Speed: 85 + TrailImage: bubbles + HorizontalRateOfTurn: 4 + RangeLimit: 11c819 + BoundToTerrainType: Water + Palette: shadow + MaximumLaunchAngle: 0 + CruiseAltitude: 0 diff --git a/mods/ra/missions.yaml b/mods/ra/missions.yaml index 75f06d21d3..417c3a365e 100644 --- a/mods/ra/missions.yaml +++ b/mods/ra/missions.yaml @@ -45,6 +45,7 @@ Counterstrike Allied Missions: siberian-conflict-3-wasteland Counterstrike Soviet Missions: mousetrap + proving-grounds soviet-soldier-volkov-n-chitzkoi top-o-the-world Aftermath Allied Missions: