From 4415942b947ca0deb3747cbc77640ab7cafa500d Mon Sep 17 00:00:00 2001 From: landgreen Date: Sat, 24 Jun 2023 11:34:47 -0700 Subject: [PATCH] subway gates subway start in an empty station station exits are blocked by a gate that opens when you press a button level exit spawns after you clear 4 stations added 2 new stations (7 possible stations) laser-bots lasers wiggle as it aims at the target this is a nerf to iridescence and it looks cool bug fixes --- img/perimeter defense.webp | Bin 18392 -> 29396 bytes js/bullet.js | 19 +- js/index.js | 2 +- js/level.js | 577 +++++++++++++++++++++++++++++-------- js/tech.js | 4 +- todo.txt | 21 +- 6 files changed, 485 insertions(+), 138 deletions(-) diff --git a/img/perimeter defense.webp b/img/perimeter defense.webp index a1b4c26829f13bd825fa1a2a7c3e4d4bdce0cbaf..071ecc597222987600d79ae0ea0ff60774bcbf8a 100644 GIT binary patch literal 29396 zcmV(rK<>X%Nk&G@asU8VMM6+kP&gpKasU7jHUXUhDu4k10X_r)DF6TnmR1FnhocgX z;=F(vOx&E8}H~DXXPv;-l|L6XA{NjJk_9*?(_67c({yYBv zu2247_FwP6{Cy;UTmMi0tNma5kN^Kc|Iz=w|IPgQ`2hdT?UVci|NlZiocR0m2e)rL ze3W_T)z9u9?mxqOSNwo_<=oHZueyKAKLGuo``7sI?SI;TAFzq5Y4|8xJZ_6O}Zoj0HR0RJ5Rb^YV}Z~6cAU*LcKeZzl!{=@u-`S120 z;{W`A%=v}-SNE^i<*sd+X!&r`VVEKkqa5 z|Nj~}$YB`Rk#iSjC;p0WBu*?cwBZ*=jU*}7xUwCLiPYb?j1>!(O0c{~mx`-(E3Kk_ z##sQ&!2bZ2X_lt#@!~fvE{WnfL3>}u_3M&c{0&leM+iM6D^nJtY%K9${E3tvJg}VtceuRr@-DSQZa0&NA~4be zQ_~CaP(n&am{=V%Ce>~_WEFBvzA8Cq24;Flu&9VTft7k>p`lxmS*m_$S51x*2c|4w zSQ1sQ&v4uoyWAoirZb*R`$I^z2|3f5l4ce^cxA_#dox&zY?F)Qo390-iv-E25zZ<2 z_sg2$=T<@y!@T&NrSX?MH*4^H3)p3Z9;A*t#kmK8bj|<&xi5r#=FH|))D)23et)+x z@DYQX_zu*kA0r9Hs+=UYx`6Okpf2c|zDM!sryk=p;}0$+=MvGY*$9xveXqwR-o;)y| z1`c@|o_?o)b5ny>_RP=C%==WZcyv$n#R|H#;YinUh`q?JQiz zQ?sMlzM!xEM`LXt5l$c!;E2`ZCfBxrCFs{$4td+HHC#*F+ej=HcW(^qk_DLna|ntO zb%r_k<`cN4d1p^(6@%&tPvPkw_<)V*7)TNgg!RMlzM5{PutYvW4owDLUUEim~|1`LKsmwcPBs<@O9!XTCtD*=zT{(hP<~SS7v` zG~I%6?+^b+A?q5qjL7?`$3EHhaOCwe7tPysXtS3dN+Uy*nf48?Kuo}}v9#&mZ%wni zkiVt$KDWFH{-gni*^C{Nj*I$M2Es?}Mg6Da^=qG_nPZ=Jtnrq2mY0FxRMYH!m7y&( z>B?@rbrHLqLX!wC&6G*03Zj~PptlxCaMXZ;dh=6gu0C%mp>(Z|5;eYO`F6<IXK6fuotq7_R9q};$0DxmCCKD1x_$4Jj_%WAHKBE^*i za-K105~F-Ub*OxYo=+5BP&9dzKfZSdVk0Za+o=j^g!I!OxK2A8U8qmz9M|=MBsgWh zIpPFhLP4-QX)9}rQZ$1Yc&|&rPZkycJ8{w*HkW^zR3b4wSaaeA_AHP5Gbn$8)X7&j znFy*9TFz?Ozun-y`oW9+U>=2CGfRX@qf_Yd5Gp(tF4=ZQ5FkprI{uLs&woy8W8jjw z&eKlHSZDzTBcx0p@L{Q4^2|eOC{sgL+6G~>ktVseY zekjNF(KZ$|lXn|Q?j~u}Sh+?jCH?y*AyO7>^yX5-@6b)v;<#2fruPqDv~5e}_Xh9% zGboW*08L%B9k1UXJKMNZa5$@I*dTJl1{vZS{W-)%1Z{Ue9C+wOe{z=Y#aRyRuXIa( z`ZWfHrOiQOogrqQ;v}M35q4_;0RF(g{10ed=WAed7c^iV$3;$HkNP#>naGYZOPTcx z5u{dcU)h(O_22u3dEdb}p{GRu`l&(y_Z9zNarO3vQbEJyh%ZW6vW5pEneHaYu;h$- z@Lo1vPwG>m8> zH-hsn%1}C=`q>HU2z`JbUrpcMJ+kgWK^ELNJ&}y=B{k+TZDW0x0*TejLtDEeJZ*iP zLp@q*eSoVLm=AJ~_r%mw%zDO?qB>sKSN(^)J&hDTZ#g8rQ~+d3M$1}O@!)L>+nF_c zq0xhTD%HS8wNwD`K(zEB{E>uAu%eBOwkCjgwygm_HFe?0g8q0sb{WRz=R04E$-{^2 zskXeb>`<+aSw)oJFgS*GRdwkGBtm}opU?p<=)mJs5hhZpLpW+q-%QK2RY4st3NM)T zH-OSUD|BUBJmUf+6Z!H#C`j;EfN!N>eJnbrOY(kh6ZmgBzYdOO^W!Y0k~c?NUP{ZP zAggBQ$(PAMd8JJcA?;bD)_q@($?4&hiTe|3O^7wIyh4(YNs|!v_oVvK4gtb!#_38Ou_mIFBW0=#B>e5VJ{%EIdN$WONYC8SxpvLw*Uj1 z52fr$u4m|;&Uiscd*H`VI!tV^Fh+>_isH(yYbj)C7ea3gdw1lrc7!!hUL(6`=#|a* zoV0_XC*1BYPQ-swJv-z=jFUp9L55Z*ezYPpac(haZJs7#x*Cb~Y(*zFw7z)+hRmKC zox1u2&w7deaTsWFIt*GVul!c0^kW*YPC`t? zk0_kX+>RKV%ZCi68*f)3-2MuNjqz7 zWBgZMGN&nL+h+>cypdN^(}}4)<6CT)_Lzv2McBJA2l8Dn9}X4eB<@%qJ9^s*?L+wj zMdAe3_&vu(?LBQ)1(%@ol<${`lF8Fo)bVX10WX*3g3XzeM^^vkN69`&62*Gm(>)3o zSG81QWi+Ae-2IUkR;zn#+`F!>O5yP7mt`I?2vuWq59ssqRqq8c_*N`nZJgX<{NzcvBKW&L8C^y| zMW!r#tR_3K0kavQTrsqZ<^_SDcpA4@nzKp=@zEwqTtC~D-Zyu%G)2o_Oi5wxAK%C( zb(`9Ngza`&X1g;P%2wkr7K(XOC3u5`|J4PMXym%ah-qb4Yl?U}4|BqBKNT}|i4G)bB{!PTiVrqQ}UC~@0M3tdC*lkj#h zV|;cptzAs{@iWboghBBx5jK}WPzc^xmPFKK{1Uw#Q=i=cY=dQ(EM7N1G1fdp`QUtV z()Yx&2trD;tAHRmPy?&;0)8VnjV`;6d@K-9hq2F;yk3FxRbZxRYsyP%FkW$vJuxQU zA}0DMn(9n6y@3jDF+Lk|KJ*|Lm2o|u^f0I!Eicb!84UIR*zNP@8**mDvbR)S7?Mj} zNtYnCI2`zEimX;Ls}G+goK?^|IuG!GKr-t{pQ??I`G&4)C3*oCAurTVIIc#k?H`&3 z*c~?~aQk#RmR?g#EkGn(no}+WaZ0y_O#0z-fJV)Kv=JDn^M#Ro%y_uNWl@lP!*VEH*Xkx!nn_B5SYweH+Y@EnP_yM0+!qrZoFRWDZ zy*=z#pLEi59tUix`;;az^*j|(@ukUT*EP!?EB>~**_Gj&q(9SHuK4%HMd@u!=Eiic za5o(+%=G-Sh4#V0)7|{*YN6{Sz>{OMOFwZ$droysT3+YL(W1)c0U&5-rz445$!N=V zD3rCvylc?#YSxnAvYx-JsGH)+u|%RLOe32i^AQ{&@}a=b^q##za0ro^j0%Vm`lb%f z&6}scLGQzw@hxn1ELt0ODF85!2jp;oJwg9Dr*KPQKsa$~m2c=m6c1zY%r5LK3tH5a zgOkcpB0xP1t{?P6vNRmQax3^?D9`4p9$!0=q}LG!Cq@@d#XqbM+kH|S$H8+)M|Pb> zw>xZPuG(lltK$v!4w+ue2+?sOOQUFu;B7;&%+OHyn z7c z_zrt@f}F!(*?0f(|MiOQTXsX&>0NQSCDm8UdEsp%2&{fLOwp#(S-s*5Es zWnhB^Y7bfj9y=syBz_!Sq=%q(GH+1}+LuO&Au=zeXX`oL)Z;lvcC$=y$P(Og-4)1j`z$`uH3?aV<(B?|al|w4f&M_1LJ|4O zl!KyidVDHlu8=c^!de)XghIZI9EEiicCqh>kz4xid;hfj@BYoH&7c-8+&|_<>~Eo; zAYxrTJysL-y|aCdyfs)Z8I%}*{=I`bFl;gAAdmr-5*JyAU!}0$yMw|$Z;N2@xu+l*1Gma5&Wo~)K7@@8x*^$K8T&ZO6Vjz$Oe2I!J5kw! zuu1W0&PoQobiaBV(;t3sLR02N-7MB+j@7(o30sv%vwRF@JhWAb1X7$FCx1&vY~)Nj z1>M^fC16mi7~O0-Weypq#krq&l$i#bYl?^rW`OAkF>e<|sq6Muowp|je5U5HID8MT z+y}%IKUs+Mo4V<4i&28q!q?b#wCmAQ^i*-&&j%UgaURS)l~pW6w_gjfPo5!1kIP$| ziYh_RrVmyYxjP|czNKxg(->bN4({B$%bPw;VCyfJU~`ysO_)*69Bwua>pXh(p2}oL zahc%`+-8@a?!(caCP%wZTdiU?Gx5$fgWHCPGH#6l?$}IV??)oDNT-F_K?%6P7Of_B z*4|Nv!RSJ6b~O|PRyBBy{+_sa1N=**%K|RJQV(y;JFPr5r3Swi`Oh>!ksD@*B#)2- zj|9>rdD*ne3CJPP;nn+~?hr9kOPe=INAUI!M%`lCisQR{{j-FE6LB&<3;{SAL453X zk5AOM3$s7KwSztbikXbq5DQD2&&awSlEVa1ewSgSH8Y^W-*NW#pT zq4tIXM4%PSc4SduFtB^x{EHKT{DgoN=#l35oEaY&(Vf>I9KnQ61T9!4G{cd`_87LD z>T%HBGv=r9da#9Olld5cR#xROwF@;hE~s1(-b!u4*|yd`8IRiS=JiN^u`1y*5vN}d~z2kd*KlaaogC>7k+i34U@dqDVzwWJ=XAkJobB)1B8`Py}Oq}szGj~ zeK!N+V%si9fdE@13PvIk>c~&;D*s{ab~^R=%7af-}Aw$5wuB;r#4-W_6+$9A&rEw@##N7=J$M;2Mr!f zwR_1q`$kc)lrk@~gRA3|1z%+*y@nRUg6ErP5hpZKm=mJ}HoDpSKy>-pa1a(7JxzuZ zMn1KWp$bat6kRzC+h|I=CEf6yYJqK?9sGU&qGxD0w6KCF5t8j`pI<6dM|e@vSQe2& z=t}(J2GdYC3Y5POC+E@+uu-~pmdN;BVK=8$-njQk5 z79|xvCwJNmjz_j#g}Htb;-A)C0Ko#-KS}JnAx|bS_I@lT+>^6SxE^+fNs3!{_7}P6 zzRg27KaCjwQPNH7=7qFEefre6!JIF;zMkWiP#ufoQ~XW{mw5B!**Rc7t%gk>>s`tU zS56U+GmccrgQ_1N{5;%6$vnQ?cX<6;Z=OtD`T(q8QXh3cZ1~Ff-rSC=6A1B*=fZL( zv?Ve<=*E8)+Vfeur9*Za{eyp_)=;sK;AIwe_e)QSsgMS}15KS7wFLZ)5S4j7p}mi?a87@Ie?(|JVY z9GrR@KcV=S1pwy`JL(UG35a^0OmakQs($ew-^nkn!;2NWXnmHjbBET1e{oX*Fi$k?bUHLs+*eWo)MPIIip;Y&EejvIBxZM`7zwXDC5EuK8{i*Hd( z5L`ofhB;5}Emm9U2~9;fF#eADYe!rvw1#uDN<*m-`4oN16T6eP`0qBk*&Us8j--mY zyctD%uQ=tT9v%@7Iv@rCaVJjjCOC>K{4ZUKUZ{&9h&wK44^(P$jz&V_o)5a0h!o-)c%@l~ZSOF6I?6#o0EA6EYt2a=Ix zT!ZE{!Z>H?lOFA9S&PfImE8`Ass$H3By2K^_Jz#z!F0I#5S(hje`XlD+XvY!$^#eG;PcTJeO+dU_`xuimI9wj$U zvB_~=dS1yO4c}UrY(&IEequMBLlmGR@)cglc9#M5K=4Yr!rDCKIl%f@NB4X94ay2( zI2+<;6|%;+?}DdQD$NY{OSc1s0;SLMxNtcvUqA&4Ev2A~Zkp12y}d}TlZ{pjzpFo| zHhQ(5(6r=)xjun$t8r{EkbT>8gG*cT0G>(50!v-2cN^NY=@4eG=bBS?P?1gqAPkWZ zSnWm*|-d-T(T^#NIF9v8ZxIEkq5Ug9tZ+K(a&a(}5DyBIaHCe?9{77rp%>za> zn@HN!_5dCU7@La&Qk-NOr829J2u z)gFa#Ost$O&0{i;49KzTlmgK|zK8r|oWH&j`Obmh(KX_*Izb#gc#E&zjJ8%ZA^_z5 zh+U#WYkO;?^;;_Ug!hv8QD?Q@7$wBblxW2QN;@M*=Y&nuGt%Ox$;_+Pv5SE&{?Jak zgf`JE7JQ12`IZAB1@+W+4@k~KqC)?0#;@`8O5L^ko+{3v7>kP7w&2>j*6*`ohTe4X z?+0$ms9D)~hTMj#aa$I#X1?wV^Ltn_HL+#(i}I3WRQr>m3a^m(##W=9T(-dG_-S<~-sOyf|&5yO(Whl{$iP z70-u6?CHD?QMnoV^b~pjKPuwIp&2uY8wb;p_HcvVMBno`HYe(I>w0wSb2Ey`AiW4C zGL;G{3#|K8?9cysv^vXz()QyhdWA-*L;>~iJDo^gsD`e z6^Lh0hz^}Z%jUDa9%`!>Hm`EQ-87YpO_WjfUcD!o4j;|dO=w#LAp!A&5Mk25rTC&H zQe|M*!E4)&00RsQO0OJniRE#lOdP;O_uy|2Y{-z}nY->0s1}Uij#-4MuojC)ZatuP{{Zo7|?JlTk2uG5L;A0<2bX^JY0i|?=ud74u(h@*jS}vM; zpP0?{!ZU$r0k^Q6KuCYseFz@qcF_U}^Y9!*^DW7G8L)^i@xhxEKHm3=Kk?BKZVDe3 zyTB~DpgrTFjHr&vMx>DQcarmHnw0-jFDZ1P+7rU%9Gg^(?W@egppoAVyZ9D!6|`#} zD2*FVt#9n0Q|kXy~A?p*s)3`r`eV3WKcHv zJXdnmk9MD0n6Ah;r0iWRW_qVwmAQcW+H+thEe+tIDWAiA?o)sBwt{CwTUq0!E74Dh z+`RJyfQ<@Cx%5D0-^BgvNvcqw2MZk#I_@olX|Lb-rS*KqYLT?wA8{>fP{Aa`IE&h# zb8uZpVbE+HGM@6Sx^Ag$<1IE-Ev7m2I*+GcNhv&4U}Z&sxYbX>FNRPs!A_KON0VSH zZghrFvI3w)qHR%b>NLz41~<+I=fJ^FkD{B6I+=pmH2hORS-ig44T&0%n2Nwr@Q)O~ zZNJ|l=20JxTdjo0=V1-BYH$Wh+Z*NbT2?I)Z}BET)682;sN7Fp;K2XyOn-FaS&?0h z^EKPR42*~@VxGn&PE~(GK*|m%CA}q)A=>S`DLsisKl{C6u^m~`jd6MMy=^_qc>dVP z!qU@O()W6c+sDT}JR&=1rns8eU`VLi5Ug3#psv7>9~(xqo{gk75Csa1~Kzp9{v}xzhnT!Hd1?swj zzZxur6DkZO6)LSmjvv4G8DIKym{?^VC56NPIXqHBNbZMOUF0p8dSY29j*&%8>0=82 zYDoC<NsY|$rPkQFb({3*?Wls*a(cGO_ z9f$HoK*lzbnUlg);=|fNL1H8;R$*EStme=}#2blAfb_Grw<`zIx&}u=oHTu{G)uxI zOf6R&Ui+#aW&fptt}Oe~#IsWk_A03CQ5O%7gp*rrU>)7||Gua}8X2c^?TgAozwV}6 zN&w6{qt{&9ty=I7v)Rl?pDJl{Y5JWj%eID$ITu)&lVi@~ZO6A^?Men_V z0y#UV^yq=9V<++kqLGa~=}nQc!6$m`r>~&+1~P0*CBo`8EG)_Tnt~D-64|&yjQ<_L zCEjbTKV#CUv{(r?&}Kf5Of!=aetq72@1ntj^CdO>R##%(|G|n zC`z?FBrDu61Yfc$$p_mD^$$JHs%OD?&?-8K`mBWM$kI&Wq+`8`e1 zDvFVIpAto4KRqg>b)Dh@Rfuo%Z;)-HdK+mPlxGx;CHjSwPkg7uxd+sp^Usw@G=bq{ zR&O&}v@Kk|<}lYBy<7=y^Bt#!|L}STeZQ^_%$h_BYLs(1{Rx+WhYuMS`lhGYGt&1K z!xRZPGeudD^120G3x%6!pFD|Zj8Ysc$`K_9M4TH=o#G86;Nl*lv}HFD+*ta@&3#ix z@6MH#{4X`-jm*^JuPNz9}C$*&TEbORa>MN_r0L0RH8@OdVGMF@hwX zW%KZ;_29DfI2Tu=>Yl7A7sjnFSogv)p_E=en&7tlMK|-XqdS!tgWnvv5t_8khnW5x zQ$QX2xE=Oo_j(6ABpQ!ieSV{F$l~pX3cy026@twbfE95 zY~duRgR5`U(K9ZD9d)$w@YnWGKs-7R*MQ^gn1O9G&I?ytcHls5ntacd+%{~#woE@+ zY+7Yl4>^@WE6YWqIlzQ&mJ}Z!A?EO|^87{BpPh}~KMLg|7>dFyiAu%Mpf}e>CGMlN z{HWjf+!kx=~4R8escV`AVUxpO-0={=wlVUtZBz5q|-<- z7MOHmeT^KxD=VKUp7rsMjCI~IhUpUl8*rR@YS2sy;5ohe4?R`VAFbeAr6*t}!YtXu z4C)}KkwHWW=}F2UEZ^mqcD7Nxjb0mPSykT9EP+r=VLO`=uhoSDW>(0R&s#(Qu8)Vw zLBbc4W3M1M{W{@C=;!Obh=yAU{uyutz1ydf6S!3)IpmEYW*&d3rEHFF5aKmo+(4g{ z;%EQlVC#vDeVBK?$#(F6g5F9(FZnF8Mw=XvuJi|BR?1}#UtD)pLdTqu#By~1I(l5v zE}g`y`{<=@^`uMwDC<$}(!W;%NNob&r#V}6khhTZ#Yo#X!E825BTfs?0VkZ_g8#80 zud~dK?2sTPwvp;X?{jUA7}N{~HYx7JFUp%eq>Ilns>CgI*z+*UUG1mUIkoEyXFr>_ z%NstY*G82zhAr#r7{kA@|45^8Lx`4Ou#spTYQC##$OvI*4IQyP`)(pg4^SYEBLqIa z`mUyflBYgD-p;sAdw6*32}p_k2|VY2O*Aomf-P1*PIJriR*x}*nSAc+QzRr*1T4-N z&uo`aeV8?F>iE&71pN{_fJx2&zhPP{<&n# zg0I3sqMZ28;v!wW6kU)|j^l=TGqm!@DHbOGPhSb<6sZg89>H!dKOr)2)yg8@5|3Nk zhss!IE29}`JV-l#N|4XR58tRE?p!{0HJ1#WiUw65H-_ach>SW^wP@bw7j(swM9)r8 z7d7M~j_Y^{{o{>@5!fUlIv*PJ!8t?}HIQe2g}`RDA!4lUaHC|$TzaT zh)6=gp<=-;g=Fw)%>+!o-*yvE(_TN@TN4F z8oE-Sk_IZ3HFVRtc~;uxn_vz$c~EO* zRff`S;3Q}i)|@bD^|dVT_hc^s_kL%OG5cE-UrYA~BgZ-HgIDFbGgBGoD;W^zPg#!B zn<8!2h1$G_L=<>Q&1S3_PCd((E@?g`|>3 z>gZXZeyibskPY`6yjJy}Rhn%_EidbA`>ES7$T7wxOSVzrxOwCosChL_N*D!=kX1>T z(qIlHd`-hTvyS(QUfK5ZqzVDmJ`OYwLI}(wUU=9+iROF-{^6VuM<<3P6`4v39m~8h zdRiZ7dZc1Y54Jq3sY`&1?8iOZBVh4-o4@uV!30a*(m7!lhSIKCd{efAQ^5r*IP?v9+iXAs2lX-L%I+RX(*e>K1O-|^zRqfQkKQ=A$79!(RDf>$1 z6Z&dv)yyUfT(W95|J?hR4&eST9^VBVA`BLCpmF)IfXVtV-ISl?AIKAqt(fWezFu3| zc@_vNl=d!%1*l}ymNKYHTjz7^p3{bS*x$s$voyo-R!EYL!8|W&pj&xR|IKF*cQUK%QQ(+m z^FEt_x!=Cxsekoe4&*&(Nk}U2yB<8H-r8gtb}(GhN%NNiE547rW*dH6Sbbv())Fr& zYZR?F2>Q#pTh2Y<#r}AvvR;D1Eryp{;F2eQNR6-c&qtBLkw<;3uep5C&xrkYY+2>T zSaat&WjDXW_zS^qH$o(q2Mra~z+jHRvC-n|0Vkv3F{TU1H=>%4*OyOSuhFDFgJ7X2N zxAom;>@!mM6a{KD9!M7x2ZyKeX%`@2hbi}uD#-^Sw((pm6qnA3DL56qai6zaIrXpi zAsRbbJYzlo15jxG{jju*^H3MaH~r^A87*frb{-*>@fKUkoHQzL<3dMG8-6*%tcP<< zV>^}TCo%fDH=gNDM7TzO98d9UQ5zQNWr?*P)oFmSU{v$@1W~inPN~a7XEd?#=#?o~ zZQYL*Cr#miA{~)Do&B!68BlxkzMVF3P;hx^vb9{KGJe#`gsg)of60DG3u*G4PJ-sf z)cLOFHW0Ovf7weO?4R0Zq3RD>aU)XI!rMFUb@(i=+Z@Q%Ut2EwlnlbEKNzQ4#Q( zj0~jzs{Jlxm^-=w&!}xgg39niv%AgjNoAZl`)sT3goE>EkK_ypX)-XS$lq!@`T|G2 z#N!S*TP7;)bT*aHML27?SQEVdDZ_NTc>vA)s+dASH@{kx=sK(#(FyU z8B57=>AbexWMQ;ZYR+6Gt!P|=C(yHdLqPW^3#mwfzv-SlfT4=9lH-bXle4@Mi8s`v zM$k5eTpbl{Ol8=G+VUunPLjFw(2hfK+Eqk@$uLgnI1V5Ceyod=IjxFw(I?R;oS+xboTIp5eKpSM531e0G^A z0iu@#{Ih3>=)!V;cMnEP{XJ{v?yt2<(h9YolM1Q7r_Dkc@|E;VbF{&wvMmtH2>DW5AJkZ=pc8W zfhr?dJgoD7W+3M1Z_fPIo++tMn^R}ilKo$jiOU~XxZBG{VFZLji`G!hp&OP00Y@Odta*J{ zS+VacbU=dhmDICfB&O~t{otA;F2QpdW!anEx2du6?UEncXb7m89zbc+ z6Xtm^tFZ(^`Eb8eFR00^6WN_b0rxb#U8rkrTGb&wdu6??#go)*p9!ZwDX}OFmDok1 z?Mgx-xq}MNvJb^eMa+$P3>`<$sS z$of~Cdh^TO##AXDB)U={sjqun%GYNuWEFfT@e!#Fi4Q!t$rSR5ge*lJ3<{{{4r?KR zv_)euhD2ecajrlV%h&AzXVLsF34;DOqL~VC%ALjbQ_Ddd&`*-r8=Au3gOPf*_7KFIz&KIZHerM986v9;qp4-Es?jv z$PwNVJ((QAjwL4IMkL+^>$q+d*Gxl=^{AoXa0bv2>&^2H#O8zP|H6mU} zN_~CsCDj`HIZUC^*`CWTEB(|%1Yc5Rx^T>ZH6ZU|@N3`V@@usxnp&FYmaiJl$Y?wb zZWj$m2a+#|4`LXh8KN^yWxKT2Ep#2KIHimZZ8R7fcjS?Iw2N^T9_E=p)WH5YnHnz2 zmAn6nrW;mq3s7~Qqm##$RUM|%iGAfgqhG6%qM?^S5rouU>!FCq?YRL}p&nqXTme+q zc^H5jJ`%+Fu$Gs!d}HBV;(KblC}YFLPJ2PcZn?}w!~D3-byEZNbjTV=NJ)`USsi^~ zZM+0Ct2?X_V4{h%XJa3Mhlq_K5rt80Qt40XKZeH%D$2CR4P;U}kd2e{rdhOP(+Rw_ zXshADLiTaxuq6$*`dtuqO!Htc!oe?RS9H={KzS2rh5?^>bPb|LFrJXO=rzh$l< zoY(r1LQ5j!bGUo>U%>=T{#PcnF|f4SfvT&2>xTHRZ2;T(;e|D_^`%NHhYE@@+xq=D zWo^u#8}y{A%4&NoBArmf`nxL={QSb}x8Li)b$QGMC8>=LoGDOh(R7o2pI^>Ton5Pk zSkA%HbkoYa8x`I$OkW{pcZ?ah1uXd$030t?nj=|kS5ex*;-0on110sT{E(prQ0h4s46kt_4?1aRB6_gYKh4u{ zvO(!!ezP$X|9iAVn*(gy5TMKfZ`zO1@w2%6;Ib4O9H!1BGtvp{3C*Mm|Go8&kQobx zsm@2DDoTas6*GgTL6EDBv+QFx07ix*?i1EZ&E&u!@H=0+N2gG{F9vHeZ`fEf`w0M^ zjtVadnjbq_KDRp%PtW*NpWFHlu2>kw#yd6NJj2UmUysslsOU#Lq^7XtvdZ_VnlY$- zay)hhVY!iO>2vpr56f-QA5Ctd`Wclfy`q1KX;0H@oh%QSW$D+S6YeRQl?5;uE#qBt z9Bh&BwA}wJV~1*kF*j3YX&{CPh|X7&T?SG0RD<-&;u^90guj6=z{X=XZeer>kbC{` zis^l)k%lSAPhd90ps~U`_a=qg`p!O4ecrHgCD9D1-#?=bfqpp*bS(MC=S$S3A5l7+ zAEHcWBA0Yur@0OH_x=uhw|I~5QP-s8W8HK;f+D8pes}Y}mVyoh2jLQl4$DVvg2#5P zQ{FU*7rf>!QAOk)wNtVmPJ7y3c`^mw^!Oh`Kio41pkY!& zQ!|3R`s5A@6y6O1hf!i!N)Pszhlz&)N5q#VN(=lci`mvw#mB{_I8~mLN0cYnGlL?4 z0ifjuj_zBuM${!?soToMVv=LVf^Q!(2T?l%nM(l)D&lR4(zk`jfNa5s3$!h&BF zNriiY4jq=bsm*3jdA;q&aWO1xtYG~)j1@A?bWJYbiJMd~Yh z>m_gb1pucub{rOkI!8yM8=?;g7twY(EV92a;80+#wwN}gwyBMOTuVNbqw8o;oMir} z4ki(4OoCc*L&;$C-2J@_0tV+* z2kv35a_C2Hdj;e zFB6Q@v(Gjy1t@7-T_pFy;7oOfkm0hABKW*`p#1p+Wp4!$+!*w9sR$0o2Y-EzPI+PSMc{6@dUgzMMb8NBa1X3Sl%10?Ysewg)kR=U=i<1c zYUHXD*PbP<#@Ji5x^rD5!Tz*Y4?r$g110lOg%$?#A{G|2$L1T}=4!7vc`?jkWl3igxBh(*kL}0PZ)~d;%xYCyzj6}(c;;a7#qBy)~ z1Rh|Y1dHx56wq!aA!n3{=e$)Z!bM8!BU#CBND>2{WSAIHI2#G^N?5fjCva?&bA@gSvdu>|U$6{Q;*FK;S zj5oGnWhOCAQf~gChBwqE5Lr%3ie;2V@n9_|u!2;2VD zn#QT3V?rWtJmp)a`f zVxG^(V7=~7wRo#lUG=uFu@b8WV{(qH^vIj~s1b7?Q0Wbt+kR#x1W0*y!sPt=^O5GS zpqIj;T^&b6QQmK@<~I~K|-hT|V&J4gFIW_Race%}Yyf2UE%zH@Gs`cswAE6luj!MJrjK;-R1)7EmOxg_JBU~)Y%>Ib9XN7cqR z2x1KuJy%~zIrxDwne!;32=k|L2#3}&O7#oA%W4tdjEn$}#o`&BCmrO)?8n@;hRB8O`Jc0mCvp%_h5dl8@v(Kw9h0Hvyf284)yT z#>05R zLuC4ZH#ll*Io+^VGl`W~dVLg7&fbQ(cFrn7e8BH<5Fd^Q7$Jm&Z2i#eS^Ss_YeJLdz-Y92=v~K_ zqP@Wym*|xCz%s3IH@l=BMe&PI!{+oqg96o6CKKP+qx%NVP7y&_XJ&K$-Z-5Qr6ixn z`=q5!h3HB45OdR{NClear{&#~Nly;6)QlBSM&9523($_U2$l9ImDbu@=MajVx)ChQGRp#=^#Xi?AqA>@@-f7(@6B=?0`{D`I1% z%|U_9f?Q0A(MO1Kur=0k>}|u8y&X5(TZ;ziJUe5OJzg!Z6W6&mL@LH6WIwRI{Zuz_ z(Sx?L<7PeM*rj5p8Mfj=fwCD^D}&y2fjt?xvVhOP_tRxi{ICZvzs{;CwQ#V(ICby` z@^;=I&-U>(uaLPo!~6K3 z*&%bcSC4DPy{c<3A+~*sks==ZlYFlp%zTz;IL=vr+H(r%I8%@S&ecVJc(3x@z&ZXH zG51d#uwSRCb1fIeINtF2dE%TRR<7D&q8I>%XxD_kha<(AIs?Cn=2-x4uDyDa*~NP2 zb1!XB3e?Orm*3?nl3WwO2c|>%`Cwg+4MV@iH{`%-nqQWCm5G8JU7ArKqNZ_6-%XiP zgy%DI{iM|^h>7x;?LM4G4x+Qvw!kt?&03l=yNiToeHMu=TBNDb3eZH(a{1vh$E>s4 zwJEL_eDmC(VRJ=KXMku-ZuyCc4Lo3Mv1e<$j!en%vc48?hqpDj$Fx2;<1zABmYWaR z_#YtOnoR&PH>yL$nECSG?s}1CN6rAl>ccEG+yh?I<(`k|h_ry@(7p9~VEy&5K;5-F zsKBM=EE<@=CSP2&5<4ob+T$BMZYkUT8rcen*tKj_g9iw?ghV>G^g$+)3LCj`QAsvK zk7~OrezMImTJHZ7uxh|*T4Ay0Ms9#5D`U8o)aVoHR$STjyWFFZifA>fdtC?WM)p#| zr||N5DkF7umdsG)%%zEOHsUi0@=h+t&u&evkg`M^ap>BXSfb6d-4#NNo@r*u+GL8V zw4~w>``T?$;_OkzEFJykBR#g{7Hyfj9XQ-ZMYp#<6cSlVJjG9ReU0IomFYEg%uf(uBo@73JR-Z$U-94qdlf?S<=mc1t5 z0)IFDQl+g#7a^q^Xgu2Few(xWTGEcX>2d1$d^U=5sKo@DkU2WZW(#s9HO!#lA{)Nj zz@&7%4ZFquZiiFi)*pqpZ2|dyRA$ao?>U^|=}6#aFXfw1_r*vy5HB?Ck?CD?V-ki}SE1b~ccgYjgRc}nDN6X&Vqq&2zaqPTOQ ze;;=aWx!L%QFDJ9DAh+c8tQ+S*8*QNY=XBtjYBD{+q6+PT|s%d7@ldFw8~vU z1=gRJ3E=B)t=7nosRrajV3m;RE(tJ?|D+}*NyUe{e3UO*Vrkz(FXh!+bN}N5U?NeC z!>KD$8Z}rFEw1rl^RWhI1d2}!6&$;0);n^M-tfrym1czIrJ-$eUPqkCc`-#Rdav z;Ni26_*Fv*BW$x3Vg>M2o)F@G; z5g@VQhX|QsEH7fTSUD*%e1M51QhMoo3VP>i3 zGP&mYWUvTewt}fxUPu8Yae06^b!iD2H2v$M09lsrTGRNm0K91{pIXA468NJprmcAJ zMqA?Gou9`sUYB~T9Lm0$xKe z8Vy`*E8uZtK7z(3?6AsE`;nxeFyGKK;$N;TYaz($1JWK_s*M68>OPN-bagaXbIQ=W z`cNNO6>{W&YQslT^sm{xAMd#Hx>2ji*1KSalwb zV8qa*w125W&mKT4XubU=9|Ef&>H8%TB&v$(ZzaVaRh}jfWLLlYnM}Q>W(cg=6Gz>J zt9BNbYJDS8&^wvW9CQ#l3_dN1YjGfxyWl1=4-G)9Hi?_l#O)!AkcZ+db>PC)t1u_Q z#jg5}6N;>VA`31Gqaxf>nw-T4AEui$^YP0a!^n~5&1HiDJ4?jjaUM8WZu;~973_X+ zi^nLO0k2JNIG1!od7P{;I2@ts7**$kxb|$K*LXX!`UzTTGm&OsmC~+IYTVf5WbvK3s9;4f0?om!8lNlh92x<($FbfH7Vxd0;M?*Fpm0J;-g28rtsy&ET}7v5Ma6?llGWi_S!6N{>4g);OTEGprG z9TAjIg%A~m$LRp*rjkYMe}~jT_MB|izZ)O#oBwOlOXmz~Jy^!eUn074Mhs<*=G`WXm;5Mg0 zjaFHK2-rjz2iowR7PazEOn@&<#iIT}95=z+Qwc-bvQiiGPH0NDmj7NvBsY5-;u$9v zAcG+tc9{rS%`v+2PlS~lT7%$+uw=+wCqtrOsmhKs-~J~r(SWonT=%KLNy(23X473X zcq0|bq)M1(knPxS>KNkY?L8?acZ`s+V1FKZFTLs$MhbsVNhE_R4RvprFNUA_Y(|yFE=CY zTamza+U&N_M}Z2fmor%6cCvaT@V6~!INi~DR`aPf4sBO}WB*b{w=-=_r3`S?hq8Wc2NnS2H;{c*W#e*#-WV_Xqi=Gu` zuc(~3pRl3G*D@Zu1@p%eJm;Uc5QYAdQTJvUdi%<08#llzbtvg)hu`S-sGO7bfmFMd zHuCJ9m_l`@#)2MURmh{@Wk0c;dmYcdbq!)9;fH^M&V26McPH9ep#C_l=XQa5jC?Lg$vhTqsurbEJ##sD|$sh~gf2%!(mW`!7-O)emw{XJB!JHDRU$=+t%= zw_Zynqa~P)xyIAmkht5eDnyfZTqmr?qB8Zh?rO~HYmn7X7+v04^&fba)PLl343K4E z>|=8wCQUvs zW^yjv5dNoeNj0~ZiX+)3$AC{XdJ}()TcI58H`kG?Elc45V5EfRF95;0LqBahDMae6 z9a8D8I=S4}{W)M+$$1(wF&!<=JRj3ArFXP0j7k=x3ti$!%T1$D6Qv(!GV)jZ`Iy_@ zSo@}6{9D>wlFCUZH0tz%Y$sZ)6m@AjY^O$3hJt15$|CTrz?EdMiWpA)!BAan{TfSZ z&O>0)KQJNBFdnv0ffvnqGX_w;Fk4=eQE2et$BATDT$3Vc+|+I|%1&#qW!%Y3b^kv4 zN{!;KaFcuad%bU@p;SGFt-3Cy1^iX(Ba7HXjKLXG6U&Cpx`CKzyyk`U3Z5^dbYwR9y#+|3H)SIdC6tVCTq~heZKjMKkd!E>DAsIsjtBFxe|`0T5S{#@ot&{HZTekI&i&d6Gx{Pp)d-cS({8<$6-^ zPe5mh02jfX)gF)t#C^BFe{)yHm5!cr*-;o*HCHZi$x6cQntC630rR;+D@1APStG(^ z*vvl;Dx*?ig$3>~mFC*+IdXDzNg36IGG+_O9E0m!(!J@v)0sw=M=`r+!zmVE%Q9K3 z61+aR`f_Gft7c!jT3l=G7j{Tw#!-lLTB3UINs&Sus0pq5{*eWPlNRL`DUr0u{quMs z^iq$BMwq&O1JxOcMp;~t9Rb)~B7zHfyh(ji;ba)eF8_KOuaP3Yh@{rTj7NNy)tMCm z`H*Kzc^`o4Bfq^UJrIj%!s4pUt}bZ(L#bl2lmlF0GFm66@=Bn z8C<_yp9BN_q-sa;)rZYrK7bmAn{v`371{XmKRLnkSLYjAk@# zI3pPzX643|@MCqgLr6DMIZ6$z3|!^4AWs@F^=3GG)XjkmRUXyGO8oPD)xthN7+EUc zVtr3N8Y29yBWEK)UW9H>H`uf7MwMpHzYA4-;=p3*lNWBspU5xIix!_yueSQ0!X;E! zCLA^hEDWr%m@TGlqTC}`)_?h_#4(X;nF`pl#o!&FJ~k-{zYv<}yVln|poFNT9i5k> zu-b&bJ+Q)`$P&Q%`MaREKC@-Ymr#UMvrxW#z@*XfwX#@2Y&-dnTdPg?m;2&VA_0Gurd& z4-d+3_|jzc9}VB6b{!wEAyd$Iqghnk$Kp@kN|3w1aOdd^j!Y+dA)P(82z%tsXTOUY zrS`E<7afIiDFVdPTYtqc>kRX89juulO4+70DHmnpY0)-%6iqE(4)<%(zt@6W*3xEmf?phdlOp_#e5#Gs z*IMtbM+G^^UG54A`|~oW_U^=+wtI5r!t3k2`-uzIw+pKH=Lh*)q+$B0^RXr!eWU`j zQK^LF0p@PC7CG5RU|L8kisH*SoRswOljv};e-PN5v!n1X=1-*Wh@W?V?Y=SlUE@Cu zJuM|gm^^HMb_xzMBlv+{B2^+evBq8D8f8-vLMp-<+PcgobZ-QDK1G|?Dc_nmtPe?C z@N1)XJaz@nAws1elSkwN)tVZ{%%Vwpy#VUpY(P*l zh0(R?Taiw?a(Kf)Ws@aiQ{4HO|oVFRT`fz;iH0$wM4B_Bnkk@vB@cr`(0@=jMFGO zc;!4F7t|cuy>D-C@Z70oB$d3!UOSpOcs3czV5ojbOr7N5NIMmOC<{5ms0R?#jsmgvc|$!&5brKm+&Gpq~^rG}1?Y-0}M84rF{bW7% z-m={vjv+{O4KF0wQ7Ot%&|jIcNh*rx$1RU-LSD$@?s9wL1H`|GpkJE2TjvS|8g~^U zMo#P+Xe`bi5F@u4mL+9((B5SjenuMYq2PVODmR>yo3x3iK1B~*dnx^NsXE#T@yz!L zB@oSPbkq~{0&IW7)O-&EUaj$-LSlNoxp&)k1CWUWWSNK)7okHb>M(Sw9-}JarhJE{ z8Q}EF-Oc#n^f_hW(t3cHyR5$X7fI`*Q`MR}R;9#FF7~T$FJ7@; zhqOERI1P#*kwCAUjtOOmieteMD}Y5&@rDxqP)7rrVU6k&RH3vXGOr6TISHhVR9CvT zvr1lo>BaEMxcCJT9eZ?2$LC?d6CCK9Q}XV+)K@;bnmqZI=MSS1=H>>t`LuU83jLQ2 zFd8}){Xjzyuezb&fOd?g^2GU#! z7IF+)4?x?|lU2^FUQ7&UZ!$GHXq=7S<$a_F*?+24o@)y$Ms4fG;=ff9?3i)QW%qMp zvQVua)Dw)JYV1T{oL{3Z;EZON1uxBH>+&H)WR^lEifD)bDGoQ?H@Cr4-~&Iu!arHt zQoGnJXX~~9i-`smR+GP5TBm4EU})J{Q&KvSc5PoOu?qOkk0EL^*@~2rCLUbkFsV<& zDu6bN2~)dwYP^f#QQib^OR?1)7F9#BdTRhiX|$3cZaM z*&tGmn^jL?yu(oNSF*bPdrBAFY5s%(Wz+AhdD!Z(D;qzmYA$JN)!63?Lrghk0?U z^D|<3YrRO6Hnn-<}B;`T*%sOv-*>Spe-%2dWWokrK|c;y0k?#JPXyv08w#@mPUO zd9$37^tgZspdVc|N0S1PPcnB<(g$wDPkReD=nh?dowsKB^fj~XQDc~oGFE=6y!KV9 z#uF2cHCu}~S8Db;*Pfy}WcwoU2l+i4sBx+TbGM;@d2aeS$A7K{c@8tiYBnc<)mZ)U z;S$#Xa=;qWkZ?ck@YF#5nq*m#;O3cF*~^d;roViHL=}iGxs^ElPiN5n`HW1&EWkb- zI|(&Ybsdqg-<4w2b|0o6v-3%_8LHHAe{xBvUS9J|>eTy0 z+ien{n$c(#f8CC_fg#bI=GQqVutaM)tlo+EQ)2P`yKNVe%O~46r_CGa;0vz?xR`Sy znA=b)ds>?|;kMWzS;uf|^AFg4^W7Tn%M?2eU1bJC zCM&I--G2!u(MFE8wNj*X#Vd0EK1>$evw&lL7Z(E05#J{I+doq>>EmTzl@S@0E2Rp` zzwF(Ahhbum8oXPfnXYI!+fWyuIrcTvY0aP@VzhPc_wDloGB3t~A%wE#nWzZ@8lkL3 zl|oqrP#ZU^qR^|h;HA@LEiA%Il;{R;jH8DhTu0s3uFO0-ZLmGP^0}7&s4@BmF_c}P ztWgeB^Z= z&HtDz&d}kGSa9@ON~J&BMLNANq>uSEtcV4HsFC_?VUJmkUBk-mX8Gs5Q=o8c%Zq}` zuk5wOkR2eHcrIuCvSB)!oco55JD2bFo)iN9EYAbAvXS zV02kJOPnXGi@1yC+(nCx*A6&Ei%y4t$C$`Ax=jbn5-2YlYo9v$G6s!9cfTFBD^zsU za>J*&nI53f%6JtVHBpZliib16toM?`}({5E6c!uCm4kgiokE#5A_Ciuopdd+YdH_?)nJ26#(5PRsj5 z!*c+zEb9+l`026uGAi)i(ya#`W=Z(xV4E+aJs`Sc|Hs7Hgzqe<3qHCi23yYT0tT;B z@`%hBz1p_-SC|Xp7Tjml!#*TfL;}v5Aa^JbP*HH58|6HM82&^W-FY@-!1fD`m1xG8h9f^ zqP=XYR_eS&!YHvN(eEwF4!mn$>v-`%$|LJKj=YJowR9(XCHt2&?yl5oYTAW^_S#*! zhUO7`l-RilU)d@6tVOaQ0q8{b6${0AnqAQJ)mFG*=mDiQ2rg;(%|Z23JFfZB*MpV* zR;|==11-3-CKVZ+Tu0aw^5>Ta-A(J=QV$moMksKYONzeh-%9W8YYT5ly!3i);O=CP zFK>W6-8$jK6m5IvJuIuNuXDtNN<}1O#|XihqPrsuC6M_;LyO8;6+OWr7CT&octk6vx5;``}nQfku?q4OpFF- zHw?2jI_fl{=kmY9Y{&ZwJcf;}y=sL+Jct-f0NUxnc0wb;Y4MKU`QqpLJ;OAL)TnmC z@;J;#e}AhS%ob4;G_lLx>Y>YV*Qd%Oe2DsvS=-B&CxYcxQYWTY}Qn^_?{F@pJBBY{|lP@aLMz*10< z%t^(R3QsXvh+K$%dF>8|%^d@CiQlxZ{UPxHM{UUm?1Z9V>ta|Wo}q~zdR9$&iz{e8 zC%iB50&6^fy|&RJf0Bm_Dgp%;n`LO7Er^v$A~Y*vRXdS-NmC*(QOe>IgHZ9(ZL&_u zk79To8$PJUz$eB>A1ZV#|C^wmG~%g9iQkvUdCOkijuNsUGwX&sQpke~3fJl7Ii8!O zfWgp!tEI|=AxAzz>z@>4->{VNqG=lC10AJzTHQYXnG$B4+dbd0hAxGP{sMKC=vJQ| zG?=s>Zi0QEJi)hYV&p*E#CUQ_QaHumh%z3_EcRsJ8camBbYeGubtN7r!IvE9Z!#qg za9VwJlhza9eHZ6M9Oq4da-nqa#h>6e{e(I)y+6*mgVnIyjUrVc)3PPe^ayXg}NP zm?d{kQ2Y=|8kB7okZ`m1Q&X>1q?WWt$7~vR)2aWzV-mLCcP5vuz6W^A`JaaK z*PplrC?i4Gh+B2JZLxMD9kVCOm{6fWh6{YsQA=lq_+$yPe-2A^?h{9cU+wjABBLxN zHJd6>$p=kxMRY%$D?!_=ZU4jCD?lEd2{`m@iK~?XP1gYOu%=Baa+2i~3z+>fx}@%n z8L(7R2dq@;iWM{N8;`UbppYvStpGDvT|nfW)GP&w|SR{BbgVoaGyd@#=6GmtY+I16tfE)Z1;v zV3z2ffsI+U&Xc{DK5mwRu3Hvp7e4;iBj7;OzZ51eNyGjBhcxL2UvvR@v1E)XP@h%c zlbeLFJR=ic%g`_nrkT-dM(UvcNuRabdTmuVhzpp;nvfKk6%*m~;>g`QKM(i+$FJJC zkKx|oBl;NS(BkYp*2mwJSiI3BG@FR{~64d2` zOR^@j|Jwk1k8Zz+dGb7&Z3}X6WiS`Y?X-y}D^~`zA9E;Kn1DYr$0MR=Os1Xya3LS% z$8;rPV{;vm&1=DMgeAcOwKjAoT3Ha(+-Gr#6q$1T@8K1pOIB!H7(aWAOrXtGoSU4$ z&l%6Kz<87U&h!@A&rmkLXfH^Ynf^;9PvKk^Q#XmDTQyGj^PqyJT?OcBo(J4?Dz1C#rEJHpc1JFvl{-|J8*?%fPW9vBw(+i0f zM|rNzB>Z_W4jnX+MlEl`>Kh8?qbn^CVPW9gXx`1~)>}SHZF1wv5I;m7HsdsU zzBBP-58XXN%lVd*dU24}ZdFqiNxs)BU&pzVtgT*j>TROHgWKAN-iE(Fi1H4-+p|NA zuq45{eDD%ciUp}fUrb!P2rBAqBOME2wxTiS z4(7j&*oc9pKWj2(Yi0&b+wwC{)p8EMFZ=JzJn2~Hnu~hG!z$BRF^Zpi?Fy~z4j=qG z?1lL|lM2*t)qW#h_O5^PXV7!6vB2UOG>V!Kbo1>qic728nDYWnoCY_u)s(o`P2OqE(A3#@j#_2;)L z!$}`@;vDw)c`4;LW>dIAdAz8f#ku>RpgAMneXEaQ6(o47!rIaa#-YnIykaC&s_fV( zHQ{^ZFU`AgRFcTqQ;pR;izaFy{~-LKDG=wLVOZQ!%4=$oEefI`{$?|b*=$k#6Y=*< zY@?(Ngr8hMAoi;b@$zE-NN+ZfE=|xV9Y=AnY_o&k`LMJA*fYp1zwX-El;2s1#P! zJh*9bITj-0)^{STZi8JFNGBRyowMa$XYs$eXZNVyPLr0s3=6YSM5*&_kcB4V?T`j_ zn7ML*sXtQ2{};xN4hvR-^sGj4-gPA6ldyY9-qCxXiF9b(sI>JvTmq~arcm1QNC&|j z`Pkw#Caq9>kHh)NhDX!XHJ1}ajk+X7m;?Pbxd}@2q^!!8M;^P%doo-Ftt48MB)IWG zoeuz%?DAE5AzN1Dsr(MGZ0Y0MoD)mO6IS`V|5Zjb(ZFEdo?xL6CH{)E9(aVZdGi9| zd%C)HYH%YEL2p=7gJhT4C_0u(@%;R>Z!vY*kZ@O-IlDYLNT=#5RdH3H{5INSoAp0p#fpeq|5JCq&gBUlqQ>RMTSwzIMcm0a6@cHRxz|P>z3GF zJ~(GBM#3$G<1UiyY@sa|6dxp{{H8~Z-MF=4!KDV2Vqa`h<;9W2d5+hmk zekaDAep(6gB4XeR`Y6t-kWNV_Yzz6H{U35Gh8Q)^Sof6MC7uG}l?yo>tA-&Tz zMRI$B*%g%x%)}r3B>B|&dsfBB!7`}&I?qAF(D()qwqti$FNW9m$toW{-N6TR~?)6aIfRdj?H_nM# z%YT^L!s}PLe2Mh?B~vCVgn^96DUqz*YHPzkXbR)MD|NQ#SHA&(%I?x+_bQKOWKO#{ zusQ-k?+fD)4?>vChgHvz(t=;}K_+-t52Dl*#4iK%;tWxm*(l`plydM~^J>8oK;OLk@#`;~a*`O%gxLN=Mgcfu3-+t1nnK^Fxc(cGZQi&|ZuO(bnf$$)_O+rt{z3_MDIQVoi7n%X@%p9Ejpn2L>hObC1S3aws@xc}say55d zJUWhsdIz1KTQ8ov{0&XR@(oN?vI#>^T-ZSpA0vhWIS4vwmO^2Az#wi_1NPI>SwrbY zu3X>jl)blqlMYJ-ySwt@0IkrA5hWD)%P0~z^iCxaC8igv%%N{*<$7KWTR0!IQkyuK?3_iL8wOvS_?R34 f1hoT|_`h;1_M(vq&LLbY#lCgcLmIKLpk@F7y3KOQ literal 18392 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
!_}8Z+ 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 Mdd1KaS^xk50JTyabN~PV diff --git a/js/bullet.js b/js/bullet.js index 0f32794..cdf687d 100644 --- a/js/bullet.js +++ b/js/bullet.js @@ -5500,21 +5500,10 @@ const b = { const push = 0.4 const reflectivity = 1 - 1 / (tech.laserReflections * 3) let damage = m.dmgScale * this.laserDamage * tech.laserDamage - let best = { - x: 1, - y: 1, - dist2: Infinity, - who: null, - v1: 1, - v2: 1 - }; - const path = [{ - x: this.vertices[0].x, - y: this.vertices[0].y - }, { - x: this.lockedOn.position.x, - y: this.lockedOn.position.y - }]; + //make the laser wiggle as it aims at the target + let best = { x: 1, y: 1, dist2: Infinity, who: null, v1: 1, v2: 1 }; + const perp2 = Vector.mult(Vector.rotate({ x: 1, y: 0 }, m.angle + Math.PI / 2), 0.6 * this.lockedOn.radius * Math.sin(simulation.cycle / this.lookFrequency)) + const path = [{ x: this.vertices[0].x, y: this.vertices[0].y }, Vector.add(this.lockedOn.position, perp2)]; const vertexCollision = function (v1, v1End, domain) { for (let i = 0; i < domain.length; ++i) { diff --git a/js/index.js b/js/index.js index 24aad57..f8467de 100644 --- a/js/index.js +++ b/js/index.js @@ -786,7 +786,7 @@ ${simulation.isCheating ? "

lore disabled" : ""} - + diff --git a/js/level.js b/js/level.js index 4d7bd77..e08f8ae 100644 --- a/js/level.js +++ b/js/level.js @@ -34,13 +34,13 @@ const level = { // b.giveGuns("nail gun") //0 nail gun 1 shotgun 2 super balls 3 wave 4 missiles 5 grenades 6 spores 7 drones 8 foam 9 harpoon 10 mine 11 laser // b.giveGuns("shotgun") //0 nail gun 1 shotgun 2 super balls 3 wave 4 missiles 5 grenades 6 spores 7 drones 8 foam 9 harpoon 10 mine 11 laser // b.guns[3].ammo = 100000000 - // tech.giveTech("bremsstrahlung") + // tech.giveTech("iridescence") // tech.giveTech("cherenkov radiation") // tech.giveTech("irradiated nails") // for (let i = 0; i < 6; ++i) tech.giveTech("Lorentz transformation") // for (let i = 0; i < 1; ++i) tech.giveTech("rivet gun") - // requestAnimationFrame(() => { for (let i = 0; i < 1; i++) tech.giveTech("foam-bot") }); - // for (let i = 0; i < 1; i++) tech.giveTech("foam-bot upgrade") + // requestAnimationFrame(() => { for (let i = 0; i < 30; i++) tech.giveTech("laser-bot") }); + // for (let i = 0; i < 1; i++) tech.giveTech("laser-bot upgrade") // for (let i = 0; i < 1; ++i) tech.giveTech("ternary") // for (let i = 0; i < 3; ++i) tech.giveTech("mechatronics") // for (let i = 0; i < 3; i++) powerUps.directSpawn(450, -50, "tech"); @@ -485,7 +485,6 @@ const level = { category: cat.body, mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet }, - isNoSetCollision: true, isNotHoldable: true, frictionAir: frictionAir, friction: 1, @@ -518,7 +517,6 @@ const level = { category: cat.body, mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet }, - isNoSetCollision: true, isNotHoldable: true, frictionAir: frictionAir, friction: 1, @@ -652,7 +650,6 @@ const level = { category: cat.body, //cat.map, mask: cat.map | cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet }, - isNoSetCollision: true, inertia: Infinity, //prevents rotation isNotHoldable: true, friction: 1, @@ -821,7 +818,6 @@ const level = { // category: cat.map, // mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet // }, - // isNoSetCollision: true, // }); // const rotor2 = Matter.Bodies.rectangle(x, y, width, radius, { // angle: Math.PI / 2, @@ -832,7 +828,6 @@ const level = { // category: cat.map, // mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet // }, - // isNoSetCollision: true, // }); // rotor = Body.create({ //combine rotor1 and rotor2 // parts: [rotor1, rotor2], @@ -841,7 +836,6 @@ const level = { // category: cat.map, // mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet // }, - // isNoSetCollision: true, // }); // Matter.Body.setPosition(rotor, { // x: x, @@ -892,7 +886,6 @@ const level = { const height = 15 body[body.length] = Bodies.rectangle(x + width / 2, y + height / 2, width, height, { friction: 0.05, frictionAir: 0.01 }); let flip = body[body.length - 1]; - flip.isNoSetCollision = true //prevents collision from being rewritten in level.addToWorld flip.collisionFilter.category = cat.body flip.collisionFilter.mask = cat.player | cat.body flip.isNotHoldable = true @@ -948,12 +941,13 @@ const level = { }, } }, - button(x, y, width = 126) { - spawn.mapVertex(x + 65, y + 2, "100 10 -100 10 -70 -10 70 -10"); - map[map.length - 1].restitution = 0; - map[map.length - 1].friction = 1; - map[map.length - 1].frictionStatic = 1; - + button(x, y, width = 126, isSpawnBase = true) { + if (isSpawnBase) { + spawn.mapVertex(x + 65, y + 2, "100 10 -100 10 -70 -10 70 -10"); + map[map.length - 1].restitution = 0; + map[map.length - 1].friction = 1; + map[map.length - 1].frictionStatic = 1; + } // const buttonSensor = Bodies.rectangle(x + 35, y - 1, 70, 20, { // isSensor: true // }); @@ -992,6 +986,25 @@ const level = { this.isUp = false; } }, + query() { + if (Matter.Query.region(body, this).length === 0 && Matter.Query.region([player], this).length === 0) { + this.isUp = true; + } else { + if (this.isUp === true) { + const list = Matter.Query.region(body, this) //are any blocks colliding with this + if (list.length > 0) { + if (list[0].bounds.max.x - list[0].bounds.min.x < 150 && list[0].bounds.max.y - list[0].bounds.min.y < 150) { //not too big of a block + Matter.Body.setPosition(list[0], { //teleport block to the center of the button + x: this.min.x + width / 2, + y: list[0].position.y + }) + } + Matter.Body.setVelocity(list[0], { x: 0, y: 0 }); + } + } + this.isUp = false; + } + }, draw() { ctx.fillStyle = "hsl(0, 100%, 70%)" if (this.isUp) { @@ -1035,7 +1048,6 @@ const level = { category: cat.map, mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet }, - isNoSetCollision: true, inertia: Infinity, //prevents rotation isNotHoldable: true, isNonStick: true, //this keep sporangium from sticking @@ -1187,7 +1199,6 @@ const level = { category: cat.body,//cat.map, mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet //cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet }, - isNoSetCollision: true, inertia: Infinity, //prevents rotation isNotHoldable: true, friction: 1, @@ -1210,25 +1221,6 @@ const level = { Matter.Body.setPosition(this, position) } } - // else { - // const blocks = Matter.Query.collides(this, body) - // console.log(blocks.length) - // for (let i = 0; i < blocks.length; i++) { - // // console.log(blocks[i]) - // if (blocks[i].bodyA) { - // if (touching[i].bodyB !== m.holdingTarget) { - // for (let j = 0, len = body.length; j < len; j++) { - // if (body[j] === touching[i].bodyB) { - // body.splice(j, 1); - // len-- - // Matter.Composite.remove(engine.world, touching[i].bodyB); - // break; - // } - // } - // } - // } - // } - // } } else { if (this.position.y > y - distance) { //try to open const position = { @@ -1248,9 +1240,7 @@ const level = { ctx.beginPath(); const v = this.vertices; ctx.moveTo(v[0].x, v[0].y); - for (let i = 1; i < v.length; ++i) { - ctx.lineTo(v[i].x, v[i].y); - } + for (let i = 1; i < v.length; ++i) ctx.lineTo(v[i].x, v[i].y); ctx.lineTo(v[0].x, v[0].y); ctx.fill(); } @@ -1260,6 +1250,69 @@ const level = { doorBlock.classType = "body" return doorBlock }, + doorMap(x, y, width, height, distance, speed = 20, addToWorld = true) { //for doors that use line of sight + x = x + width / 2 + y = y + height / 2 + const door = map[map.length] = Bodies.rectangle(x, y, width, height, { + collisionFilter: { + category: cat.map, + mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet, + // mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet //cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet + }, + inertia: Infinity, //prevents rotation + isNotHoldable: true, + friction: 1, + frictionStatic: 1, + restitution: 0, + isClosing: false, + openClose(isSetPaths = false) { + if (!m.isBodiesAsleep) { + if (this.isClosing) { + if (this.position.y < y) { //try to close + if ( //if clear of stuff + Matter.Query.collides(this, [player]).length === 0 && + Matter.Query.collides(this, body).length < 2 && + Matter.Query.collides(this, mob).length === 0 + ) { + const position = { + x: this.position.x, + y: this.position.y + speed + } + Matter.Body.setPosition(this, position) + if (isSetPaths) simulation.draw.setPaths() + } + } + } else { + if (this.position.y > y - distance) { //try to open + const position = { + x: this.position.x, + y: this.position.y - speed + } + Matter.Body.setPosition(this, position) + if (isSetPaths) simulation.draw.setPaths() + } + } + } + }, + isClosed() { + return this.position.y > y - 1 + }, + draw() { + ctx.fillStyle = "#666" + ctx.beginPath(); + const v = this.vertices; + ctx.moveTo(v[0].x, v[0].y); + for (let i = 1; i < v.length; ++i) ctx.lineTo(v[i].x, v[i].y); + ctx.lineTo(v[0].x, v[0].y); + ctx.fill(); + } + }); + + Matter.Body.setStatic(door, true); //make static + if (addToWorld) Composite.add(engine.world, door); //add to world + door.classType = "map" + return door + }, portal(centerA, angleA, centerB, angleB) { const width = 50 const height = 150 @@ -1572,7 +1625,6 @@ const level = { category: cat.map, mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet //cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet }, - isNoSetCollision: true, inertia: Infinity, //prevents rotation isNotHoldable: true, friction: 0, @@ -1655,7 +1707,6 @@ const level = { category: cat.body, mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet //cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet }, - isNoSetCollision: true, inertia: Infinity, //prevents rotation isNotHoldable: true, friction: 0, @@ -2534,8 +2585,6 @@ const level = { // level.difficultyIncrease(10 * 4); // m.maxHealth = m.health = 100 - - const mobSpawnCap = mobs.mobDeaths + 100 level.isProcedural = true //used in generating text for the level builder simulation.draw.drawMapPath = simulation.draw.drawMapSight @@ -2543,13 +2592,12 @@ const level = { document.body.style.backgroundColor = "#e3e3e3"//"#e3e3e3"//color.map//"#333"//"#000" level.defaultZoom = 1400 simulation.zoomTransition(level.defaultZoom) - level.setPosToSpawn(1400 * (Math.random() < 0.5 ? 1 : -1), -250); //normal spawn + level.setPosToSpawn(450 * (Math.random() < 0.5 ? 1 : -1), -300); //normal spawn // spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); //entrance bump disabled for performance level.exit.x = 0; level.exit.y = -9000; // spawn.mapRect(level.exit.x, level.exit.y + 20, 100, 100); //exit bump disabled for performance - const stationWidth = 9000 let stationNumber = 0; let stationCustom = () => { } @@ -2562,43 +2610,23 @@ const level = { train[train.length - 1].isMoving = false train[train.length - 1].stops = { left: -7225, right: -1725 } - const stationList = [] //organize the possible stations into a random order, with one station removed - for (let i = 0, totalNumberOfStations = 5; i < totalNumberOfStations; ++i) stationList.push(i) //!!!! update station number when you add a new station + const stationList = [] //use to randomize station order + for (let i = 1, totalNumberOfStations = 8; i < totalNumberOfStations; ++i) stationList.push(i) //!!!! update station number when you add a new station shuffle(stationList); - stationList.splice(Math.floor(Math.random() * stationList.length), 1); //remove one random element from array + stationList.splice(0, 3); //remove some stations to keep it to 4 stations + stationList.unshift(0) //add index zero to the front of the array + let isExitOpen = false + let gatesOpenRight = -1 + let gatesOpenLeft = -1 const infrastructure = (x, isInProgress = true) => { if (isInProgress) { spawn.setSpawnList(); //picks a couple mobs types for a themed random mob spawns - - const mobsLeft = Math.floor(mobSpawnCap - mobs.mobDeaths - 50 / simulation.difficultyMode) - if (mobsLeft > 0) { - simulation.makeTextLog(`${mobsLeft} mobs left before exit opens`); - } else { - isExitOpen = true - simulation.makeTextLog(`exit opened`); - } - function removeAll(array) { for (let i = 0; i < array.length; ++i) Matter.Composite.remove(engine.world, array[i]); } removeAll(map); map = []; - // removeAll(body); //can't remove bodies because it removes the train - // body = []; - // removeAll(mob); - // mob = []; - // removeAll(bullet); - // bullet = []; - // removeAll(composite); - // composite = []; - // removeAll(cons); // don't allow constraints that don't come from a mob (like elevators, rotors?) - // cons = []; - // removeAll(consBB); // don't allow constraints that don't come from a mob (like elevators, rotors?) - // consBB = []; - // removeAll(powerUp); - // powerUp = []; - //remove any powerUp that is too far from player for (let i = 0; i < powerUp.length; ++i) { if (Vector.magnitudeSquared(Vector.sub(player.position, powerUp[i].position)) > 9000000) { //remove any powerUp farther then 3000 pixels from player @@ -2617,15 +2645,293 @@ const level = { mob.splice(i--, 1) } } - } else { - for (let i = 0; i < 8; ++i) powerUps.chooseRandomPowerUp(100 * (Math.random() - 0.5), -200 - 100 * Math.random())//only spawn heal or ammo once at the first station + } + const checkGate = (gate, gateButton) => { + if (gate) { //check status of buttons and gates + gate.isClosing = gateButton.isUp + gate.openClose(true); + if (gateButton.isUp) { + gateButton.query(); + if (!gateButton.isUp) { + if (stationNumber > 0) { + gatesOpenRight = stationNumber + } else if (stationNumber < 0) { + gatesOpenLeft = stationNumber + } else { //starting station both doors open + gatesOpenLeft = stationNumber + gatesOpenRight = stationNumber + } + if (Math.abs(stationNumber) > 0 && ((Math.abs(stationNumber) + 1) % stationList.length) === 0) { + simulation.makeTextLog(`exit opened`); + isExitOpen = true; + } + } + } + gateButton.draw(); + } } const stations = [ //update totalNumberOfStations as you add more stations - () => { //slime + () => { //empty starting station if (isExitOpen) { - level.exit.x = x - 665; - level.exit.y = -920; + level.exit.x = x - 50; + level.exit.y = -260; + } else { + var gateButton = level.button(x - 62, -237, 125, false) //x, y, width = 126, isSpawnBase = true + gateButton.isUp = true + if (stationNumber === 0 && gatesOpenRight === -1 && gatesOpenLeft === -1) { + var gateR = level.doorMap(x + 1375, -525, 50, 375, 300, 20, false) //x, y, width, height, distance, speed = 20 + var gateL = level.doorMap(x - 1375, -525, 50, 375, 300, 20, false) //x, y, width, height, distance, speed = 20 + for (let i = 0; i < 10; ++i) powerUps.chooseRandomPowerUp(x + 800 * (Math.random() - 0.5), -300 - 100 * Math.random())//only spawn heal or ammo once at the first station + } } + + spawn.mapRect(x + -1400, -750, 3375, 100); //roof + spawn.mapRect(x + -1500, -210, 3000, 400);//station floor + // spawn.mapRect(x + -550, -220, 1125, 100); //floor + // spawn.mapRect(x + -475, -230, 975, 150);//floor + spawn.mapVertex(x + 0, -200, "400 0 -400 0 -300 -80 300 -80"); //hexagon but wide + + // spawn.mapRect(x + -1350, -550, 50, 150); + // spawn.mapRect(x + 1300, -550, 50, 150); + stationCustom = () => { }; + stationCustomTopLayer = () => { + checkGate(gateR, gateButton) + checkGate(gateL, gateButton) + }; + }, + () => { //portal maze + const buttonsCoords = [{ x: x + 50, y: -1595 }, { x: x + 637, y: -2195 }, { x: x - 1487, y: -2145 }] + const buttonsCoordsIndex = Math.floor(Math.random() * buttonsCoords.length) //pick a random element from the array + if (isExitOpen) { + level.exit.x = buttonsCoords[buttonsCoordsIndex].x; + level.exit.y = buttonsCoords[buttonsCoordsIndex].y - 25; + } else { + var gateButton = level.button(buttonsCoords[buttonsCoordsIndex].x, buttonsCoords[buttonsCoordsIndex].y, 126, false) //x, y, width = 126, isSpawnBase = true + gateButton.isUp = true + if (stationNumber > gatesOpenRight) { + var gate = level.doorMap(x + 1375, -525, 50, 375, 300, 20, false) //x, y, width, height, distance, speed = 20 + } else if (stationNumber < gatesOpenLeft) { + var gate = level.doorMap(x - 1375, -525, 50, 375, 300, 20, false) //x, y, width, height, distance, speed = 20 + } + } + + spawn.mapRect(x + -1500, -210, 3000, 400);//station floor + spawn.mapRect(x + -1775, -1600, 3400, 1100); //center pillar + spawn.mapRect(x + -4100, -3325, 8000, 700); //roof + spawn.mapRect(x + -4100, -3325, 325, 1500); + spawn.mapRect(x + 3500, -3325, 400, 1500); + spawn.mapRect(x + -225, -575, 450, 425); //lower portal blocks + + //upper parts + spawn.mapRect(x + -1425, -2400, 1900, 50); + spawn.mapRect(x + -775, -2750, 575, 1045); + spawn.mapRect(x + 475, -1900, 450, 375); + spawn.mapRect(x + 2225, -2300, 125, 350); + spawn.mapRect(x + 2550, -2350, 700, 50); + spawn.mapRect(x + 1375, -2850, 125, 650); + spawn.mapRect(x + 600, -2200, 200, 195); + spawn.mapRect(x + -3500, -2275, 825, 75); + spawn.mapRect(x + -1550, -2150, 250, 250); + spawn.mapRect(x + -2575, -2450, 275, 345); + + if (!isExitOpen) { + if (Math.random() < 0.5) { + spawn.randomMob(x + 2850, -2425, 0); + spawn.randomMob(x + 2275, -2425, 0); + spawn.randomMob(x + 2000, -2150, 0); + spawn.randomMob(x + 1650, -2150, 0); + spawn.randomMob(x + 1000, -2475, 0); + spawn.randomMob(x + 725, -2450, 0); + spawn.randomMob(x + 525, -2175, 0); + spawn.randomMob(x + 200, -1950, 0); + spawn.randomMob(x + -25, -1825, 0); + spawn.randomMob(x + -975, -2000, 0); + spawn.randomMob(x + -1500, -2225, 0); + spawn.randomMob(x + 1850, -2125, 0); + spawn.randomMob(x + 225, -1975, 0); + spawn.randomMob(x + 25, -1950, 0); + spawn.randomMob(x + 25, -1950, 0); + } else { + spawn.randomMob(x + 250, -1850, 0); + spawn.randomMob(x + 225, -1950, 0); + spawn.randomMob(x + 125, -2000, 0); + spawn.randomMob(x + 0, -1800, 0); + spawn.randomMob(x + -1725, -2300, 0); + spawn.randomMob(x + -2025, -2175, 0); + spawn.randomMob(x + -2050, -2250, 0); + spawn.randomMob(x + -2000, -2350, 0); + spawn.randomMob(x + -2950, -2400, 0); + spawn.randomMob(x + -2775, -2400, 0); + spawn.randomMob(x + -2425, -2550, 0); + spawn.randomMob(x + 1950, -2225, 0); + spawn.randomMob(x + -2700, -2100, 0); + spawn.randomMob(x + -1925, -2175, 0); + spawn.randomMob(x + -825, -2050, 0); + } + } + + const portal1 = level.portal({ x: x - 250, y: -310 }, Math.PI, + { x: x + -3750, y: -2100 }, 0) + const portal2 = level.portal({ x: x + 250, y: -310 }, 0, + { x: x + 3475, y: -2100 }, Math.PI) + const portal3 = level.portal({ x: x - 800, y: -2500 }, Math.PI, + { x: x - 175, y: -2500 }, 0) + const portal4 = level.portal({ x: x + 1275, y: -1700 }, Math.PI, + { x: x - 1275, y: -1700 }, 0) + stationCustom = () => { + portal1[2].query() + portal1[3].query() + portal2[2].query() + portal2[3].query() + portal3[2].query() + portal3[3].query() + portal4[2].query() + portal4[3].query() + } + stationCustomTopLayer = () => { + checkGate(gate, gateButton) + portal1[0].draw(); + portal1[1].draw(); + portal1[2].draw(); + portal1[3].draw(); + portal2[0].draw(); + portal2[1].draw(); + portal2[2].draw(); + portal2[3].draw(); + portal3[0].draw(); + portal3[1].draw(); + portal3[2].draw(); + portal3[3].draw(); + portal4[0].draw(); + portal4[1].draw(); + portal4[2].draw(); + portal4[3].draw(); + } + }, + () => { //opening and closing doors + const buttonsCoords = [{ x: x - 800, y: -2245 }, { x: x + 250, y: -870 }, { x: x + 1075, y: -1720 }, { x: x - 1600, y: -1995 }] + const buttonsCoordsIndex = Math.floor(Math.random() * buttonsCoords.length) //pick a random element from the array + if (isExitOpen) { + level.exit.x = buttonsCoords[buttonsCoordsIndex].x; + level.exit.y = buttonsCoords[buttonsCoordsIndex].y - 25; + } else { + var gateButton = level.button(buttonsCoords[buttonsCoordsIndex].x, buttonsCoords[buttonsCoordsIndex].y, 126, false) //x, y, width = 126, isSpawnBase = true + gateButton.isUp = true + if (stationNumber > gatesOpenRight) { + var gate = level.doorMap(x + 1375, -525, 50, 375, 300, 20, false) //x, y, width, height, distance, speed = 20 + } else if (stationNumber < gatesOpenLeft) { + var gate = level.doorMap(x - 1375, -525, 50, 375, 300, 20, false) //x, y, width, height, distance, speed = 20 + } + } + + if (!isExitOpen) { + if (Math.random() < 0.5) { + spawn.randomMob(x + 1125, -650, 0); + spawn.randomMob(x + 150, -950, 0); + spawn.randomMob(x + 100, -975, 0); + spawn.randomMob(x + 75, -975, 0); + spawn.randomMob(x + 275, -1225, 0); + spawn.randomMob(x + 825, -975, 0); + spawn.randomMob(x + -50, -1625, 0); + spawn.randomMob(x + -950, -1550, 0); + spawn.randomMob(x + -975, -1550, 0); + spawn.randomMob(x + -900, -2500, 0); + spawn.randomMob(x + -975, -2550, 0); + spawn.randomMob(x + 675, -1950, 0); + spawn.randomMob(x + 675, -2550, 0); + spawn.randomMob(x + 1225, -1825, 0); + spawn.randomMob(x + -750, -2450, 0); + spawn.randomMob(x + -700, -825, 0); + } else { + spawn.randomMob(x + -675, -675, 0); + spawn.randomMob(x + -575, -925, 0); + spawn.randomMob(x + -425, -1100, 0); + spawn.randomMob(x + -225, -1225, 0); + spawn.randomMob(x + -650, -1250, 0); + spawn.randomMob(x + -675, -775, 0); + spawn.randomMob(x + 75, -1000, 0); + spawn.randomMob(x + -1100, -1575, 0); + spawn.randomMob(x + -1250, -1850, 0); + spawn.randomMob(x + -1625, -2100, 0); + spawn.randomMob(x + -700, -2500, 0); + spawn.randomMob(x + -375, -2550, 0); + spawn.randomMob(x + 250, -2025, 0); + spawn.randomMob(x + 675, -2175, 0); + spawn.randomMob(x + -1000, -2000, 0); + spawn.randomMob(x + -1550, -2325, 0); + spawn.randomMob(x + -1725, -2425, 0); + } + } + + spawn.mapRect(x + -1500, -210, 3000, 400);//station floor + spawn.mapRect(x + -2550, -3200, 425, 1375);//roof left wall + spawn.mapRect(x + 2125, -3175, 450, 1375);//roof right wall + spawn.mapRect(x + -2550, -3200, 5125, 225);//roof + + spawn.mapRect(x + -1325, -550, 1375, 50);//first floor roof/ground + spawn.mapRect(x + 775, -550, 675, 50); + spawn.mapRect(x + -200, -875, 1300, 50); //2nd floor roof/ground + spawn.mapRect(x + -125, -1125, 50, 275); + spawn.mapRect(x + -125, -1150, 800, 50); //3rd floor roof/ground + spawn.mapRect(x + -1450, -1475, 1600, 50); + spawn.mapRect(x + -1325, -1725, 800, 50); //4th floor roof/ground + spawn.mapRect(x + 50, -1725, 1350, 50); + spawn.mapRect(x + -1125, -2250, 700, 50); + + spawn.mapRect(x, -525, 50, 150); //door cap for ground at ground y = -210 + const door1 = level.doorMap(x + 12, -380, 25, 170, 140, 20, false) //x, y, width, height, distance, speed = 20 + spawn.mapRect(x - 200, -525 - 340, 50, 150); //door cap for ground at ground y = -210 + const door2 = level.doorMap(x - 188, -380 - 340, 25, 170, 140, 20, false) //x, y, width, height, distance, speed = 20 + spawn.mapRect(x + 100, -525 - 940, 50, 150); //door cap for ground at ground y = -210 + const door3 = level.doorMap(x + 112, -380 - 940, 25, 170, 140, 20, false) //x, y, width, height, distance, speed = 20 + spawn.mapRect(x + 450, -3050, 50, 775); + const door4 = level.doorMap(x + 462, -2300, 25, 575, 520, 30, false) //x, y, width, height, distance, speed = 20 + + const portal1 = level.portal({ + x: x + 2100, + y: -2100 + }, Math.PI, { //right + x: x + -1275, + y: -650 + }, 2 * Math.PI) //right + + stationCustom = () => { + door1.isClosing = (simulation.cycle % 240) < 120 + door1.openClose(true); + door2.isClosing = (simulation.cycle % 240) > 120 + door2.openClose(true); + door3.isClosing = (simulation.cycle % 240) < 120 + door3.openClose(true); + door4.isClosing = (simulation.cycle % 240) > 120 + door4.openClose(true); + portal1[2].query() + portal1[3].query() + } + stationCustomTopLayer = () => { + checkGate(gate, gateButton) + portal1[0].draw(); + portal1[1].draw(); + portal1[2].draw(); + portal1[3].draw(); + } + }, + () => { //slime + const buttonsCoords = [{ x: x - 675, y: -895 }, { x: x - 750, y: -70 }, { x: x + 75, y: -570 },] + const buttonsCoordsIndex = Math.floor(Math.random() * buttonsCoords.length) //pick a random element from the array + if (isExitOpen) { + level.exit.x = buttonsCoords[buttonsCoordsIndex].x; + level.exit.y = buttonsCoords[buttonsCoordsIndex].y - 25; + } else { + var gateButton = level.button(buttonsCoords[buttonsCoordsIndex].x, buttonsCoords[buttonsCoordsIndex].y, 126, false) //x, y, width = 126, isSpawnBase = true + gateButton.isUp = true + if (stationNumber > gatesOpenRight) { + var gate = level.doorMap(x + 1375, -525, 50, 375, 300, 20, false) //x, y, width, height, distance, speed = 20 + } else if (stationNumber < gatesOpenLeft) { + var gate = level.doorMap(x - 1375, -525, 50, 375, 300, 20, false) //x, y, width, height, distance, speed = 20 + } + } + + spawn.mapRect(x + -1575, -2000, 3025, 100); //roof // spawn.mapRect(x + -1575, -2200, 3025, 300); //roof // spawn.mapRect(x + -1500, -210, 3000, 400);//station floor @@ -2646,7 +2952,7 @@ const level = { spawn.mapRect(x - 925, -75, 875, 150); spawn.mapRect(x + 475, -1400, 75, 1250); - if (mobs.mobDeaths < mobSpawnCap) { + if (!isExitOpen) { if (Math.random() < 0.5) { spawn.randomMob(x + -850, -450, 0); spawn.randomMob(x + -850, -125, 0); @@ -2694,6 +3000,8 @@ const level = { // drip3.draw(); } stationCustomTopLayer = () => { + checkGate(gate, gateButton) + hazard1.query(); hazard1.level(isSlimeRiseUp, 1.5) if (!(hazard1.height < hazard1.maxHeight)) { @@ -2705,11 +3013,24 @@ const level = { boost2.query(); } }, - () => { //portals + () => { //portal fling + const buttonsCoords = [{ x: x + 775, y: -1695 }, { x: x - 775, y: -800 }, { x: x - 375, y: -2083 },] + const buttonsCoordsIndex = Math.floor(Math.random() * buttonsCoords.length) //pick a random element from the array if (isExitOpen) { - level.exit.x = x + 950; - level.exit.y = -1725; + level.exit.x = buttonsCoords[buttonsCoordsIndex].x; + level.exit.y = buttonsCoords[buttonsCoordsIndex].y - 25; + } else { + var gateButton = level.button(buttonsCoords[buttonsCoordsIndex].x, buttonsCoords[buttonsCoordsIndex].y, 126, false) //x, y, width = 126, isSpawnBase = true + gateButton.isUp = true + if (stationNumber > gatesOpenRight) { + var gate = level.doorMap(x + 1375, -525, 50, 375, 300, 20, false) //x, y, width, height, distance, speed = 20 + } else if (stationNumber < gatesOpenLeft) { + var gate = level.doorMap(x - 1375, -525, 50, 375, 300, 20, false) //x, y, width, height, distance, speed = 20 + } } + spawn.mapRect(x + -1600, -3450, 300, 1475); //roof + spawn.mapRect(x + -1600, -3450, 3225, 100); + spawn.mapRect(x + 1300, -3450, 325, 1525); spawn.mapVertex(x + 400, -180, "-300 0 -300 -100 300 -100 400 0"); spawn.mapVertex(x - 400, -180, "300 0 300 -100 -300 -100 -400 0"); @@ -2719,12 +3040,11 @@ const level = { spawn.mapRect(x + 125, -700, 1225, 200); spawn.mapRect(x + -1325, -1775, 775, 175); spawn.mapVertex(x + 445, -800, "-200 0 -200 -300 100 -300 185 0"); - spawn.mapVertex(x - 350, -1850, "-185 0 -100 -400 400 -400 400 0"); + spawn.mapVertex(x - 310, -1880, "-185 0 -100 -400 400 -400 400 0"); spawn.mapVertex(x + -675, -725, "325 0 250 80 -250 80 -325 0 -250 -80 250 -80"); - spawn.mapRect(x + 650, -1700, 725, 550); - spawn.mapRect(x + 1500, -2375, 275, 450); + spawn.mapRect(x + 625, -1700, 750, 500); - if (mobs.mobDeaths < mobSpawnCap) { + if (!isExitOpen) { spawn.randomMob(x + -750, -1925, 0); spawn.randomMob(x + -425, -2300, 0); spawn.randomMob(x + -350, -2200, 0); @@ -2745,10 +3065,10 @@ const level = { const portal1 = level.portal({ x: x + 0, y: -200 - }, -Math.PI / 2, { //right + }, -Math.PI / 2, { //up x: x + 200, y: -900 - }, -Math.PI / 2) //right + }, -Math.PI / 2) //up const portal2 = level.portal({ x: x + 1275, y: -800 @@ -2764,6 +3084,7 @@ const level = { portal2[3].query() } stationCustomTopLayer = () => { + checkGate(gate, gateButton) portal1[0].draw(); portal1[1].draw(); portal1[2].draw(); @@ -2775,10 +3096,21 @@ const level = { } }, () => { //tower levels and squares + const buttonsCoords = [{ x: x - 300, y: -3120 }, { x: x + 600, y: -3020 }, { x: x - 575, y: -1770 }, { x: x - 450, y: -2370 }] + const buttonsCoordsIndex = Math.floor(Math.random() * buttonsCoords.length) //pick a random element from the array if (isExitOpen) { - level.exit.x = x - 450; - level.exit.y = -3150; + level.exit.x = buttonsCoords[buttonsCoordsIndex].x; + level.exit.y = buttonsCoords[buttonsCoordsIndex].y - 25; + } else { + var gateButton = level.button(buttonsCoords[buttonsCoordsIndex].x, buttonsCoords[buttonsCoordsIndex].y, 126, false) //x, y, width = 126, isSpawnBase = true + gateButton.isUp = true + if (stationNumber > gatesOpenRight) { + var gate = level.doorMap(x + 1375, -525, 50, 375, 300, 20, false) //x, y, width, height, distance, speed = 20 + } else if (stationNumber < gatesOpenLeft) { + var gate = level.doorMap(x - 1375, -525, 50, 375, 300, 20, false) //x, y, width, height, distance, speed = 20 + } } + spawn.mapRect(x + -1500, -210, 3000, 400);//station floor spawn.mapRect(x + -1625, -3950, 3225, 350);//roof spawn.mapRect(x + 1300, -3850, 300, 2150); //roof wall @@ -2869,20 +3201,28 @@ const level = { }, ] - if (mobs.mobDeaths < mobSpawnCap) mobPlacement[Math.floor(Math.random() * mobPlacement.length)]()//different random mob placements, with mobs clustered to surprise player + if (!isExitOpen) mobPlacement[Math.floor(Math.random() * mobPlacement.length)]()//different random mob placements, with mobs clustered to surprise player stationCustom = () => { } - stationCustomTopLayer = () => { } + stationCustomTopLayer = () => { + checkGate(gate, gateButton) + } }, () => { //jump pads and 6 sided platforms + const buttonsCoords = [{ x: x + 275, y: -1817 }, { x: x + 2025, y: -1995 }, { x: x - 2025, y: -2420 }, { x: x - 2100, y: -1995 }] + const buttonsCoordsIndex = Math.floor(Math.random() * buttonsCoords.length) //pick a random element from the array if (isExitOpen) { - if (Math.random() < 0.5) { - level.exit.x = x - 2075; - level.exit.y = -2450; - } else { - level.exit.x = x + 2250; - level.exit.y = -2020; + level.exit.x = buttonsCoords[buttonsCoordsIndex].x; + level.exit.y = buttonsCoords[buttonsCoordsIndex].y - 25; + } else { + var gateButton = level.button(buttonsCoords[buttonsCoordsIndex].x, buttonsCoords[buttonsCoordsIndex].y, 126, false) //x, y, width = 126, isSpawnBase = true + gateButton.isUp = true + if (stationNumber > gatesOpenRight) { + var gate = level.doorMap(x + 1375, -525, 50, 375, 300, 20, false) //x, y, width, height, distance, speed = 20 + } else if (stationNumber < gatesOpenLeft) { + var gate = level.doorMap(x - 1375, -525, 50, 375, 300, 20, false) //x, y, width, height, distance, speed = 20 } } + spawn.mapRect(x + -1500, -210, 3000, 400);//station floor spawn.mapRect(x + -3200, -3200, 300, 1400); //roof left wall spawn.mapRect(x + 2600, -3200, 300, 1400);//roof right wall @@ -2952,21 +3292,32 @@ const level = { spawn.randomMob(x + -600, -375, 0); }, ] - if (mobs.mobDeaths < mobSpawnCap) mobPlacement[Math.floor(Math.random() * mobPlacement.length)]()//different random mob placements, with mobs clustered to surprise player + if (!isExitOpen) mobPlacement[Math.floor(Math.random() * mobPlacement.length)]()//different random mob placements, with mobs clustered to surprise player const boost1 = level.boost(x - 50, -225, 790) const boost2 = level.boost(x + 550, -985, 900) const boost3 = level.boost(x + -850, -835, 1900) stationCustom = () => { } stationCustomTopLayer = () => { + checkGate(gate, gateButton) boost1.query(); boost2.query(); boost3.query(); } }, () => { //crouch tunnels + const buttonsCoords = [{ x: x + 625, y: -1395 }, { x: x - 15, y: -1595 }, { x: x - 800, y: -1295 }] + const buttonsCoordsIndex = Math.floor(Math.random() * buttonsCoords.length) //pick a random element from the array if (isExitOpen) { - level.exit.x = x + 0; - level.exit.y = -1620; + level.exit.x = buttonsCoords[buttonsCoordsIndex].x; + level.exit.y = buttonsCoords[buttonsCoordsIndex].y - 25; + } else { + var gateButton = level.button(buttonsCoords[buttonsCoordsIndex].x, buttonsCoords[buttonsCoordsIndex].y, 126, false) //x, y, width = 126, isSpawnBase = true + gateButton.isUp = true + if (stationNumber > gatesOpenRight) { + var gate = level.doorMap(x + 1375, -525, 50, 375, 300, 20, false) //x, y, width, height, distance, speed = 20 + } else if (stationNumber < gatesOpenLeft) { + var gate = level.doorMap(x - 1375, -525, 50, 375, 300, 20, false) //x, y, width, height, distance, speed = 20 + } } spawn.mapRect(x + -1500, -210, 3000, 400);//station floor spawn.mapRect(x + -1575, -2200, 3025, 300); //roof @@ -2981,13 +3332,12 @@ const level = { spawn.mapRect(x + -225, -525, 800, 210); spawn.mapRect(x + -100, -1600, 300, 193); spawn.mapRect(x + 925, -1250, 75, 75); - spawn.bodyRect(x + 625, -1550, 150, 150, 0.3); spawn.bodyRect(x + 200, -1475, 75, 175, 0.3); spawn.bodyRect(x + -25, -625, 225, 100, 0.3); spawn.bodyRect(x + -1000, -750, 125, 175, 0.3); spawn.bodyRect(x + -625, -1450, 75, 150, 0.3); spawn.bodyRect(x + -650, -300, 300, 75, 0.3); - if (mobs.mobDeaths < mobSpawnCap) { + if (!isExitOpen) { spawn.randomMob(x + -750, -1425, 0); spawn.randomMob(x + -1050, -1100, 0); spawn.randomMob(x + -825, -750, 0); @@ -3006,6 +3356,7 @@ const level = { } stationCustom = () => { } stationCustomTopLayer = () => { + checkGate(gate, gateButton) ctx.fillStyle = "rgba(0,0,0,0.08)" ctx.fillRect(x + -225, -325, 800, 125); ctx.fillRect(x + -100, -1425, 300, 125); @@ -3013,8 +3364,8 @@ const level = { } }, ] - // stations[0]() //for testing a specific station - stations[stationList[Math.abs((stationNumber + Math.floor(stationList.length / 2)) % stationList.length)]]() //*************** run this one when uploading + // stations[1]() //for testing a specific station + stations[stationList[Math.abs(stationNumber % stationList.length)]]() //*************** run this one when uploading //add in standard station map infrastructure spawn.mapRect(x + -8000, 0, 16000, 800);//tunnel floor spawn.mapRect(x + 1500 - 200, -2000, 6400, 1500); //tunnel roof @@ -3073,9 +3424,11 @@ const level = { //track what station the player is in if (m.pos.x > 0.55 * stationWidth + stationNumber * stationWidth) { stationNumber++ + // if ((stationNumber % stationList.length) == 0) stationNumber++ //skip the stationNumber that is the modulus of the length of the stationList infrastructure(stationNumber * stationWidth) } else if (m.pos.x < -0.55 * stationWidth + stationNumber * stationWidth) { stationNumber-- + // if ((stationNumber % stationList.length) == 0) stationNumber--//skip the stationNumber that is the modulus of the length of the stationList infrastructure(stationNumber * stationWidth) } stationCustom() @@ -17029,7 +17382,6 @@ const level = { category: cat.body, mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet }, - isNoSetCollision: true, isNotHoldable: true, frictionAir: 0, friction: 1, @@ -17058,7 +17410,6 @@ const level = { category: cat.body, mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet }, - isNoSetCollision: true, isNotHoldable: true, frictionAir: 0.01, friction: 1, @@ -17120,7 +17471,6 @@ const level = { category: cat.body, mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet }, - isNoSetCollision: true, isNotHoldable: true, friction: 1, frictionStatic: 1, @@ -17183,7 +17533,6 @@ const level = { category: cat.body, mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet }, - isNoSetCollision: true, isNotHoldable: true, frictionAir: frictionAir, friction: 1, @@ -17196,7 +17545,6 @@ const level = { category: cat.body, mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet }, - isNoSetCollision: true, isNotHoldable: true, frictionAir: 0.01, friction: 1, @@ -17321,7 +17669,6 @@ const level = { category: cat.map, mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet //cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet }, - isNoSetCollision: true, inertia: Infinity, //prevents rotation isNotHoldable: true, friction: 1, @@ -17367,7 +17714,6 @@ const level = { category: cat.map, mask: cat.player | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet //cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet }, - isNoSetCollision: true, inertia: Infinity, //prevents rotation isNotHoldable: true, friction: 1, @@ -17943,7 +18289,7 @@ const level = { } var circleHead = Matter.Bodies.polygon(m.pos.x, m.pos.y, 0, 31); - var losDomain = generateIntersectMap().concat(mob.filter((obj) => { return obj.isNotCloaked == null && (obj.isBoss || obj.label != 'Circle Body') }), [pendulum1, gear1, gear2, player, circleHead]); + var losDomain = generateIntersectMap().concat(mob.filter((obj) => { return obj.isNotCloaked == null && (obj.isBoss || obj.label != 'Circle Body') }), [pendulum1, gear1, gear2, piston1, player, circleHead]); var oldMap = [...map]; var oldMob = [...mob]; var spawnGearMobCycle = 0; @@ -17959,7 +18305,7 @@ const level = { level.custom = () => { Matter.Body.setPosition(circleHead, m.pos) if (!(compareArrays(oldMap, map) && compareArrays(oldMob, mob))) { - losDomain = generateIntersectMap().concat(mob.filter((obj) => { return obj.isNotCloaked == null && (obj.isBoss || obj.label != 'Circle Body') }), [pendulum1, gear1, gear2, player, circleHead]); + losDomain = generateIntersectMap().concat(mob.filter((obj) => { return obj.isNotCloaked == null && (obj.isBoss || obj.label != 'Circle Body') }), [pendulum1, gear1, gear2, piston1, player, circleHead]); } oldMap = [...map]; oldMob = [...mob]; @@ -17976,8 +18322,7 @@ const level = { // light var lightPos = { x: 400, y: -2775 }; var lightRadius = 2950; - const vertices = circleLoS(lightPos, lightRadius, map.concat(mob.filter((obj) => { return obj.isNotCloaked == null && (obj.isBoss || obj.label != 'Circle Body') }), [pendulum1, gear1, gear2, player, circleHead])); - if (vertices.length > 0 && vertices[0].x) { + const vertices = circleLoS(lightPos, lightRadius, map.concat(mob.filter((obj) => { return obj.isNotCloaked == null && (obj.isBoss || obj.label != 'Circle Body') }), [pendulum1, gear1, gear2, piston1, player, circleHead])); if (vertices.length > 0 && vertices[0].x) { ctx.beginPath(); ctx.moveTo(vertices[0].x, vertices[0].y); for (var i = 1; i < vertices.length; i++) { diff --git a/js/tech.js b/js/tech.js index b45c964..141d7e4 100644 --- a/js/tech.js +++ b/js/tech.js @@ -6103,7 +6103,7 @@ const tech = { }, { name: "siphonaptera", - description: "sporangium and shotgun hatch fleas", + description: "spores metamorphose into fleas", isGunTech: true, maxCount: 1, count: 0, @@ -6123,7 +6123,7 @@ const tech = { }, { name: "nematodes", - description: "shotgun and sporangium hatch worms", + description: "spores metamorphose into worms", isGunTech: true, maxCount: 1, count: 0, diff --git a/todo.txt b/todo.txt index f6d76c9..0c693f0 100644 --- a/todo.txt +++ b/todo.txt @@ -1,14 +1,26 @@ ******************************************************** NEXT PATCH ************************************************** -new community level cantilever +subway + start in an empty station + station exits are blocked by a gate that opens when you press a button + level exit spawns after you clear 4 stations + added 2 new stations (7 possible stations) -extended tech sorting to experiment +laser-bots lasers wiggle as it aims at the target + this is a nerf to iridescence and it looks cool -acetone peroxide does 40->33% more explosion harm to player -fleas can do up to 30% more damage before they despawn +bug fixes *********************************************************** TODO ***************************************************** +only calculate setPaths on subway level? + does this mess with flatland, no + +improve flatland performance? + +get rid of the word permanent in bot tech + ersatz bots, perimeter defense, network effect + maybe reduce the fps on the line of sight graphics to make it look more like a sensor? make a bot the follows the player the pov for line of sight levels, not the player also need to make the vision a slice of a circle, not a full circle @@ -1228,6 +1240,7 @@ possible names for tech this is violated by expansion of the universe https://en.wikipedia.org/wiki/Cosmic_censorship_hypothesis - black holes can't leak Alcubierre warp drive (FTL with negative mass) + Spherules - A spherule is a small sphere or spherical body. It can also refer to a thick-walled spherical structure that contains endospores and occurs in the parasitic form of fungi ******************************************************** CARS IMAGES ********************************************************