From e9d226259e7878a2f735810980840a0f5d0fb2ab Mon Sep 17 00:00:00 2001 From: landgreen Date: Sat, 18 Nov 2023 17:49:01 -0800 Subject: [PATCH] grappling hook field grappling hook is now a field (work in progress) reworked physics to allow faster speeds, but more control improved rate of power up grabbing more player control to hook retraction rate changed hook shape and field image graphics grappling hook field coupling, more tech, bug fixes, and general polish to be added soon aerostat - 88->100% damage in air 22-> 25% damage on ground foam damage reduced 10%, ammo increased about 10% after hitting an invulnerable mob (drones,spores,worms,iceIX,fleas) don't die or lose cycles added JUNK tech: mobs! - summon 20 random mobs added announcement of mob names in console at start of new level bug fixes --- img/bulk modulus.webp | Bin 12740 -> 0 bytes img/field/grappling hook.webp | Bin 0 -> 40594 bytes img/grappling hook.webp | Bin 7280 -> 0 bytes js/bullet.js | 869 ++++++++++++++++++---------------- js/level.js | 34 +- js/player.js | 132 +++++- js/powerup.js | 83 ++-- js/tech.js | 180 ++++--- js/visibility.js | 44 -- todo.txt | 58 ++- 10 files changed, 800 insertions(+), 600 deletions(-) delete mode 100644 img/bulk modulus.webp create mode 100644 img/field/grappling hook.webp delete mode 100644 img/grappling hook.webp delete mode 100644 js/visibility.js diff --git a/img/bulk modulus.webp b/img/bulk modulus.webp deleted file mode 100644 index 736fe5b777f97c24a4f3da7689fc13f918a933ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12740 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 SshPc_t-s2M-hizEfd2t&2_j_x diff --git a/img/field/grappling hook.webp b/img/field/grappling hook.webp new file mode 100644 index 0000000000000000000000000000000000000000..1b1382642af90d3497b8dccbd7ea9df81fba9280 GIT binary patch literal 40594 zcmV(vKMM6+kP&godo&W&wAOW2LDu4k10X_r)PXGW2mUcWl8(q8u z_y%MZ79Hc^ygjVF7%N4HUhdExYc>O%zZz}>b#vw) z&HojDCOp7@a_&3+&Fm}uqxvW22FM40uP`~pe*WZWqPS9p!awA@BcrzAL`%z5Ai?f{ty4P{l52${tNwg{oi3<*uTbq*#8^*|MM^YxBmC| z|8@W6|J#4s|404fZx3_!N>6=|j?s$@BVn{xzY<&~L)t+WSBUD#&MNP8kvUq2P z<^7-eyV34(r*k{m|Fl1t601@IO>UZ4Fu1IXaGeQC!ERnshVOH4|MQ9(DnIC7S6=@7 z(x3dI8@;CQE*@CY9a)n(O+f7eeKCF>tf!@7GS0`wt7=_3BfRF;ZBiUPRvoa*Q5^k9 z*-rbLR?pPxI;Q5+q{LXEh7+*}v)NyOU%3|oC!e7SC#)jHADKs)O)Kz{(q4~Zu=#mlNi0u16WBE)jECtsr%5VXIKmEM zKSL-&q!}KcOcP^?(XwcAM=m0rxAK?i7$Kd?nA{E5?S#McA#;xk=FtRjp#b<$`d>AX zK)d<-Xs$8-Rd6pQu(#CLwUu|$80E4Pf{al(CcLxDq3lHK}Cz%{%;xb3?=2*PNs$^S|syUIT*?ykL(zGOC-T$YM7s-T1}vOOX@8mcRDb=^E+4I_wE|S1$TZa6tR6fwJ-z6c^#7ETPKoF|<6+q! zIrHNzdnDH4ackZgWBM1)A1sVp781i+LCq(n05zmF9(DJZ#o3v`;(N$TvdDhnV=K8P zHSqj>UI)}GmIgR>T||$Okl(U$sWjFnDmt^z-al2V_H5Az&ITMR>Z-Mb+@DF}HQ5W2 zfn-_#ytFAYkccxT>K`tvYcAq%Z2?KIGZL+PN4rGY{W_Zy(mT#Xy8ZV^%H7ab4s0mH z0Zqxc>sbQiznHoB;P|$y_^3dDa2JT&#YD3pHt+4BS=lQCeJg0Lv7?Wsg}HtB$xd%L zV|mB-#jxI^wc%gx2bo}eiORG=nl(dh2<2J^UHnb`qV(z zPbdk}m$PadG&0-IK#N~=VMApV;peVvr?M?#^Pnt~U}uG$7$%=cvJl%}Q^8R{)3!Jk zQEBMqca@QW`$!O-7wRuq?2M%ORAvp;iBdmZ-E$?x@lZyFyA1VaSlD|#zE^#P+mE*M zgV@{E(ic9tPTYibQbHtI%e&Y$3UIi0*^aJXAbwdbP3<8q3y&z}Y6QqQ;Rejs(x5pJ z*BI4?y+2zWJk3KG8WaQCxn(8SBSGvWr0mTRK{8z zj}w6ci;f;_MSlhgdfBC)BA~|0H&nmssiJoLU`b-Up%+hVGxmS_tDz8`3%XF8tvAyw z=EwOf!?1deu9@jR)7BI8%c9S#A#1LJU6-EWtHCr**`98w+5AR1hS5#Zi`J-3H5l(E zZPkb4v!iGxSN@JnB2)j>(95q8{e@Abfd4>MOHKT?gIPSmvLYnT!FpS7Ny*Pu<1ojH z05yhAM08=e(PCO|n7@td3CAR$a$6GjjHrT7tnfgT;VSmLjjRd~!TfyJIT#R_F;XW) z%Nx+Bixm(6Tg}P&$`nZwn7?LJo@$ik2i#n_h30~rS;nkbSMK%6%j0R~!YSyHxZoCx zIAN)JJGXEE{_;HS`5`Q7TcBIohPMd84q<`e?A z*noRtOsQl}6|1JrkMTk{&kap%|E%mNZNuI%>^kR3#rm2QR{@K$pvrKK#?E| z@JdY3BVGM$&%=5;+W)ufk7EC-l;+ z$}6p8qw0bGjS<798Y3L3xg;kOtb}`|$n)Z)LL)IguEkz3fR;V2-@i8CMCKD$L*Q!8 zyvG@+*#;QK$v-*BN=&w2T_!`Uh0Xve8B#x6SVH~X_@un+hXA=0CjUNL6CXF$f??`Q zUH;E^*HIRJY4a>&K&q*Uv-d5(EK%Rg#a`~ zNFu+@E++|5^gj~+b<>a-_qy*eTx`{5^k;%$jkzq^<+6ZEJ*D!#uQ7W@{Lr-?s3_oS zC~=sj^-uMnTog7UU+8pa_X5UpiK#@ER(qdYa!8oV%L0gl>ql=l|} zwMK9>%lbmFp*4#eac-SoShO47#G@*bkH`H9C#&Y+f|D%0+)+*(J$BHDP8i+>@9vq* zf+M7~k13fRx#yz}!8g0w6Vrrj2Va-R>wFC!HQM%IME*i`dm~zEiXBE_C!Xv3o2u1e z@-NQF=pcI?Pd{+9LBOV|qb|avL~RLJGA92cV?<%o%pngxFHi=%GiK23$PN~xq#F^b z98^*jA(RNZ+JFF&f40M_Krd*fdM_($>p5Q=259s3kRCx+pKA*WlW-_;_iz>AQ?oGn z&)L@7mmef5lpsm?2DmU>vOj+MP+P~hxFqKca8zgQu}uXLpz$x^{ZFbUzfbsn1h}?q zRi^nI06HS&_f6SN3|J?{X=^Odzk)dz7|jcE4~rW8nfa!r_ne`r2`O7tTHRhg=-hXj ze*Un!GU`U4>o2Wn8D}1HC4gS~kd2!h5FTNa&hkpaDNCZ)qA2_CSr5OZqJ!CqV%Zq& z?apR4cR|(Kyq{G+$c_=M*~+*BytS+)-05gk#F|E$?qZpoAdyo>*wJObJSPtuYJ?@8 z!l=p4|GELg5wRiGB~`*lCVU&QVd^30a$+y*@vi78lVU&_XW-a)Aw;G_#^X35kkB0tBXMQ@y)1@(}4 za0v^>pfW>uU@p+asG;Z!ln<)~uB4yH(vch#jl1fbwqN zOV!L`dN~n`pv^5_vP~TY9(6LLmX+?Nv-nWr2b7Z%iNbul<^&XezBIjS@h$3xKPlxL zojqy`rD$}@-K}k1z8)^!F{@#g(EC0++pccjx+9mQ-#kX!$18R}34aBg@RS$I>-(^K zMpm_}p#t|Bk5;_e6{~S2U#nORol-xxXgg)%x#r{RiXCmm(ET9D4rY817(D-Z?}G5N z`<3r}q({qqMU-`ie6?k%zsmg;OgXozC>i`x0q@RJ4EtBqR5h{7u!pUzumXu?yXfz7 zcbInU>o`hw$5qobXG{_HFh>#Z+CeptQ0rtQr$O7slQGP4YFHPG@hZCer*EqA$44|G zJxQLOpL$=&n6$GpWBU}Xr;&#XQyA{lI~b*7?sVmi`@({xslPiUsK_3eV-ls%Rt zHh&^AK{XQmrXgLl9SU=qyMjz_tf|b4$L-gL+J_$d1`lwPw8MI?;?-+`tYp}Eq$W`h z{fmska13UA)V{t;T(mO=tO#EC-W}@s?Hi;7wtei#osG*tl^Vt{&;Ub zVAHXBoK54=hrardIpwp+B&S&^zkQRkblVRV&NA(fLwokHyHk}P@gK`EjXRotIL8s& zn>yKtq?J5ZMjuH-uhj@XC&Ti;Fn^N$(i2S4t-$BR(<_Ro#J=Bn@At50NAP+ue06|z zHQ#%*Mo~jaYfWAh%4lpnz>{x<-{euQX~eVkws z^*O6Q^$!AoKy-h}FuxoH=a|O%m@{!po2j6B-|I&2+|?~*yCbdcM z%0U5X77u_cOaB&BFDpI8ooeD3jhDo_br5G7cKwg`BfWi;4uHXL>264I(#egwLDkW- z)B2c;z>KDfH~^uB3t}bV!Q~#<&V4_=!BFo%pxt6_AASp*eKmeG9c1g!GJ!@=w-N~z{AAdLtMVl{MaFH3eft3%>%=~ zNC7zXEhaTi&ithX#Q*>j2X0}@>v=sB+~8C$9B{;NIhEb~iOOLBH(C4Y@cIB%vGr62 z=yM94?sHEB(<&DHzG^4wwhajcj}p$XQ&Qa{Vf6FDGcyN4|_Q82+)ic~tg-N8Z8 z0(VmQfD>bvAN=(P!w&XXBJEA5xw7%5v<33<#Gxi1%hYEDkSkZRQ|F8KqAVQ8|MR^q zHNVvAZ|A7!HaRV{a_z0|e)3agABeBIbUr_jQO5a+iTWfk{jZJ3=^{9Cms8Og$M zrlCiB=d)3mkJqNyK&{glfs!N*N=NvS8xI+;bmG<1CH($g%3x8jJ^gR@?*JPhYvphl zS8CZ)<(QYddPDU(e6oaN6i~PhB17E$dYD}B!g2(y92d>5g->PeYHNvQjhGuM&%uV{ zE>Nk>Ta3bHf>PMoU#E!U-bf6`3PXXZSVv*r0imFD-sRkd(wAssYmPz`LzSY08}RB= zDF$FLC@}0(u?r((_AT@-F6&>_1tF@5Az1;Xn;_v=!Sus(zum`lk95&Xt_<@!7S z8-pbJ)<*)0V{Uxlg|TK15Qq8)$yn6`l%!v&Xf+I=kUvdK z7pmUCF90>5t2wsq?>?rfR>>6hST{pM@q%a(?YTOj^|omN_)4y@!*aHo^m9);3B!XP zl25D-$Pllcha!>arT?E_9A_}ku{n%OPaxM7x|memLRcg~(bM?RVAT?F0P||ZL(qKg z8&KQFrP@Y@CwihUnHGj+&oPuBX!>H0J;kOw{S@OaTr;%+Ep72OvxldhNVCvz>vy8~ z3RN2eD=kCr_L0vnE0fy+t{F3ZLR$ieaMgmQ>G;Js|4!ubg{+SnbrCun+6`%akothv zIP_K3uV$@oh^S^YQri|=C~%jF-r|K32}NHxPuX19z`a_`S6yu^(#}n$NL|78-3e8= zS=?PzvvskP%sYUT2aK9u7SUU#@abl5R46T|YoKFUmRV#(^rdeO;yg<8>Wfd>RmxRza@3om;e$A`> znmlsZcIaB1lP>C1ua$l)t%h+`-aaYwoMx^%vR~Xh4pr7xJOwB0heR~YMP2;{-U&#s74XpA#RggUcR5nNtUk|;m7=(5QZI)QpCt6O1RdAdBG4y$HByUnnZ8@hx)09n`l?A`>LF9JdyVO`fi z>bS6B*_Y7Vk7yL7>)$_}RwiXcw`+tDL?vEfo3zPRpkoqk5Ypn{g9EN*n2}%I;vYRz zkg`yHF+;lddEa8GAhc4pbT^hG#^{Dt)|tOrzqFiz;Q>N;6S$@_+6j&~F}3PavR8b>*CUz6#l zn0v7F#k($%!+9p*z2NClUZ-)AF_0haJGJV8S%nOE!XxLjwp z#trkbe&6~9wgotp6?U5?#)hnenC8P6hB$z;AMs}9+Wb85JTlWb3xV}A)4YkK$l&_d9L2eZ#PX?s^ zXDWTux;>^m&oQvOI^$2yPL@moS{)scAE5L6qbo$IbpH*V{@C%^_GBjFI_Ba2Jtkn@*A+n4Oyz{g`iq_pi}1BXp_%L+e&~Bc@NZ<=s7k1I z&^AD(6UYTDCC}>N-`q2Tf!-J*1)pU=CVQyba+HUbkI~6$5rFPADi6DB72ZZ+*h3oY zmbbgL57bPX!+~=|^h9IRaPuS@hQpLd`aMc>1b&PQh_51M&SC6-3iva6`vot)up^G!tE zxep&3LV5JKH2#H&$$O%A+o-3;9gSIxqNt8!_SkN6=lHlPUGMpDpYD^xJ$~@ewgx|N z|0?k>Xi%^bC4$c2Zdpnvx>wSKV32e#S2~J5jgAt?ll=*+CauRGzd8Rspua*HXXbgr z49GKFfe(O6q!pWqzp|50Fng<{Ng}XU`gIu>%UFM)x(*L#BIPr#I2i;$^)XenPs3a7 zq(g>}@IFDSaP|{mDSNr`uWTwG^2F-#>Z+f10ER>b4y@zj&vepD`K55}d$P3ad!)O4 zo_yUo$$NMnjLb2)IypTEP~vD%HEuv$1FYg+JBd8QfO`d9B`z5uh~3W9lP8h)lyoHo zs}9}+4UQA86pKmn5G}*1=iy}l0xcSme+19PT$z{^#PozyXth8bi8FyW%Fne|ASc_x zzC^H&V0d>L-z&T7zOw5S@;yJiGvnUSkfp_kc+}=@n>em>3cdi@GvO3Df!F{U>vN4# zPj0lk%!Zt)I3=NM^B98TZq3ho)A$=;u|)uv>9 zQhu3$Hg|=SjJlnS{uV~$)T#_Hm{#W<@tr;%r7Q@XHVZ310!x`q9O1FQT^Fi$`{n4vVZEG@HTVXrTX;B z=1`%`(Az<1xc%H?Tk-7GZRTRV(!V@NmycD%2VejFR@HZ7rj?}!(Gr{M?y`Z75k1$P z+p1oraL`vXNhEI^H}@>mxKkG~7`}EiBMCK;OHyqY*Dp935rvlhjN@^e+pEO)4w}7G z{nYM~257yYKL70mSV6iQ%T))70%of7j{wJnSVLZ*O`AS;JMjDoWwKwrO-f&PhLj=- zWP~mMqn)7nV*x;>D;rD=%Qu;-sCwHbEsSFZ8aC&|n*{ir+Q1q19xD^F>>Hw}cT1LF zI)zbKYA||m0s~sacXmcIS#jXGdGAMvJMi(wr(L>5tYO#^3%p2@n4PsmHgx7?s+ZC3 z1>< z648g7ORZ8WQy^`yg%{E!2swk$pdwzNUP69RtwswLX zaUZcX=z#onj1PAG@xH_@BM@}K7Cc9kaLy>B;vYA7==H^?R1j?2H1s_5E4L^8Y7OD# zyk#}pEuYoMs1s0NW?2-3=3&358^ZC&;4_}z!f`$!r8Nd!+8XnrXUuT|8p1WV^2FL= z4XzS2wde80%>s!1uWlY?7|Kd(X1d(}NN6e}y17<=PI)-)&!dAwbrch2I1oxF-BEMb z*2GC@7(*vuC~Wv!xSf^AQ0WzY8@-JO&mOxAfanonLNIy3Cq9*i_qa{(C_&k~?&d9g z`X^!j_d*jUO|*J()Zz-+0zjm0ocdS$R8lyN^eq2-WdmOo#GUf#i*rXtloZT_T5z)g z9ESb`oVtjZmlo*ORGa>(wagN_@QVhBMrc*)UEv;PK%Z1N4|b9k zv2s;Hd#}(pt-ngfHl4WDuTb`8zDZCsCdMzYioSaiRxEI#s%q?P6%R9F6thiOSPro( z1Pd6tIi#L{m4%Ovb)IS=L2M$%ws!(-nIMxJs}Dm$V_ExzMC=nDW%+(1L9Fk|;Us#}L^P=6j}2vKo#B zgZQ!Gy$*!9F9?i^Y1z>_OL|477JxiUt^1%`!(x*h@f2}U)+27C=NhwE&x4&@`(cNB zws6%jid;N20@Ca}#!>*q_pN@fPZ03i;Nb4G4A%J}Jr+gg{tb@rve~32RNFr=t zwd!;p`e=?!%~IFbAH5?(7LdSZDGTa88~+htEq{)%^gRy0_Evp<4s1e&7*oR>Qlyls zH<{arPl59?+l#?{adrX)6dZhy8%@C9ac{bX%b;Sa{#>gTKiJjlY@WT#3txAkB93Jw zK)SDo_SqZSY5xnHzxoR7->y**N?T-S=%F=gb1FIgpNC46X-?0hnngfTC2N+FkArt@ z_RIW;23S&zufRQC<^BEibEJT`L-27MOnK{}d5cP~%%T_DE$pW&^@nLJ>(AHFG1?tI z%BDkQ)W5;oH+rw_NyYoCDO^+{{1rbC^EGafI{}#_c{&QLA$KhC_B-gxG2d|jaI%k+ z&sk}BkAhv@#>*h79za2OLQ_1(R{cwD$?%7$pIDWSx9m}vjfsl#r$jpkIJ1nVb}1%S zyOAvW-@L`=`WEUWu4^NTNS^67c11^HfVK%Xy{v=BuNf(S8D~mqL_t%$JXT)}xpuEw z!dc*9XRIg*FxADKh#~O(3$wzn|FyHkEhtQKn#SwSI%eS_!Um zJGL>bK~qfEnu`7WFvJ2tmTV0vK6|r6&rGW~OFi=-7yD+LibG33B_{Oi0@ z7kOAHDYBY3vo~El=yPVAizf)=Nkszjz~z677gNP>hE$4<)$>i-_={9!U%M;v}JQ z?6X$RdR)AUagi!JLP*vLt}Oc5xBnrFBF_8MIV{c-f(Y6b&hh&P&M<&yoLGz;`SCR- zmaf^-$-SrNHO5-t`L-u*zqwW8dGUhd524ihd9FhcwVB1>xFCIB@xwv+SwUI|78ALn z17(EIBx?G3-=pBaCdfvQ3-%zD^$1&hC%zP~Yya>)^9!w7l65~OsH9fU{M*Xaw!Z!R zbs*`Yn6tPNQ{1(X9RUgTFb{B%=(TBbSgA=7Fc!YWHRGcm#D1KWlk*~0+1zPm6+PPy zYirBgL_y$>cw(LKyW*0h_7LC9W}G%Nh5%{rlU8Mm@g+Gy6%{V&>pW{LRr!9+9R_hq zqP$aqr2s#z-W8J+d=qkgQIZ=1@y0&>Rg!#+fu^|M8M<`8U*fe6Ll>5r?2Vp-MFBn# z4A4G0uayY33DYFkc|3tI z{Pkdjz&T#bY+`SxcNgYmB}j1`bC;n}4||}vW*sDkl?2Xm@maGbI_?O1`Br?%Zl*i~ zIxulT&m_~wLHQT4in)(Yo$Ru<+Jbkmp^n8?h78<(E!Mq1%% zq5V(5|01VlQtZ8cZm%d!MMT^k&)AxVwSx;4AVdcK#ElhP|4SELkj_nrYbcnDkJdB z=hRl+jXZ9Zeg!fnY()B#cYo0B`XQm`g;L4@=Ua%J-LIkml6g_6sN=_G(UR*6z6kV1ylzwtW*D z3*2mWyA>=Ca5B^mej&RCZKm56mm0uxu%Y7>ngB`Jd$kBiZo&Wr->}y;?o&IvCp=`> z4qsV8yz3+%mbxhm7i`2eUp!Bp(tMfU>VO1v88H|w`9zE20t?l?}!ALRuUm?VOis@`N z_399Xfns>oHMwdEGjnMpE^bjKtZORlH@HfJU(MQ6m55&Ema%}MKCi^IWo8WVqK0cnZo>Ed|p3ElAS?$8N;<$c#*FnudA^g$W7$n%Hp;%PJ?=7@T^ z>PMCFVtH-s8<$O_eK^~UqY#Fu{O>4281gpfQJ@Z4)X5_3y-Nb-`Yu!XRhn8d&bG3MNgNzG!;NWoFzHIXY!A%J!WeS&=su zZeZkcRj#xwDpz7d2ht?MGlF($|9uc}LU&d^sTl9iM?%c?Dk|RU%Q07|mf)q(gqvQ< zJ^c;ts;X#6)K<4f=2E_a>cg0*Emboo1=OR@+zp9N3wqJUQN%SC_JcW0Abu)B+Z!z* z(5ZKstzP@v&(MhRJe!raF|r(JB?E3&8Q%kyScN3@npLVB3P?0iH~e%>gR#?6OPz#} zMZ|h^M09`@E(9L^o)2FIB2aN_RRAGq1X5G;vT|dAoah0$+{AKA=1j=AGi1RHj#-w9 zL0f320Zvxg_Iev#lLUh&5wwE9MC?F)J(W*DdC{58&x~j?Mu#@;PYi?Hk@A~^ZHcl& z9d-g$D@`50Sj&QN)p#15`UtZwhM9F)bGT#gIMmV!lG6vWrjtiVO7_B#BGzmihKA+fe) z_dczCs{aDJi5p%w{$(G#!I!i!DoJJ>KxL)FbtLyVcjCDK#}t|%jy4_x^?r@IjyV(# zt0{)tXNxW>P^DG zjK+V@-mlP;UWcHIP?F$CD$r@J;KI@2HiTSv2rO4$U|PK%_!!qVwLnq7z<%4BfpNz0 zKAqcZ0v_h0UhHf;|Dq?CG+!-HlHLX+lqqqHe@4z1VvfR~WWC6IaGA`h^x{UWBRNIA zyM&nvKS-7gqzZiTsi)mq;PMiYd=fZL)xbiPZcL=)469LM{A>{L?6LpFhq)gCEg1I3VgLpJ*1iaCw5ij8c~uM z;RYwKbp#Hve}?4KGPH@yga<+vG-0J-!&-e3w@eQy=VTViX9w~KDT}Pk7RpetEA|q` z%oZ1N3(Q+%Wgi(P$sQ528DKUCpf$KrsLI2Tw)-eg!HF<@OI=B5;c}R2ChB*`bu@c- z_c7x_-F=WT=a!5oXgom7S)}-h>`918nh)6E?*ldXF8Q_Tq+;@$tA~$h_3#_C zldKnQ(S~e&9jztQ8pIy$vB=QdV!+XNO!UhxU~|=1`P4R0i23U`2HLdrh_*uJq;S!Kw*kOsRKMe=7n zG$i%&E^3TX>P}4VWD~}+f%|WO1IS*g5RyD>^gMINti20d)pM2p?0g7U=>Ph-@2HOMj87#Ohy92^9}&`ZeR zF9NX+mw+NXFQpx0%3GIT%3+f;w=mbHn#{}%8B#r$!@WsJ3;Q1@Cj+nMig=z*cr zJ7Ta(ukYm)W-%lokdv2LlI@}o{HKS3gT_V|D&aG1;{X;LIdPnoLF23_3SN9!h=9Gp zD3h&2ZPbfteA+}hP0RFW4k$*aE-k9y)}wmHwN=K9{)o4s?CW7+1(yD7&)?^NXMrx* zt2=9Vm&2dgQ_Iz7jxtG_N}nn3n}-W@a+a6|=w07&5W$vqJbl(E<$l4A-hqK#3I{ck zlTAWp@RO!maOvGEzj?Tf+pQ?vzgCL8t}KSIssc5MFWy{$$$)1UBO)`-=!;`std6qD zalIsxMB|eMUz%`6-xs+WVGl_IRuS)U#SY~%Qzee1Q?QS^P_j3Kz=h@bDMMKS_;VE` z&-LaP{meWz0yJ^Q!}Pw`)lx zh90;#Vd8(Gw`q_lEOsV1N(KB61H`t5L4~^85jBE<{=3^PZXxgZ01<<-ztJY!VXh}s=$ z$YA_Kz&P5RAou;;xVDfj{!Y`aoMDTu!u{ezM(ngB@tZxI;%o}T>nXIJXo>Pa8&ESn zdcu(~nn*nZ#GcKf+=Z9IYw4Cl zPZd2qZ|?eqvnj*RcF#o;t-q zd%}t+3u;9{&gB3;yuikc9NzF@dzGk@li<=rQPU!>pR_S9#c4reWJxi~fAhT^z zfA`VrAQv`C-)0se-}_@sT8?T*u8S4{?2*p>(H9@kEU6Cg{w&jNDhdU9*rM);pb-Hc z*T|S2zoFpvy*d!!NoG)WC`xqC%GG56>f`Gf3YL#xxx0O zhv%Rcnm6eisFP&bH`!&oz5h&XL{(FSoOqlc_b@?^Rca48RbB((L6OMdtip zKm*SxXIJqiXlVVSQSfcgCOOo+c^4qX&3>r5M=(bZuR)rd4aV28#CZD`Ydz)XNBuVP z3yFWVsty{qwv4x)a{_Omb0r~`oD@<6B(sWDKP@vrTN%ZNt3;w5N0t{kXC<99K_+_W zn;%`ZkAUTn)V>wD$a3hg9*5kpuzBg=e2$qhOa{+%CPzUWl5-`C7vZ2W1X!)7!WtS^xoI&xTP8eWXq8d_N`?)QF30|M&@(FYB59_&4S0o-3AEEI$fRCHvQ|dBU{0oTY zha+n-NNgNL{ig8OD^Bi=s5lig<1$ZWyt%)&L}1>SPph#}pv0sG!rCh25faP3ydJC= zTHLymZCnv*U>F^RE4Zev(b|CDsPK6T<@!5b)|k0FE;_0LfL(cQsVHDz6FsXP_Z+w; z=RnuSKZvXa+xsny_e>lTZsWzqobt}Z!izMX*f=K1YUX%aI7XC9?3fFeDzgfMB@J6N z+qPwE<>b&poYiTdg{iFc+j3P~YN&M|fnr5^8^28q5?+eFpo^oc?q=M2{$6*_HZ}C7 zBeAK1;a^J*94}VaAox2{s)++i678 zn`I_zndHsUAd4JmLc9#|c>&||p--KXc*wc7&y-qWF5(`_lSP&r0OM6WQ76<~6Ej4l zIDs5yba11>UX>8x$SnK3_HTk{Az`mV!es+BS{%1F^vs-^U5GCZXEyk2h;7Vqq&f6Q zyQ~NBCOg)~X$(CX>vUfr@Id)SdaL=S0-0XVl6MfmwtVWSN%*OOq+9wQej&EvVX9{X zKRqH4Xo#DUk%EmJl~YLD3FCo{cus+dxul0)7f=M&*_mnFYUyyZG2UCgg>GBFw^<*= zbZwY++123{BCXI3jUIXZXY$Nb+anoxaLdQ&;!m;zqrfczu=_f$QL%-C3i1T&N)0C9 z$p-{oN@p8dSM95MoLEW#xQyb@Oi0n*-j4huSAf#Ro3nA&-^ymJ{7MJDGn{82z*hNO zU0IeMG_$p(m!_s;$+>DxtU5nI0eHB%6x(!>f{I+0N6F!QY?S?&wBX1yd_Kgyo}>f` z9$m8CcP%FldvMkOc=b6Ahp*0~&V_TW=&&9NEp{nh=LnPCjfuCdC#&{dfnb-UwOQW0 z&)Y);Tz3GR#XuV&wVaU=oVxNr_5=OiHMqp|45mGFnKyMEGtU>q#;z>WJ@t?@MW?w@ zJjb(vvE)})LPeF1Sw56*xe~EmfdSJObj8nfAA(OSCiSo2JYL6R#Knm5vGj^I7$uyH z@Iyb?w?kqLyYkh;#E4=p{>qO)@&$`ung5*vB7BX9XN^QK<%uDQOUJPvO!0t-BR6;* zyl@WBtrQAWTjo*YAq*G*Fu&fRO&iya`EeaGaBKe_5krIPb}Nb52|9sT@c#iycwG6P zZq#r4W!b5rk(N8K*lt>^iMk9O$%{Jd*;SHvYO%RiUtPWxz=?;a9Ihw|PvIOUe@Esw znS?MJfz)StHK8P%dV-yL_7vG0&#_=vI*XV2iRi4WYNNeElFR3z0_BZ` zb8@fegDk0#Ab1?aGkGe5!D5k{qgi^(^;y7o^L*^Zj(`Ha`91?2|H1(2W2wy5Z}a4ZTl6;I%Zk`D z{FAWI388;ew>9)YbbZsxg& zTcXVO4<4m}A<^YOo< zdwMBw;^)*X7n1;U`mF#_*x@NLd)k2D3B_J$7;Q7eoV8}8imp+ouJJchw1T+VsIaS= zOdQBKEnon&=5qqamK!eteX+AYq}O=Qwo(VOsRZ}Kp+`krHn5YL0w^BV6S1=QfkHcy z=w(ea9Zs`mAPK(Y_jP=))m3cg5WB>9C@DGc%X3z*I$@R>3L_e%olPG4G1`W522V8J zG6BnFr>Y8lM2VjZ3OL z3Z1D*7k4#hT#0Bv3>wo*9kZ`OwYxj6cT~+Re`7E}Ee99lUbh#^od%upJWW|+KaTKt zn-&b^*hPOfpyu=s7w@pg@vi`unptI{jAkljQQi!rs#soUZOuyP}~q ztsA2xsJG9UVD80#V=L=A6K%SAlgNi)d+!2bC|u@Q!Qg5|5qJv{8$C=pt0R=OTSlS z6;!b6w`pGd93`~QoQK?sY034tJ9b$n48QB3*~vSqE6TUWE406*YRfmGZ36XKpPhkDBKnN%5ht)8<(x2FZIavYBQtofI#Z^2BdHKNG z9bo-4*S+)s>YH&TVTGb4g4yQBns4(=r`U0PY>~(4F#@@r^rjqSrv!V22m+{ni2va2oNW@kk)5_bF-7v@!1F7)}!0)qHzN&7f ztH<^6{&;!X^$NPdD6{+-wOgdQu71)NqE7`4y=XK;Ym-1e7G}P4u#r-n9qi&c45eI$ zV9NkgZNlh^6-A{X5+X=(J*qU%(E)Bz*F4?8&}IM9-P_lM6pg8w-crj?^1w7i#@HDOMgs96G^u4XFu@rY3jKO|7}W)Xcr5BJQj?;3 zna)X}7_36r7VH4ctQj%bJ+8YZn@MMXeH&md_QMFe+KKC_s7z+Vc2PNtY)#=L|M4Oc z4~X?ZU-vh}l3qvUQzuSqL86g+!sURzIUN;Q2iIOf{-i~HDMQd~KmzC=c;Kk%rM1o@ zVK>!AcH1W^?#NpV<62n)CM-Z1SSGgRFB2D0V<~l^DFL>Jva;-t@qfBzjxQ-Cr?JutNtv5@C?$^Q!9uf@GYSbKv8_4=?#Y3-Vn+iGrn5v|3q-vYB|!wGly zzpPr!7Y`%pe-EQn$7he|1pD6`4(6I z3BZsLvVSG)8g($iSKO>#tuA8p`F^DgfV?a?5`V;)03|@$zf}j3H)}x>dBhd*(V~@L z$9hGd6M8sZIatp(+4yVTZ(wEW$dVt=>jx85PJFM#W%& zLWfePfE!R8TJ^7Xso=9qePk^J3KS(}tOtUKy;8*Aav zS)KijxtruuB66;H2PfPbdi@b56CyTL=AQ{1+?)z4Q4IV$YZs*17Zll1C>um5*XKhL zWA3+)jp!Ya0dzkh%Y-PpQ~0wKUCOp^zrBE_$g+a%uE&F=qf(PHofhwY#7KeNLV5lzXw_LxueIvSHZ|yMdLPB zEd+;$VD0SWEU4soNH(n~2dVY0E!jWTfABa6i*FJ55tlkETT)C*TdkDwa40J4yqbSp zfZOsLe?}1h^U6MST^nq2eDgn=``_IwaAKgtf2{P@y5BuP1H~FWK$(FKZv~b%Y31d; z-d7VGVKqH^$01?4_Ep2NhWE<^*DNdZ9f<_R@Nxo-^@NXzr{F7AMmS9qeL%4^GcramNjTUE^M8jchIw#T!VgFgtylj`xerP!_wr z1PV6vJ-lmMQi>ML{j8TSzv#=GwvuC0lpv)B^sog9+!xM`lxZVq99QT(#D&{A<(~~T zR4v{jZ6;S1M(Q|K;N)RoLrDQMmp)XsmTVGlzO97CNGi9h6|8@g0cZQUt3wc+fIn^) z4i}a0(N$e=Qv_^Y8lRF(fFk* z$Y2yGbqn47=HwY%M9C^&whd}}hO0Y8f}4bP({o5}=LiZUdYYj>sxl3!l1$^pNrfhg z0y|;e34tb(rrCm38QFl1lY^ycGC!+T`SY{`A-lLsw1?)`*$;Sa!ri@eBZy?VO`^&x z<-C0m9a3{+%MS98Wf8KkEhvoEn|l!I$GTF0Oc`_YIX|HIxPmotOgWf9^OHk7;D4Ih zEj{OnVH5 zbooCytRx)+(HwC@Z}KRVzh5h6X1`YT7ms>zNjyImJpq7v?5RhM%!JBtt&y8|RYO

GeUr1)nRY6} z5I?h^-rs~W9{JA@ltGz2nfY#C0_AL7QPt{CQi40*LdIKUdzQBZU3^@dIc&s*^aNg> zyp)7YFTIf4AC?C(3wSNzF{weTo4NIfKP-1V8VZW@7mF0RT%DCt>-MWn)8!(;b4aiV zPpA}N8Bjy?f%x4g;9l-khEaC(UH?37m6b)PQ%Npokl>y`Dow-6V5`Lr<_$6e2q1;@ z1Nkyt-tk_UVY8!Pf&1ZJ-GjRfE805L9~@6?%JX~5g+GCSPx757xfJ*sY^EO(uX16j zUxYielwBdC)f{6#plFH=K5)rfLre;d*qp~Ufz2g$G;ON#M!rv7fpfpoaI%+qJf2JI zxQtpgjI{O;c9Tm4DT&begHVZ}f=4Ys)JngPU?imBl2Eg$q=^-szlxPvEY+vZ=du5Z zvDpJ|Eg*197Rz`)0G-#>XNQv(-PhFBE5ufge={V}gD|q_gEXRZU8oT{70GyxZ~t8^ zx~sPVu^)cX#jM0Oho$kN@+@jwEJnA7zT_6dXRxXK*kIZb0^gXS@M1-na9NC>(&(CL zc_XY5Vx&hIX!p5=qi4Gp++I2~a{0A9B+axYBgtjrzO67hvW)8&H9( zNijWta7J8jn7!CHEN=&iTjoXPw`BA$JmmZp)}d5jEE>Q*rSRIZ1uJ4eg~F@%ygXjX z8yQV9X_&^yKmzPoe3faY!pFGMx7iG%;kg}+Db?4CEfbFKAIGz#P0^lh;;hDu~XQU~SdER@~eodxn!)U1b`z=3+Omco-ZXZo<_buw3b%Nb*)^)bx?^)8Zd$EeHG zM8_0UFqR({!m!n}b z?iDeQhzQzCSh7!O zhnP-JzegjAQZ*SC>0#Q_QoCTU*!ifQyZ+#5b8E<29y*P1X)F~S8nGjLWlf+yebJtux_~?yQ0(Zy1>V3`L{=x1009>ChS)`JRXA=NFGu8h-;k9Ygd91*lBS zCwdneJhpJV9Q`!;>_NnA8KAXb&hG>_olw;!u<^cM3IGVD+K52fMNuL_t(r5~koGkW z=o$>G@p6S#<)e~N+suGEEw`1L@2;61{*=-D^Po_xi^GFZ7j!fez6eSNXZl5^DgjP9 z|Krhk6&LBWb%26AtWUCIM3#5eZjGmtf7+q3 zA7AkEExvTk5w~b|_C%!$izR}H=W9Dcl(e(>Gi#(t%(+uqb(SS{OJ@D_7k~>>O^9jC zE_$XFyuMP1wnqVpD-!(9JTo2+`K&;s#aH<8_0B{t9uwc9@KoJ0s^yTDY#kJu(abaL zR{VC!yHme$F*b6r+W&$PJ&+Q0S0&a#L~mhXp!A8HNRM#f4&8l$v4GYKTVC%Fy>pyu z@MyrVgO~^qrG~O`U)%GAEfuMnQZ(D`x&*+PaTRE0(zZrohjj$#V3zbuz-i6x-?2}! z1k7n^Je3@%>7&_2)1dCdQKneNKdF|G@PgUr$@N}ib%Pn@MC<);qe%ej5VrP(mLW!R zn&5DX_a&9{YN$nZ>yE2fJX-#gh($n*xbf^44Drp1bA=V-jNZHj8=J4|Lz1SUN(q~MmEE$qQAE~za5Q&m0{vTx%Nxau^ z_ih}yuM^+x$vtm83x&FktotQFC~aQJPW`rzhWNOBAzQ(T8#|x3eZ;pGTb`621&3_2 zW;eLH5P#dRz~&%3dA4^sZ$~9-x4s0^Nr|a?Pmz98lLLQUT|rVU9s`Ay9V(T19GXUP zq{Ckf;Z2S3@5(+4FNdW%$)YG|{bGYcEY&^yE4fUYiM-O+{V*|KM%x*npQHdA^VJm9 zXoN8Phd$aX1udO~wgLOtd|D3-ruywiM2ke|s*FjpI`t-HEM=aUA)sL2QAh@~qTvAc z5C`3b%AX2yK!A~whPhpDy+NdITk-g^<2z`2E%N8hhd&I)nliEw+&@agKv{*>K}3nXGL$5CZq^;Xq4h zt_|h%>a{gU>T?38tUZ`MT+ldAwX<|I7-jj+WH>KCoPdD?FbX^(dad?3!l-TUofF80 zcK;C!)HO5&O&eRt_1JqT@k~8E;W~=gjGcaX^V;}w1WJC2{3ukaS6;)9MAc|Mc{F;w zJzZ9bTXjwGgjyTEF2(ou)vnL85jP_9Xpeuk0!p6S;pB~;=YBfK%GDcjD})M!{(P_~ z>Y_kB9Wm6dyQ%vgd|=N#cIjziK+o|XG%+&&pt)satyZqMhSvJq$gB$jd`Mh8fPdesyKomBbab zS5kH;I*KM?WIIEf2LORkladh>q+-SCJL4eH#Jbq695=l+LGU6+)O}C)jWEEfr!P@4 zm_l^3sbtNx-cchW-COp^I_|q&?!Je?B_5AOZ+#$VXc7{|nBoLG_8cu|7ezVtK*yvu zO8}sWQA`v;KmaKM;q=i0q5i426qa4u=g@JjV7DoP{01a~7PMJb-{6-0B9Uo0F=@J( zm`p)wls%Crr8kgAoJze#6=D-A84!!$9{Y$C8%;b!V@W&-QQ16_xu^i=86oH9I27ke zhj$04fhb53akl?!Tt|PgsN(|UAg5LgL9~++>{ygX6*h{VM)VRK$1Re*w}TP$nfo9ZxC8vuN9_#eTq4iCl>P19)m2>TiDMvBkblU#Yn(5%v*zu-u?0+iDS z!*SPfY7X;euH5;$r=}9A2|f1J|5ufpvD|H&w*Dy0+Rmb?&BJ%nQ8A(E))bWMG*85O zTRAA{ZC9ULP&h)V&LtP-M^F*xtmstHoA>5oFaVsjqVydSq8FoP`Pfi0KB9SsGp~BJ z3vR*c<=>dbEnwvla%YCg6__Jt}Iw-{Sesya~?BFvJ zsyI^jA0nZcw^303+$)8a{IzcyKg{5!R#aC5jFPw~SA%5MYeIhcN_?G@wH2ad=05Q; zWce7W-*aRw9E@pWx|9Qu5Ak4slR!ULI%2>|79VOQdPR1|%GkV1B*77huUAb=tu^`i zNEzq1whI=19fc4N=34wmba)AntO5F3)r(PSxJbc?vIf-0?o2aTBObB~tW*6MObBZr zV(0&PFzblvDQgSD;Esvbg$wc`C}C##exB0&gr~sq8Tk`;7`1`)HUQvQfe0;(rORix z*3toYUvO4FWtmj55d$K@-K_(i_@&f*6L2L<^^m2{ixG9vOy9?{G-k9)7a>=ir5~YVBh1v0>stkZ)K1JY^X`)c|+Lkk7B@8kdSg z&$y)3z`gwqI_GTuaz>Lt#fZ%)xB32y*p-VtLHPjd7%6BEmY)cOm(}|-fN1|CJ|TE6 z&S8um)Rf7=)N3>5_HYNgt5?&$?*w)pm%1H~R9e`@$4RPwr;UeiN{_nPclodT)Rq6D zWnHRLM7jewbd;}qs9+n;h3fbCQQoE3dki~{g7>ixgeZMlHEXtUzaOmuZJ1t}ExyvSR^IXK`_VXs~BFoZrgn@vZ ze*H>zo@8qH-6~T9emf|WUeK*z30jiQY1^hQ!X=K9y*Kcmm4%mJn~$eYn{dK@on02w zHjj>b&K_L`f2kEHsgL+;-`UWV{u%X1;`4td&!<^Rr8WK_7i>a3p)M-hcjlj!bCM>fdE87wwJmvxA_hccbWIZ#|IDq#;D13jx%ws2vZ zeyC~#O^w_>z4xJ41gnh+!jk`-OZaw=(z_Yhc^6n>aO;#3Fw=mPn~gB}55QG>uQgkh zTf9%QTD!I~Oz;AB|L78zL??3075g`~*A!CLX94hiO$cE`utz-}U{3+}qfy=smlj{Q zy8*boMA)!5Tk8~YI9H&n*Jyr z;vCsQC7?tdFMb(&eW2J`yQ(tc`e90J;h^RB$FA3j-C-$2tMcTaa8-oMu@g;(Z4)`T$3`S;YHK!{Iq_vB!-uk^XGr*kdmk;sr4(9Oy|a zJ<^oY43qbgEB^Z?rP||Y#1H6JSg20b;!y7u=Ww-!x;;b+0<}%4!&l7qcH}Q~_&4-a zUzaelvlOu-EgOD{GsaOWAm#$tf)m1r`+(EFwum>O1Cr9N{1wbt0>%b?;nap(Su zrgUyl;21;xbh~yFyYpGJOVWguK%rgRv-fSUf_?Rr&$P-s?%|G>_rAyUT*?dl@C!Q# z^odnMaSrfP^XDV48dkA?{6pBw$fj^{Ip;NMfWu>xflo*;ps0$~`r@XvxtHdU5(!^R+ zpS$N!Ssm}TJNk=^@Iu;%uIxtmx#Wi(|W zM7(D4LcsE(jaUYkFo0I1mdbtS-^?a;*XWe9#lUzPnP=S*P`GVyCV&sBajR`D%O^>KNRAGAdyaVp*J0)AVfI#6K@r3jQ0u)<|B@O^>unUk|I>q7K z)JHq{ZmIpmB@upK;LAyuvEW1E|0BDAm^7uKlG5uo#VK$^iLQJn?&(d&L3NB;?p%rn*3lZ1t7J{`?L+L%WK$R zzT(M%TsvP!S;!|!7yGa3tY$O4cwSlaV9I|2v0e|9F6G)MDU}?98X~u|$fRV=no3Hj zQjxCk;j%n@pLbqYmr+tgA%%~X%IYpHVUhnw<$(&@!Dw9lpF;nL zV*@ByVYFf+e!OZ$e<5G>;ix{tKK;A{*GW#rixKC63BTLJCUDF0 z_?+0xZt{x#o*Cr5eKim@=|yH%Jjf1l3YRqAx8hd2`96h^t+VRLkQ@xD^6u&>&7WfN zA%7chmbstua41kJq}WXAAc`1nMv|Ss#U(bCi6zsgcOZtO@GmyVJ%i8SJibUe@=4Un zk&#J?;AcCrh@8n1DH-TTK2LHR=66In^XtI>?8s=u@Il&iL1K{;Vje#dcf)W)_nBX6`edH^ZQn`H*DaVc0{*?Ic4b#^0Y(5zPgb47MB_jaR#WUl zE3uuw?-V_ z187UQRB7oaky`rXKeCg}t2j+^D4$ARq1mET>LT}AKn!5Nqa!Hp_&CQgOgRt^tz-33 zu|8XOZ`sn23$aY?MKP;A3xrwvC#Y>|Tipt5R2Poc3)Gf@^mSNa^Dr>?r16#NZ|JAS zNYs@KL7tvYx;soJZl?;ETM&PT4>$|mcJ0Uk`5FD*Vo0ab22Qq6e19%oGz*uDL=5tP zQ6swunCW&kOZpo_Y6H+K(aW;1(+~vQLgPdb<1CtlW$bh}nX+dw3zQ}$nQ2JiFa6R= ztrmu`mNSW-5EheLE!PN_YMONS>tHA6Gn# z`GK)$ye+V%r_nOoz;&xE3;w`;pdFoV@jrEiLl}k{=ud=>qc6bhqY$t*LhLxlU^~oP zMrm(!hdEwxF!4*j`U3hMIX_OX?)R2P_6K&>ois`>*yXpDHb7E-+#?POP1+AhJmNlRBsh{J(2`%mzGn@neZ8+n`)dX3orhuC+1G>kXI~p_48bl(M?I{Q%t4m z48fu~p$`^v{!d&vbP>yiCBXuV&`Vf?=2xs*$TxE~pm9+>;uR#X3YDs3>+?l6kP(Dxf#l_>`3SP&D6!SL44pD_TJqy|U z+hD1EexlEmDVq1_h2NJ`yk3v++-YfpAP@(`t_S*m)D{w=*-o>}fC(m*oGo1hHqIMn z@d~RU<0kQfh5=XrVfXUHlWC}W)asqu+~rnP?}|E&lckV7YUg30ZR=>qkBAx6{=iIf zo&;-`5N+$)yupjtNy{HiNogt=w^3n}2JmvyYM1s3f*@5g1ACM=NDj6%c~glazq7O$&RXxek8J zGTN(O#YaY29v!yyfwgIZ{0&FY6BlD3H%W~o&{!U@fMZjl;N}g7F$`t|>e7<0igxG( zi*OZI{xC-e80^12`i3+Hu}RfcV*^`QXwq%%B$vqDXk}>Z_NFQY6XBN_pju`u$r)n8 zXCGFQ7-H9J21eR)#VbU1#g9deh5i^Id#2h@2X_Q+jB0BXpcBmDURp>%3$YccrYW(!dAzYuR&yIOS@g+&FaG7FN8&;Pfgf z(63$zSc8SJ&KX|0?VA_oZD1(d=7K6Fa|_ zi1(E6lT|Cg)J}~(nvikq1y8WASg$^BO7~?ro8$Iv$GRpIz&nG`Zen^WaW-UsiCm3+ z@tI|R!SI+oC-z~V=`{td;~x83-R4vG6Ivb??K3<@eb69{I~|jh8=sJ9fk-i`-4{kX zTA(srDp?>4$8NPd-^bwdZ$Z+NSYs_H)q0zHf#oO-8Y>(6 z29aU2sA=+F)t_NkfUn)Yr~V7@6(Rjj0q}OMu>)-!GR$Q3@I>T0Uw*F?4Wb226a+B# zk&fCXVa#RJE8{V_ts)Hv>JQ`UtIq^sUv4m8p}AIz=W`|osJwc0v#AaLd$Py5W#;F4 zeleVD7o-8uhQ_jleuI53PTY)s|L{u4THL;p04c=|&1k`jT(Eyt6X zM>e}d4xZ_ON70d{3d07*somuxLdc=s;Y-Kb*zbC-d#AEUsj4R0nDt1mTIqy#dcQgm zRg`9$^algYt5kz|zJ(bY+C0829q@a8_X}fX3hUFYqVCR+)B@_h1I)P2znHqe>4)7f z@h`tt$JZ-%a~8T`6r^a*RPAigV=B&V^th0G;7F2*M4nhWm@@ya#^zDZe#8)!AP`t& z%u;Q7{$SpcL<6il;|+9@_qx=QKLypiMv8M!;a5hOd}4%s%y;^vA0d8ZQH^cJ$BGsu zKJTH~jjJQ$2!ciO4$P@u=Mil5U}~a^s<08!5syMt-bM%MB!}+_&)^pKmBvc2Xv<5Q)jG^9Fw1yE#u3?V8_|a4je|tQ#4fE#KSm_^R zl9QW~bLjWCP`LCvEG9+wI&g zq;7SRg`7VA7j8uk3Tb)$HEVh@g}5VkYjF=cY5|2By)SCz`^{N3||6EGV6$*!pe^bdp$Z%5pqS&H6r&FIstz(<>c%p)8|@i9J6! zp=nFOLn^5{E^u~j?7hgH7q+%_l3$z(^>U0u_Kc9DSW9lKfY2KusA?W{@rcT4Rk2yl zTNt@0AkNCXSwuj5$SeS+F(H{Z>MBG1E~hlu0Z+21*Q+B_0~+UqHvpnfyh2K*Ohgs$ z)NR0V7j0OQs_{*@vp;4}TW9BnLNceg{SD`Dxqx1df`*W{Hur@y>}%(ZF-Rp+XJI$K z4j{nZ)EZNd43R;^l?F#$2BA8){IgJx(`?-uw}Op6y@Y@<_2Z5PulabH<&#bOvuso~ z0cxzxOr(oC;qhp2UugL4o+SGK!O4I`^&1Rf>9< z$*=_<2EMK7Pv*uMnQiV)63u!{Mi$H(fD>o|sutjd{J6^L)_fqT{01{>b#8vmV?kim zzt7Ai#l!F;@etf1#h$%5DAkJ;%e0*HnB}zqA4f@(^OQstl2hvP!Mc0hUzSoJHHknI z;K;ES$U%5>6G1H^C9=5b3hr0IvRc4o`GEafvwkZ?zuGxE3N6{}#=#O(2d)Q4xKHccv3U!$>q&q*ZaF240N^*V3~C0& z?o}hp>^p%P4x@D7OXY4YLtm|zrX*Ea!ZM*A#R1l7_V5$yUnsxrt@EPRdoSm#4U@e6 z7D1!xoniS3hLT|Okm}YSItBk*S3s6Tf}EYZ09UpF`}$VCaGW#&D{UKWTsN`^IJLwP93hkuIl5_Y=AxMEJIcCS01PIxX^A!OdGx z1Qf|cU`w|R5#WUzj@uY$dH)m{m`=$eJnyy8B8-akZ%yuUM6BW(?S8}|rr{RmZUJ218GoQ3=$I=Q!pHj&748kgm6t@n zIIWZ9&yj}qyUm=_<>x&l$vMm1jcYXI_2b*P23iOWkVlD#4y-K7pPH5kV+57dpaeQg zC0#X6YC;T*JGlcEbP;SPX0%%5FJ1cYoZq~&jljIc(*XWMg07Ll8iDkJAq$H^{69%D6HcoOxl5)|E-O$;?uQv1W%!zdp7f&l z{&)`(E}%8lVwtoU=e%1K3pfD`wIT(&(e#<@6ZCi_KFjnHZ_2H^43Ku=S;``i)mSE$ zi@8*>oM-AyePC)18oJ=hk*YE-V!vtxlF1|TkzHGvr^9GV`l!pglMj@Is?jQDxrX4i zgr&M}eXM7i)9sbMy8LqVQD_fEprYA3uY%VdV!-Da5Ex0E_hyhg8=|e;y0HCi)=J zIb&1j8*U(8_=BTu#yCBqA~`2Z4PawV%yg>HcR>4$ywIJzc0yfzrTP;0#bS!AP8}*; zJ<;MgP&D80$@1O2MH>wvl06e+e;$vNlO6L7zGJW}jQMaN_v#f0yp)4AKC&E~Cfj|i zg(I6mDBj2#Ltg^lqL&WYG-8y=3g)Jq+Bx7NfPp289*!BIc&PCz`?k|uR3`Rx_sDnTPJ^MHCtxO8df^k%w+)O-Xmx6~!~#tz zzEFie!q8kcHr599$Y&H(I4qRotv<*Rga^U`5iQXCdh0YWe;GP{7s&wh5XkUH{hQTBW~oJ5Q|)28jqL4S1$g| z!aB8x`pqAuZuJ6s3U44WPUhs8^l7G3#?$m(hBKJb?Rp`H^wbNf z5LVNvJS#|YLf3G}`-Cuj_{ z^=mGWJ;qbI+@sG(Nwxtik}@QQO81w6YTvCazUQC_LsmQhU!r+)&a>#Vh?F#1R?+q7#lD=1@3-+di&tQCzU^md`$`{x z1mC}n1^brE_4f3UG%}dO?Oy)wycl3fW?f;Q0r;c1_pYRhr@Wa#)#`sYWxw zFq`dL9fLCcbZeulxQaSbSGbQ1$jIgQ-8<~|>4F>BQwcgACr$6MX@99+KD;wR&3~-J zxo8rO;hu%3Y}EI_y1_|6>BfW(P{_j443Sm zgzs@J(Lc8BG|OrOko&aVlrZ=+uGp6;#P&79rKJ|s_$JjoeoxCv$CgVg@lfdyi$I+j zY**wC`P`l;9SneRTd2w80QjyTX7Bdc#>_<1PFAQR;Dyc#bPS(B5^mU2#Q7&9S3Z?h zep7Y`LZ;2Dc}{E9KZI)_<)G&h#Qx6Q|MFAuwM|t*>L3sQWk~&BOTBOqEX9O7B0dYw zIn{lodL;|u`u?RTS2BOAkdq4Htv(u|9j!X?B3rfuA~Bm;x><@J#4`36b$SOa(q|>H zE4t5)cZ}UbFb0NBWBGP#K*9Z1FZ>+$`WR$GP+cY6>0q9b`7r7+g153g6{Mh13}_rb zN+$bhca>ed^nfrD9YrOHhoWvhO( zF8R`{u${PzX6&uOSup!yYqH70X;4ufHfA|F9UgWM*Zu>Fz)s7M&-?na&GIrrmE?65 zn;dA(+$M*R$-57jA_5peCedrdpB3#)<&o*hh5Z<%oSWqN-no%KAn@^Lo57-(-r5=onLvf|7k`Hsi zky6(70g@Je)-d?lL4`lT)HJTuh5Relr#~(&(6E2mrcEP3VXV@jJ)PU$&wGxfv%t`` zJ6~X;FB|Y`n3d4QYUad)VTxj_$e>ui-c=6mwUL>kq7MGxKy-1lPT;uzqL(St-zk(~eAs&Dxfj|aY^I{0K1q}Rb^kD% zkrKLQc0O8UaAe;jAG#T9{!iI3y+hNqG(1^@sr(<0NDPw4O1@`$d;5Snxswb&Vhxk! z%?9&6{6)E_eIA~kIou2-f}C0gY5_+o$NN>m-|c}J)NhsH5TE;^CL5w4yyDG+hJ)bGEVhZB&cA)WZWHj6C+@-cAVCv8B(Hm*R^ znJvCs&IMrVSJOGXM-)*7^@(6AkT-Y-m<~J8#eH8I8U*{vRX)18jPS9QBH>L*@CENk zZCxioolpTs9a|322y#>|ZK%B9aK+P$Nj>0uFfZ))x#R++so`90F=K2AHuE``H|D0c zGFR?@plV?}RK|B5<+qbW_@)Te8zpMBBnQT{+$906q`Zx6SqJ%{%?Wr4AK}@7%Q)bJs7`VPSJH2_n}}> zJ5l5RsN;p?DyOiN04ZJ5_k?KP<{xgDZl>sA#3x#_pb$lG8WtvrD|d1sV1WHxD{+bn zVMl)=d0$}f7){g6@E&ww4RBc!5y4k_;8^fq9G5T)asy-jHRBjuNxM}uI|U7+8GEi3 zo*K3TfEPKxPFc-sk$9&D4nq2xpOH3O^AX7Ne&Eq$z!#sirzNfJcus|8&&K3^#l|>@ zXzegbX8s_2OTjoGXu(CBltq!O-Xt%BVNi%5H5Y&)6uB*7343_~9#5QotW?myIRc%& zb5Xen^HT?{H5YKca*leI5yw}{hlD74RRu%+ij`2gar3gb?9MgEHz}6;PBBtNw0)IU zUN(yMuuEbtIflJ>B~Z=fCy)}cn34+=Mz!mn*VspG(pFL?3JL*TDhIGBGPV}Z zrH-=qu!W!eHo1)s`=kA3NrY3;;Z^Drg|e!5<)aW0o`MO21?lsRyobYkngywWh0C7T z0b=mZWW{^lxE{SkrWb6{c824+__HRJ*N+|YLvj}o%MS5NYFBmJ0#2xBkqI4^8oX=A zf^F#+zNm2?Ojdf)?#tutb4dF$Q>c;do9$T4oLPq#r@^t2zbAwXZrSr|^@R&r=!KqJ zhuk(h%>T)1C?zq1!QX`Fg4zd z77KIPJlVW07FJIx*=D@*s4Pzc`$TN6e!S;|On%8pgcfmKj-!oSM{X^B3t>zN#}*tM zW3zak2~k*MOCg~Xkbr1;%FgSj?)Csqb@g@;2VwC=E@0y}MGsJR0`|=MyS6un*Gaz4 zrJM2A!+CqCbNjt?Ugo|x=8`pS0)Ycf2%O)BtyQ1VXc4H+v1N){I)%nbuOR`ntdd#W z(L}Y^oGg1G`V(OrvUOW1=l6p2B!e-NjS1n69OBJog_9nM9dGusUMF3eHtmi0agXqi z*eHFg{X4d{o)&nYC??|So(yUxRyOL#){Sx@?us7|*HuQ)d^Ot#eSi8;YCW?h}2efHLFa=&sQQ%to#qz>b*5-5_>NK1~}A@TzzEf?U5P zt#}VqxTaU6k>m=T3D0@r@H zK!?tndZC{Cje0D5N2XMpZwZ7s=zK@kEq89kO~BKw%_oJ`wc{uBCOsI0l-nWI1w(|y zKGz-LFR6(bK#xA#IDv^B%oIVO;l<#t9rL3R>B>`#bGHSM({#=3Y?4yBdJ9BL{KzqB zTWaHFuZwZE9v8MZFWCIZI3lTbt8<~v1#q%U%pZ^`*4I`~vTfHpp2`lW3$rg#s=3@__ZN&ImjqkatJQ*1c41MwN#vhi zZGc6$n!Yiri)}`97=neAq14*aGww?2Lq*IE>E8(I!i5zE0>aXI(d%t%8z|Cm7y-0~ zk9{$GMk>eFky}@Ygu_R!bocKT=~2h6(?OF^`&Megx1i zq?Tgc5;YXxMBKr?fz7MTsfB_8fm@P7;{bzq zGkz`GREQnK%Nk7zxU1j}S!#JpCXKW@EJEcEr;fKnjE+QaHgmMg!{`kMgLsvAQ+&{g zlV)_bm2FtXOckX&#h8RpLBwNf@&3CQ*zX!FkB2M9zxK4=a$K@OW5#pRsOTKI@V?D;>|UG$G8q z4dnL@D$nc;2yspuk*pgc{>`{oRp66BS}eHZ(U9Q5K3Aj~Hf)+AtrnFHoowDrXTFZ+ z69)mWeRd;KcAl$WzXi&|3u;TFBvJNDvNL8P&$PnPyHGkHvU2P^hMJAj2fQXEbc1kF zgH!&*k&l!IM+S+)H5Sn`5@~k6EKq#vG-_VXuF4z8>@Nz^%oA0hEZ@(W63Q5nTw|}y zr1u5edJeWEMKATz3MQS0ju_%_6GGlSqYFJNMoM2eW$2_H9i~Ec>QoLhQk9GtYZ$Uv zfjwjlD$=>r$R-Ph5+MQDd<;v6I4aVt*?9q29;e|tXg(@Os}Z$fnS$o{nX<1iYt}}1 z$4v!x#`i>S({Fh4mJl!t*51AC>+a)T+KxUhekhIu9$gT+7 zf=lYYxVIpNwtNaLU3}s-D7^5^WC>O><(n6p;!L6F$hIHi-fvJ-+neZM*?l zCwb1M_R@i=BF|cMO6~zzNt%^u&WY<(a-sTd;dk^{XS=%?#7Ze!zac!#Ij#{k-FGlz@fLnD>WH^?pw?8|w(Gw7pKr+^YSVH*p)EI872DCt(Cd zE{Wjv;6iC$o7`Ay8pFa!yAVk@u5YROw-$b1#3W`+wQ#;u8}U%9$ix_4`31Wn4QWiE zIlQ@Xk+JtJhvZzh0GRMdIvAPlK&~OZHyr^)v?2d9nYC@(_OAr{C z)o9B~L5tq5CFyoQJ7bmhv7q9tj~W$)&6p#(ji97yRpqJBGb~fD+!i@~Oc%Jq zM#Xuu+1zT}H+Ut2r&_{wl#?KH9#k|%`JR^1Bu2pQoqzsr!Wt&|A8oyfXxDj*-a;!TRRTl&OT{29=b=saSej2CpDc;{rS+&b>!w{j}!OLu|C^08t}i<^NV)k zrFANHcMRvy)G;kj9yV8i+wUBt0?3lWTgqCj&D@FqMmHGQvp{3eS~mF`ZkvUFg_BAK zamJ-!ajNbAi0+c=sZ825D6~fz&=(ZzDtO@|Y5c(}tqa!cyG6D<4{y{)!rJMSjjGd8 z>i@kTr*7x50w5Q|ni|j0B{fe^oH^C9e{}8mO=}QpmEO4<3&)qCfLL$t`2$gxMj0a* z#)J-hHtbS|CnOXm)})U`Ejo!a8jN{}Ae$h=zN*#hU93Xa0^r>9nVf2MHfu23#@*LZ z102G5?=)K>gjV$##m)p%#f1hH_gNTyZfXu^PH^6*8if*>VTyz}fY8NB8evc}!VKCh z2;_4A$?{D;Y;AD|_oi$%phaJ(tHa}%6Ckdo3hC8Yr`8$==LVf$<+lk+gDhvK=nw3_ zp7OI}uFv#}SIC2J^f(?aFA)DM|WkaGau)`q$eBuMG6u&W}UXbM2 z7%NtA%hlzYz{@&Q^F_LRRB&$vQ^D}i6p=eV7l^`Wue+Yrv1<9P5D5ze%Pf}^tc_@b z#N}2U+T1LlSi&G~%IPy?+k}jA@21sjM}I>DLdc zr&D5J%&)fWP9Yy90Wrdo#jhg{AqeT3p-T3TANoD#+srw5RMvnTT4;CE3snLu_+4 zYK2`0ZY;dH=lodDS z?K@VEMrEXcZJOq$CTyF~*mV==A+aZ}xM`PBC+GuXi(SD~;Qn~mF4)K3mlusgg6EB> z2e)J9UdlPpwr-vgh`~i^4>v6L!*r-c*2uduS z+=cmTfcFJ)OxMa=@Ls&7qLP9AT}3{_%ua#Dqp%-PveYm@-Czg5Rg^D4(1x!YZ+iR= zvG}Y#2fkP~{}vW;MyfL-Atm1EF5X7z6CMLo=^*H+s3K--;!NoH8Xli_u^cI(-uz+| z#CHp}wXc zqN1%eZuvv?*vscN<>x~VSDYHF%5_FLD&Lwu9C(v<7;hHaIx?^0zI8HOuI85R{lBeh zlj0dCfic2uMYTpfYI&62o%{bf-HCr1vGz!P?YJN^Qw-%|~zxP*3XAHGwn( zj0$X4$s+^>UBS~xT&rPr*;UQahihpF~O`BIa`?hzPVKDu)qXW$j{DZ`ubIkq|1aH3&_3a;Q(W2(CaB4_Dm!CIcj|-1PAMafsx|!vDHM~;T9)Jd+dFBc40L6BoGHVLA2^f@1^FF2CL6YcUtP?@1)EnXv!(hfw&~|lY0;vR@6mk z(;E~RHjR2ETIK<>Y)`w)QzvJ+E{v^+inHb$|4zcd|6Y0a2KS#Usre>9>ywAVn|l_F zR#dNZ#-+}-nt{BYmQi-xQ_O2*y@g-xAp-ZzKB1=M9$32uvJ8jD!(Q0A2YoMQ-ilj2ib<;3mO}_lD^1}f|r)n~xz1(aR zVahuzRHx!pZ8JjW&HB>nxk~=MAg^(BZiz!XZns;3PJ@OVhNp|x{50UUlkc1NA=0Yi z+D8{*+$*Kb!b6_YjOF(+o(S%{+)2HS+&m|`5$pBk;p%YH*vR?Ecm|I$-fVE*UW2_o z2xmyZFK7R7lembmXr2)B7x{gvVtW*H=xGY1PM%6$!@iFWQS7TB)RGBHe?}NDj*olo zXv*e02?BQvo@!dXdY0qg8+~vFdDZ2dz6zFjQF0xI&#vRb;u<24XKgk5iI_V?n|M$p z9}_?N?*7 z4F-QEmkg+C+@6k@Z>sv;-VBW9M@Ry_aFr0GW*Vp`UWpT`*m*X1%UnAYjW-9~J%0D8 zf-^vCYl=7=gu1+)ByLt92X2GY21Vp#ws?uBEVbi%->q`;!T)hk#h%zW{B+Km9_#Eu z?!mPzXiV*bvA-GyEanA6`>g3YATxk6gDF}$Ciy#0wtMuJSx-D2{%S#pZ>Oac|4}(v zNs%`Bq&3}s0q#_oWsW;Eh9Vlb4qrvb;5?a9ggqnLnDtMI++YUeG_S}KalnLmN#rHs zp99@!z*Ns4E9Jvu^jE*E!U*DW#4x8LlbiAi<3g3C@sv~|?hTB$4||role;Yhp$gAxa)37<0gCjv!A4{t{q6V5Wz>U0j?W&9f(-e#ofA|swBSWPpL zv9V$j+b(@ayp-urKM{25QZOQ8c;Cfua-Z9{pWYPk?-K8M4E7&J_11kF0078Y79mRe z^uw&6bG{}_07DS6kfZ06WFBF$ZfgYGbhyei0Ui(q4hBdBoJ%s}$!9YkuAI_RxR< zhy!&86_iH4?)BuoRYCeKKccr2zSSB;{$TIQP*$Gz+ADzWZ zCHIZyiLn5hvoBqo8zEwXF*YB<3X7qUYzga}?(f0OKZI$`OGE5Q>LfBra|mnQs(6bI zJwutlBy&3=%9iMIPe_Yh@CY<59qn=c8r{gAkYmsbse@xHWmA)h)Z}U$E z+8z@+8oLVbKcaXHfH~5^)Fg!8e*qWMy#(6m=$}STgBuCskfXmujp|#f@JXVF^!9gB zh3}cjjwzGkQy#FIL#3;hz@cs{bi2Q#_2(KG@8Ou}R80Kz*D79>%gBh5H4co$#H{Md zCubSen~Y)ot9`g8AO}$`fMy|;=H#TxYOushJvltON9*ZbAq(~Jy~;eK8ae(Ftr2)e z%qeHU0hDNrUfaW6sYwYK$iY=zOC^%rk-)IH(1=0wi$Tf#TK*1BCGrlueOC4h`xZa* zK#9n_@HDRFx%4UF=4pCnrQTc zeypX!#>26M&+S6y#q!gH@8Yqn4*w_xyi3fwBa?i&bt zw-8|tirPi4xdt2Ec&M*Bm$(bxE1)l#-42j{#juKd+Xu3oL)!(1T#y`E&;}cKtPl`v z)vygZ8~o)hLln{hqc@1SWiJ$tyy;ykki+YY8THCwH%kC{NNgle*?1q`+XT#bmgknT zqo&G)_SR#E;I$(W@w+xjKPP+P6ql zX6r0-il}Z5#c<#IYfJj~=SCgv5DXwkf0y(%NHN&s$sc+fE@RLI!Lb>aT#d^+0somH zJK{z0LHorP-FBntViAFpIhyvpDTAuJ;OX6DwpW5;&@{z6Wk=c0*ZCVE?}~B;91sy$GYT90-?=wQtgi^vylei z9h0Q~aW3Y-dRG^!Qc{qisW)hN@uU7a4$~yt_H}YO&gDwrB*&2&tR~lzPmRyaQHkcf zSsqMjmU)l*6$~3Lgb~W&btFGd<7Cg<#Iy@)N zoLE!%o8IaAJ9qKZgxE;jI-lEXJrnxR-w4+A`xg*rhQI)XutyaIq!;;oc#1(&H&hF2 zq4*V2+_OhFlD*i&s7=IpUMf*c!5AOM$FALk3l_vG7d z4DDbK@b@knHlvW)Csx$R3_>yp1%<))(cDYB0(gLHJ-1^`Iht@c09ivI#^$shAul+O zyMZ<58%Md#PSYx*UAu7gsiSMuJ~aNr zVl97gp%Pm=nA`H5Rz83>R9xkEl%ULv7f!GzmlZK8fzN75q=ekbFP_P$274%oyg4$x z>^VpaR@v}4C@xeuQxw1lU^>i9q(Htn-UV6H+XRUl>@bSr>%1X#&2Gr}%XxOAE0tH) z8Ee|Aw+z3fU8lQ6mDvf^EMarI`>9ogSEtqh*={n)p3F3a)c`|%FAi6JWLlSpJQ7(X zq$w%o$;>f1Z!O1+17ol0r9|qyNSKxJ7SWqtn$+xO2@Xk#wevyjSQnbq5zFhY)7jg+ zO3~GMBhBTjP*Q0NbX_OBM0#$NiuLUskeIt~T7km_x%OV^VsFQ;v)6qkPnmDx5!YTd z*juQz%zoE%MS|I2g=?Wf@kU1X%``D(b{e*j9!#gf#Cp-G^#tuHP@w8!#(t(S8s~)H z_3f>?qfZjy7Keyr`cXk^=F9FvzMDbnQZJ7a6nFYJuKZXbpw8$bH}zPE4SRoN&%WFspuM~C2(j*=71J}mNTJH&RVcujD8@JWj>t+{UaL8P}%@?>O@T+lh6tq z-~Na?rc;U60-EEo{qZr8`SJ0mi-Cp(O6d;W!mt%nmtA8n)2SlH3UMIuJtfU=1=j{g zuLVU{4j2xAHK?`Nt_he>AOw9Nb0^KL?leO-QZ809XzD;Z_#|{W3$(jNLTgy4^o6l#p9*U&7X7p&%qC-ntXQS1y z=a!=%Q}rJ41p%ORC6R5cViurNhy~+Ma|KI>_OG<$1eedkVu4VlN+ZQ^kXkg2%|P>I zFLJ;OfIC$excVxR+Q{EGeI`lngPTnhtkrgU>lLB|d-cTH+{zJwu}Cq7D1^!OB-6JR zC?O69aw!0VFbSRFhD|H8GJ?H}q7z34qc{`d9K!8ODaA!~SgHe$&gin_h z4o*-aG`RxfLWKO$nM}+YitjOj?LdNU*KeQ~F~1$Y53u*?8N!U0MY2{WtIUM>AF8nv;C1xd*JP}(DBNwmB4 z&6t{KHCfCH5CGbwf93(QhdpU? z4bKNY11D~LW=hz5>!Sh(zHTH@l`S0Mf@fZdF8G%Cx#V8q(ph&u&urg^9; zo)JO`+abPn37BFA2SmG>``WcOoecRKAsaknf8-~Bg-nL?*bQN-xd$W{ks+$20P)Y~ za*B|^L`G#g4Kd8w`rGr zfp*xMEMgpR19UYE=8>tl6O7PKx_EbKOK|@pQwBt$5{}&GScE6WI=c`Bt{IXM{g$oI z+iv6Bn1+?e_MBdG&MEo!8hZRFx+@kCEbpK`7?Br6N!Mr)OM;l32I?HZTUnY6HT;{~ zIVHqumRrO%ZW*G@McBJ0ZHA3tAnv;!WbxTLad^3KX9psQ7oZkh^I$zN2jPftcAJ;a zvdMcDnqoVeba<75QW(59Em2H9Q75YR3Kzl@&Sft>Bc>Xh7|Z)W$635HKQj0U~r z5%!>vVXc&_E4KyI&SDD*4csPQ`aWa2DXGX&ik-pXY1S17KYe_AgT6`5;(H-1ubC z&5OX9fB#na_Nz;TFZga~%B0S`fo*pcwM*vQO^g-x?OAB4R*^<};$=SkP4YZ)A3w2? zAGOAUl&mG3NZPvj%9O`e-LtD` zKqtotyC`KTcDGf@xL+VnUeTls-Wai$1}7M0&Nl{U zop`3=1||MdYPGC`Q%4kLB1ycmA1V^`Fi_kPIo7VKw<1Vr0#Mk}1RO7w?EmPj)PG5c zD!4(yUI@-H360YkZ5E;HfJ$S|@>Y!_+c@mVzP}boQBXAwnEMX6JSB-EJO}%_P^{dp zC(3waI@Mw%k9-+oZwZR4k7DKzm2_)|E5A;uZv%&2^=tQ6;!W>K9E!IHb z1WDn?mWdvg?n7UH6rQslv)|`9>3jtCUy>6CmzpYD7)c#yV`Njl6aj?Dn)SN?bG)7GC{X#$SaE*liF1)iK@xUNg$3ZMhu24R;cY9XV8S3AU zL@4y?yUegwIfk17rZBUbb+Unhdt(*hI{OKk&`#xXm&MqdYcHuOabQgV05M%%fEGdi zdpE^zW#iTnji~!;F595yqmT$ zI%Se*3*|(hNi1B^VTOvmr0xbcms8?G+<~`uB%dTZvE^3J`J_d_s-L@dJ!ac$e1Uwc zLxr>vYh@T4NRDgMf5C?}x9j}5d~Wn_>cV>94WxIjKMjnwa?zduNDi~5PdCq_+z1IW zffQ~8o79TG{UZNgstvm+W1=ypZUiE(7%8oSAlZ3tkEv=?6Q2)X^bK33iK2kS00005 C<0yXs literal 0 HcmV?d00001 diff --git a/img/grappling hook.webp b/img/grappling hook.webp deleted file mode 100644 index 4b54ade3b55b6b42fbb392298116d23729aee3b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7280 zcmai&WmFwakcJ0>ySvN9;o>1k2<~n{gIjR7;1amF2iM^45J+%`;OFJPE|kiR-Nja?q5A>vQko3WB{NoDW;;O@>T~000632^M?Um06nr`E~Kvp7357&+@;R@gFw%FBbZTJzO1KUvpIcVP_3B@mDr`Wk$>Y!6yHMO&y*8 z$%nk=2-$sh`)93x#y=gSncHiszs^XnMh3V5YJfB#_V4#Q4gk%e z06?_#uSS;(0GJ^F&@}O{_V1oJ8ao@m)_>jPSA(^%0D#Ny0D%4x0B|P(07dUVW3TT2 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 QTd_r { - who.isShielded = true - }); - } - if (tech.fragments) { - b.targetedNail(this.vertices[2], tech.fragments * Math.floor(2 + Math.random())) - } - if (tech.isFoamBall) { - for (let i = 0, len = 3 * this.mass; i < len; i++) { - const radius = 5 + 8 * Math.random() - const velocity = { - x: Math.max(0.5, 2 - radius * 0.1), - y: 0 + }], + + // [{ + // x: -10, + // y: 2, + // index: 0, + // isInternal: false + // }, { + // x: -10, + // y: -2, + // index: 1, + // isInternal: false + // }, { + // x: 35, + // y: -3, + // index: 2, + // isInternal: false + // }, { + // x: 37, + // y: -2, + // index: 3, + // isInternal: false + // }, { + // x: 40, + // y: 0, + // index: 4, + // isInternal: false + // }, { + // x: 37, + // y: 2, + // index: 5, + // isInternal: false + // }, { + // x: 35, + // y: 3, + // index: 6, + // isInternal: false + // }], + { + angle: angle, + friction: 1, + frictionAir: 0.4, + thrustMag: 0.13, + dmg: 6, //damage done in addition to the damage from momentum + classType: "bullet", + endCycle: simulation.cycle + 70, + isSlowPull: false, + collisionFilter: { + category: cat.bullet, + mask: tech.isShieldPierce ? cat.body | cat.mob | cat.mobBullet : cat.body | cat.mob | cat.mobBullet | cat.mobShield, + }, + minDmgSpeed: 4, + // lookFrequency: Math.floor(7 + Math.random() * 3), + density: 0.004, //0.001 is normal for blocks, 0.004 is normal for harpoon, 0.004*6 when buffed + drain: 0.001, + beforeDmg(who) { + if (tech.isShieldPierce && who.isShielded) { //disable shields + who.isShielded = false + requestAnimationFrame(() => { + who.isShielded = true + }); + } + // if (tech.fragments) { + // b.targetedNail(this.vertices[2], tech.fragments * Math.floor(2 + Math.random())) + // } + // if (tech.isFoamBall) { + // for (let i = 0, len = 3 * this.mass; i < len; i++) { + // const radius = 5 + 8 * Math.random() + // const velocity = { + // x: Math.max(0.5, 2 - radius * 0.1), + // y: 0 + // } + // b.foam(this.position, Vector.rotate(velocity, 6.28 * Math.random()), radius) + // } + // // this.endCycle = 0; + // } + if (m.fieldCDcycle < m.cycle + 40) m.fieldCDcycle = m.cycle + 40 //extra long cooldown on hitting mobs + this.retract() + }, + caughtPowerUp: null, + dropCaughtPowerUp() { + if (this.caughtPowerUp) { + this.caughtPowerUp.collisionFilter.category = cat.powerUp + this.caughtPowerUp.collisionFilter.mask = cat.map | cat.powerUp + this.caughtPowerUp = null + } + }, + onEnd() { + if (this.caughtPowerUp && !simulation.isChoosing && (this.caughtPowerUp.name !== "heal" || m.health !== m.maxHealth || tech.isOverHeal)) { + let index = null //find index + for (let i = 0, len = powerUp.length; i < len; ++i) { + if (powerUp[i] === this.caughtPowerUp) index = i + } + if (index !== null) { + powerUps.onPickUp(this.caughtPowerUp); + this.caughtPowerUp.effect(); + Matter.Composite.remove(engine.world, this.caughtPowerUp); + powerUp.splice(index, 1); + if (tech.isHarpoonPowerUp) tech.harpoonDensity = 0.004 * 6 //0.005 is normal + } else { + this.dropCaughtPowerUp() } - b.foam(this.position, Vector.rotate(velocity, 6.28 * Math.random()), radius) - } - // this.endCycle = 0; - } - }, - caughtPowerUp: null, - dropCaughtPowerUp() { - if (this.caughtPowerUp) { - this.caughtPowerUp.collisionFilter.category = cat.powerUp - this.caughtPowerUp.collisionFilter.mask = cat.map | cat.powerUp - this.caughtPowerUp = null - } - }, - onEnd() { - if (this.caughtPowerUp && !simulation.isChoosing && (this.caughtPowerUp.name !== "heal" || m.health !== m.maxHealth || tech.isOverHeal)) { - let index = null //find index - for (let i = 0, len = powerUp.length; i < len; ++i) { - if (powerUp[i] === this.caughtPowerUp) index = i - } - if (index !== null) { - powerUps.onPickUp(this.caughtPowerUp); - this.caughtPowerUp.effect(); - Matter.Composite.remove(engine.world, this.caughtPowerUp); - powerUp.splice(index, 1); - if (tech.isHarpoonPowerUp) tech.harpoonDensity = 0.004 * 6 //0.005 is normal } else { this.dropCaughtPowerUp() } - } else { - this.dropCaughtPowerUp() - } - }, - draw() { - const where = { - x: m.pos.x + 30 * Math.cos(m.angle), - y: m.pos.y + 30 * Math.sin(m.angle) - } - const sub = Vector.sub(where, this.vertices[0]) - const controlPoint = Vector.add(where, Vector.mult(sub, -0.5)) - ctx.strokeStyle = "#000" // "#0ce" - ctx.lineWidth = 0.5 - ctx.beginPath(); - ctx.moveTo(where.x, where.y); - ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, this.vertices[0].x, this.vertices[0].y) - // ctx.lineTo(this.vertices[0].x, this.vertices[0].y); - ctx.stroke(); - //draw harpoon spikes - const spikeLength = 2 - ctx.beginPath(); - const spike1 = Vector.add(this.vertices[1], Vector.mult(Vector.sub(this.vertices[1], this.vertices[2]), spikeLength)) - ctx.moveTo(this.vertices[2].x, this.vertices[2].y); - ctx.lineTo(spike1.x, spike1.y); - ctx.lineTo(this.vertices[3].x, this.vertices[3].y); - - const spike2 = Vector.add(this.vertices[3], Vector.mult(Vector.sub(this.vertices[3], this.vertices[2]), spikeLength)) - ctx.moveTo(this.vertices[2].x, this.vertices[2].y); - ctx.lineTo(spike2.x, spike2.y); - ctx.lineTo(this.vertices[1].x, this.vertices[1].y); - ctx.fillStyle = '#000' - ctx.fill(); - }, - returnToPlayer() { - if (Vector.magnitude(Vector.sub(this.position, m.pos)) < returnRadius) { //near player - this.endCycle = 0; - // if (m.energy < 0.05) { - // m.fireCDcycle = m.cycle + 120; //fire cooldown - // } else if (m.cycle + 15 * b.fireCDscale < m.fireCDcycle) { - // m.fireCDcycle = m.cycle + 15 * b.fireCDscale //lower cd to 25 if it is above 25 - // } - - if (m.energy < 0.05) this.dropCaughtPowerUp() - - //recoil on catching - const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.0001 : 0.0002)) - player.force.x += momentum.x - player.force.y += momentum.y - // refund ammo - b.guns[9].ammo++; - simulation.updateGunHUD(); - - // for (i = 0, len = b.guns.length; i < len; i++) { //find which gun - // if (b.guns[i].name === "harpoon") { - // b.guns[i].ammo++; - // simulation.updateGunHUD(); - // break; - // } - // } - } else { - if (m.energy > this.drain) m.energy -= this.drain - const sub = Vector.sub(this.position, m.pos) - const rangeScale = 1 + 0.000001 * Vector.magnitude(sub) * Vector.magnitude(sub) //return faster when far from player - const returnForce = Vector.mult(Vector.normalise(sub), rangeScale * this.thrustMag * this.mass) - this.force.x -= returnForce.x - this.force.y -= returnForce.y - this.grabPowerUp() - } - this.draw(); - }, - grabPowerUp() { //grab power ups near the tip of the harpoon - if (this.caughtPowerUp) { - Matter.Body.setPosition(this.caughtPowerUp, Vector.add(this.vertices[2], this.velocity)) - Matter.Body.setVelocity(this.caughtPowerUp, { - x: 0, - y: 0 - }) - } else { //&& simulation.cycle % 2 - for (let i = 0, len = powerUp.length; i < len; ++i) { - const radius = powerUp[i].circleRadius + 50 - if (Vector.magnitudeSquared(Vector.sub(this.vertices[2], powerUp[i].position)) < radius * radius) { - if (powerUp[i].name !== "heal" || m.health !== m.maxHealth || tech.isOverHeal) { - this.caughtPowerUp = powerUp[i] - Matter.Body.setVelocity(powerUp[i], { - x: 0, - y: 0 - }) - Matter.Body.setPosition(powerUp[i], this.vertices[2]) - powerUp[i].collisionFilter.category = 0 - powerUp[i].collisionFilter.mask = 0 - this.thrustMag *= 0.6 - this.endCycle += 0.5 //it pulls back slower, so this prevents it from ending early - break //just pull 1 power up if possible - } - } + }, + draw() { + const where = { + x: m.pos.x + 30 * Math.cos(m.angle), + y: m.pos.y + 30 * Math.sin(m.angle) } - } - }, - do() { - if (input.fire) { //&& !Matter.Query.collides(this, body).length - this.grabPowerUp() - if (this.endCycle < simulation.cycle + 1) { //if at end of lifespan, but player is holding down fire, force retraction - this.endCycle = simulation.cycle + 60 - // m.fireCDcycle = m.cycle + 120 // cool down - this.do = this.returnToPlayer - Matter.Body.setDensity(this, 0.0005); //reduce density on return - if (this.angularSpeed < 0.5) this.torque += this.inertia * 0.001 * (Math.random() - 0.5) //(Math.round(Math.random()) ? 1 : -1) - this.collisionFilter.mask = cat.map | cat.mob | cat.mobBullet | cat.mobShield // | cat.body - } - } else { - //if not enough energy - if (m.energy < 0.05) this.dropCaughtPowerUp() - // const returnForce = Vector.mult(Vector.normalise(Vector.sub(this.position, m.pos)), 3 * this.thrustMag * this.mass) - // this.force.x -= returnForce.x - // this.force.y -= returnForce.y - // this.frictionAir = 0.002 - // this.do = () => { - // if (this.speed < 20) this.force.y += 0.0005 * this.mass; - // } + const sub = Vector.sub(where, this.vertices[0]) + const controlPoint = Vector.add(where, Vector.mult(sub, -0.5)) + ctx.strokeStyle = "#000" // "#0ce" + ctx.lineWidth = 0.5 + ctx.beginPath(); + ctx.moveTo(where.x, where.y); + ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, this.vertices[0].x, this.vertices[0].y) + // ctx.lineTo(this.vertices[0].x, this.vertices[0].y); + ctx.stroke(); + //draw harpoon spikes + const spikeLength = 2 + ctx.beginPath(); + const spike1 = Vector.add(this.vertices[1], Vector.mult(Vector.sub(this.vertices[1], this.vertices[2]), spikeLength)) + ctx.moveTo(this.vertices[2].x, this.vertices[2].y); + ctx.lineTo(spike1.x, spike1.y); + ctx.lineTo(this.vertices[3].x, this.vertices[3].y); - // } else { - //return to player + const spike2 = Vector.add(this.vertices[3], Vector.mult(Vector.sub(this.vertices[3], this.vertices[2]), spikeLength)) + ctx.moveTo(this.vertices[2].x, this.vertices[2].y); + ctx.lineTo(spike2.x, spike2.y); + ctx.lineTo(this.vertices[1].x, this.vertices[1].y); + ctx.fillStyle = '#000' + ctx.fill(); + }, + retract() { this.do = this.returnToPlayer this.endCycle = simulation.cycle + 60 Matter.Body.setDensity(this, 0.0005); //reduce density on return if (this.angularSpeed < 0.5) this.torque += this.inertia * 0.001 * (Math.random() - 0.5) //(Math.round(Math.random()) ? 1 : -1) - this.collisionFilter.mask = cat.map | cat.mob | cat.mobBullet | cat.mobShield // | cat.body - //recoil on catching + this.collisionFilter.mask = 0//cat.map | cat.mob | cat.mobBullet | cat.mobShield // | cat.body + //recoil on pulling grapple back const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.0001 : 0.0002)) player.force.x += momentum.x player.force.y += momentum.y - // } - } - //grappling hook - if (input.fire && Matter.Query.collides(this, map).length) { - Matter.Body.setPosition(this, Vector.add(this.position, { - x: 20 * Math.cos(this.angle), - y: 20 * Math.sin(this.angle) - })) - if (Matter.Query.collides(this, map).length) { - Matter.Body.setVelocity(this, { - x: 0, - y: 0 - }); - Matter.Sleeping.set(this, true) - this.endCycle = simulation.cycle + 5 - this.dropCaughtPowerUp() - this.do = () => { - //between player nose and the grapple - const sub = Vector.sub(this.vertices[0], { - x: m.pos.x + 30 * Math.cos(m.angle), - y: m.pos.y + 30 * Math.sin(m.angle) - }) - let dist = Vector.magnitude(sub) - if (input.fire) { - // m.fireCDcycle = m.cycle + 30; // cool down if out of energy - m.fireCDcycle = m.cycle + 5 + 40 * b.fireCDscale + 60 * (m.energy < 0.05) - this.endCycle = simulation.cycle + 10 - if (input.down) { //down - dist = 0 - player.force.y += 5 * player.mass * simulation.g; - } - if (m.energy > this.drain) { - Matter.Body.setVelocity(player, { - x: player.velocity.x * 0.8, - y: player.velocity.y * 0.8 - }); + }, + returnToPlayer() { + if (m.fieldCDcycle < m.cycle + 5) m.fieldCDcycle = m.cycle + 5 + if (Vector.magnitude(Vector.sub(this.position, m.pos)) < returnRadius) { //near player + this.endCycle = 0; + //recoil on catching grapple + const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.0001 : 0.0002)) + player.force.x += momentum.x + player.force.y += momentum.y - - //need to scale the friction differently based on distance? - // if (dist > 500) { - const pull = Vector.mult(Vector.normalise(sub), 0.0008 * Math.min(Math.max(15, dist), 200)) - player.force.x += pull.x - player.force.y += pull.y - // } - - if (dist > 500) { - m.energy -= this.drain - if (m.energy < 0) { - this.endCycle = 0; - if (m.cycle + 50 < m.fireCDcycle) m.fireCDcycle = m.cycle + 50 - // refund ammo - b.guns[9].ammo++; - simulation.updateGunHUD(); - // for (i = 0, len = b.guns.length; i < len; i++) { //find which gun - // if (b.guns[i].name === "harpoon") { - // break; - // } - // } - } - } + } else { + if (m.energy > this.drain) m.energy -= this.drain + const sub = Vector.sub(this.position, m.pos) + const rangeScale = 1 + 0.000001 * Vector.magnitude(sub) * Vector.magnitude(sub) //return faster when far from player + const returnForce = Vector.mult(Vector.normalise(sub), rangeScale * this.thrustMag * this.mass) + this.force.x -= returnForce.x + this.force.y -= returnForce.y + this.grabPowerUp() + } + this.draw(); + }, + destroyBlocks() { + const blocks = Matter.Query.collides(this, body) + if (blocks.length && !blocks[0].bodyA.isNotHoldable) { + if (blocks[0].bodyA.mass > 2.5) this.retract() + const block = blocks[0].bodyA.vertices + Composite.remove(engine.world, blocks[0].bodyA) + body.splice(body.indexOf(blocks[0].bodyA), 1) + //animate the block fading away + simulation.ephemera.push({ + name: "blockFadeOut", + count: 25, //cycles before it self removes + do() { + this.count-- + if (this.count < 0) simulation.removeEphemera(this.name) + ctx.beginPath(); + ctx.moveTo(block[0].x, block[0].y); + for (let j = 1; j < block.length; j++) ctx.lineTo(block[j].x, block[j].y); + ctx.lineTo(block[0].x, block[0].y); + ctx.lineWidth = 2; + ctx.strokeStyle = `rgba(0,0,0,${this.count / 25})` + ctx.stroke(); + }, + }) + } + }, + grabPowerUp() { //grab power ups near the tip of the harpoon + if (this.caughtPowerUp) { + Matter.Body.setPosition(this.caughtPowerUp, Vector.add(this.vertices[2], this.velocity)) + Matter.Body.setVelocity(this.caughtPowerUp, { x: 0, y: 0 }) + } else { + for (let i = 0, len = powerUp.length; i < len; ++i) { + const radius = powerUp[i].circleRadius + 50 + if (Vector.magnitudeSquared(Vector.sub(this.vertices[2], powerUp[i].position)) < radius * radius) { + if (powerUp[i].name !== "heal" || m.health !== m.maxHealth || tech.isOverHeal) { + this.caughtPowerUp = powerUp[i] + Matter.Body.setVelocity(powerUp[i], { x: 0, y: 0 }) + Matter.Body.setPosition(powerUp[i], this.vertices[2]) + powerUp[i].collisionFilter.category = 0 + powerUp[i].collisionFilter.mask = 0 + this.thrustMag *= 0.6 + this.endCycle += 0.5 //it pulls back slower, so this prevents it from ending early + this.retract() + break //just pull 1 power up if possible } - if (tech.isImmuneGrapple && m.immuneCycle < m.cycle + 10) { - m.immuneCycle = m.cycle + 10; - if (m.energy > 0.001) { - m.energy -= 0.001 - } else { //out of energy - Matter.Sleeping.set(this, false) - this.collisionFilter.category = 0 - this.collisionFilter.mask = 0 - this.do = this.returnToPlayer - this.endCycle = simulation.cycle + 60 - m.fireCDcycle = m.cycle + 120; //fire cooldown - //recoil on catching - const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.0001 : 0.0002)) - player.force.x += momentum.x - player.force.y += momentum.y - } - } - } else { - Matter.Sleeping.set(this, false) - this.collisionFilter.category = 0 - this.collisionFilter.mask = 0 - this.do = this.returnToPlayer - this.endCycle = simulation.cycle + 60 - //recoil on catching - const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.0001 : 0.0002)) - player.force.x += momentum.x - player.force.y += momentum.y } - this.draw(); } } - } - this.force.x += this.thrustMag * this.mass * Math.cos(this.angle); - this.force.y += this.thrustMag * this.mass * Math.sin(this.angle); - this.draw() - }, - }); + m.grabPowerUp(); + }, + do() { + if (m.fieldCDcycle < m.cycle + 5) m.fieldCDcycle = m.cycle + 5 + if (input.field) { //&& !Matter.Query.collides(this, body).length + this.destroyBlocks() + this.grabPowerUp() + // if (this.endCycle < simulation.cycle + 1) { //if at end of lifespan, but player is holding down field, force retraction + // this.endCycle = simulation.cycle + 30 + // // m.fireCDcycle = m.cycle + 120 // cool down + // this.do = this.returnToPlayer + // Matter.Body.setDensity(this, 0.0005); //reduce density on return + // if (this.angularSpeed < 0.5) this.torque += this.inertia * 0.001 * (Math.random() - 0.5) //(Math.round(Math.random()) ? 1 : -1) + // this.collisionFilter.mask = cat.map | cat.mob | cat.mobBullet | cat.mobShield // | cat.body + // } + } else { + //if not enough energy + // if (m.energy < 0.01) this.dropCaughtPowerUp() + // const returnForce = Vector.mult(Vector.normalise(Vector.sub(this.position, m.pos)), 3 * this.thrustMag * this.mass) + // this.force.x -= returnForce.x + // this.force.y -= returnForce.y + // this.frictionAir = 0.002 + // this.do = () => { + // if (this.speed < 20) this.force.y += 0.0005 * this.mass; + // } + + // } else { + //return to player + this.retract() + // } + } + //grappling hook + if (input.field && Matter.Query.collides(this, map).length) { + Matter.Body.setPosition(this, Vector.add(this.position, { x: -20 * Math.cos(this.angle), y: -20 * Math.sin(this.angle) })) + if (Matter.Query.collides(this, map).length) { + Matter.Body.setVelocity(this, { x: 0, y: 0 }); + Matter.Sleeping.set(this, true) + this.endCycle = simulation.cycle + 5 + // this.dropCaughtPowerUp() + this.do = () => { + if (m.fieldCDcycle < m.cycle + 5) m.fieldCDcycle = m.cycle + 5 + // if (this.caughtPowerUp) { + // Matter.Body.setPosition(this.caughtPowerUp, Vector.add(this.vertices[2], this.velocity)) + // Matter.Body.setVelocity(this.caughtPowerUp, { x: 0, y: 0 }) + // } + this.grabPowerUp() + + //between player nose and the grapple + const sub = Vector.sub(this.vertices[0], { x: m.pos.x + 30 * Math.cos(m.angle), y: m.pos.y + 30 * Math.sin(m.angle) }) + let dist = Vector.magnitude(sub) + if (input.field) { + // m.fireCDcycle = m.cycle + 30; // cool down if out of energy + // m.fireCDcycle = m.cycle + 5 + 40 * b.fireCDscale + 60 * (m.energy < 0.05) + // if (m.fieldCDcycle < m.cycle + 5) m.fieldCDcycle = m.cycle + 5 + this.endCycle = simulation.cycle + 10 + if (input.down) { //down + this.isSlowPull = true + dist = 0 + player.force.y += 2.5 * player.mass * simulation.g; //adjust this to control fall rate while hooked and pressing down + } else if (input.up) { + this.isSlowPull = false + } + if (m.energy < this.drain) this.isSlowPull = true + + // pulling friction that allowed a slight swinging, but has high linear pull at short dist + const drag = 1 - 30 / Math.min(Math.max(100, dist), 700) - 0.1 * (player.speed > 70) + // console.log(player.speed) + Matter.Body.setVelocity(player, { x: player.velocity.x * drag, y: player.velocity.y * drag }); + const pullScale = 0.0004 + const pull = Vector.mult(Vector.normalise(sub), pullScale * Math.min(Math.max(15, dist), this.isSlowPull ? 70 : 200)) + //original pulling force with high friction and very linear pull + // Matter.Body.setVelocity(player, { x: player.velocity.x * 0.85, y: player.velocity.y * 0.85 }); + // const pull = Vector.mult(Vector.normalise(sub), 0.0008 * Math.min(Math.max(15, dist), this.isSlowPull ? 100 : 200)) + + player.force.x += pull.x + player.force.y += pull.y + if (dist > 500) { + m.energy -= this.drain + // if (m.energy < 0) this.endCycle = 0; + } + + // if (tech.isImmuneGrapple && m.immuneCycle < m.cycle + 10) { + // m.immuneCycle = m.cycle + 10; + // if (m.energy > 0.001) { + // m.energy -= 0.001 + // } else { //out of energy + // Matter.Sleeping.set(this, false) + // this.collisionFilter.category = 0 + // this.collisionFilter.mask = 0 + // this.do = this.returnToPlayer + // this.endCycle = simulation.cycle + 60 + // // m.fireCDcycle = m.cycle + 120; //fire cooldown + // if (m.fieldCDcycle < m.cycle + 120) m.fieldCDcycle = m.cycle + 120 + + // //recoil on catching + // const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.0001 : 0.0002)) + // player.force.x += momentum.x + // player.force.y += momentum.y + // } + // } + } else { + Matter.Sleeping.set(this, false) + this.retract() + // Matter.Sleeping.set(this, false) + // this.collisionFilter.category = 0 + // this.collisionFilter.mask = 0 + // this.do = this.returnToPlayer + // this.endCycle = simulation.cycle + 60 + // //recoil on catching + // const momentum = Vector.mult(Vector.sub(this.velocity, player.velocity), (m.crouch ? 0.0001 : 0.0002)) + // player.force.x += momentum.x + // player.force.y += momentum.y + } + this.draw(); + } + } + } + this.force.x += this.thrustMag * this.mass * Math.cos(this.angle); + this.force.y += this.thrustMag * this.mass * Math.sin(this.angle); + this.draw() + }, + }); Composite.add(engine.world, bullet[me]); //add bullet to world }, // grapple(where, angle = m.angle, harpoonSize = 1) { @@ -3178,20 +3223,24 @@ const b = { y: 100 * (Math.random() - 0.5) }, beforeDmg(who) { - if (tech.isSpawnBulletsOnDeath && who.alive && who.isDropPowerUp) { - setTimeout(() => { - if (!who.alive) { - for (let i = 0; i < 3; i++) { //spawn 3 more - b.worm(this.position) - bullet[bullet.length - 1].endCycle = Math.min(simulation.cycle + Math.floor(420 * tech.bulletsLastLonger), this.endCycle + 180 + Math.floor(60 * Math.random())) //simulation.cycle + Math.floor(420 * tech.bulletsLastLonger) - } - } - this.endCycle = 0; //bullet ends cycle after doing damage - }, 1); + if (who.isInvulnerable) { + Matter.Body.setVelocity(this, Vector.mult(this.velocity, 0.1)); } else { - this.endCycle = 0; //bullet ends cycle after doing damage + if (tech.isSpawnBulletsOnDeath && who.alive && who.isDropPowerUp) { + setTimeout(() => { + if (!who.alive) { + for (let i = 0; i < 3; i++) { //spawn 3 more + b.worm(this.position) + bullet[bullet.length - 1].endCycle = Math.min(simulation.cycle + Math.floor(420 * tech.bulletsLastLonger), this.endCycle + 180 + Math.floor(60 * Math.random())) //simulation.cycle + Math.floor(420 * tech.bulletsLastLonger) + } + } + this.endCycle = 0; //bullet ends cycle after doing damage + }, 1); + } else { + this.endCycle = 0; //bullet ends cycle after doing damage + } + if (this.isFreeze) mobs.statusSlow(who, 90) } - if (this.isFreeze) mobs.statusSlow(who, 90) }, onEnd() { if (tech.isMutualism && this.isMutualismActive && !tech.isEnergyHealth) { @@ -3295,8 +3344,10 @@ const b = { y: 100 * (Math.random() - 0.5) }, beforeDmg(who) { - this.endCycle = 0; //bullet ends cycle after doing damage - if (this.isFreeze) mobs.statusSlow(who, 90) + if (!who.isInvulnerable) { + this.endCycle = 0; //bullet ends cycle after doing damage + if (this.isFreeze) mobs.statusSlow(who, 90) + } }, onEnd() { if (tech.isMutualism && this.isMutualismActive && !tech.isEnergyHealth) { @@ -3304,7 +3355,7 @@ const b = { if (m.health > m.maxHealth) m.health = m.maxHealth; m.displayHealth(); } - console.log(this.dmg) + // console.log(this.dmg) }, do() { if (this.lockedOn && this.lockedOn.alive) { @@ -3425,13 +3476,15 @@ const b = { minDmgSpeed: 0, lockedOn: null, beforeDmg(who) { - if (tech.iceEnergy && !who.shield && !who.isShielded && who.isDropPowerUp && who.alive && m.immuneCycle < m.cycle) { - setTimeout(() => { - if (!who.alive) m.energy += tech.iceEnergy * 0.8 - }, 10); + if (!who.isInvulnerable) { + if (tech.iceEnergy && !who.shield && !who.isShielded && who.isDropPowerUp && who.alive && m.immuneCycle < m.cycle) { + setTimeout(() => { + if (!who.alive) m.energy += tech.iceEnergy * 0.8 + }, 10); + } + mobs.statusSlow(who, tech.iceIXFreezeTime) + this.endCycle = simulation.cycle } - mobs.statusSlow(who, tech.iceIXFreezeTime) - this.endCycle = simulation.cycle // if (tech.isHeavyWater) mobs.statusDoT(who, 0.15, 300) }, onEnd() { }, @@ -3507,27 +3560,29 @@ const b = { }, beforeDmg(who) { Matter.Body.setVelocity(this, Vector.mult(Vector.normalise(Vector.sub(this.position, who.position)), 10 + 10 * Math.random())); //push away from target - this.endCycle -= 130 this.cd = simulation.cycle + this.delay; - if (tech.isSporeFreeze) mobs.statusSlow(who, 90) - if (tech.isSpawnBulletsOnDeath && who.alive && who.isDropPowerUp) { - setTimeout(() => { - if (!who.alive) { - for (let i = 0; i < 2; i++) { //spawn 2 more - const speed = 10 + 5 * Math.random() - const angle = 2 * Math.PI * Math.random() - b.flea(this.position, { - x: speed * Math.cos(angle), - y: speed * Math.sin(angle) - }) + if (!who.isInvulnerable) { + this.endCycle -= 130 + if (tech.isSporeFreeze) mobs.statusSlow(who, 90) + if (tech.isSpawnBulletsOnDeath && who.alive && who.isDropPowerUp) { + setTimeout(() => { + if (!who.alive) { + for (let i = 0; i < 2; i++) { //spawn 2 more + const speed = 10 + 5 * Math.random() + const angle = 2 * Math.PI * Math.random() + b.flea(this.position, { + x: speed * Math.cos(angle), + y: speed * Math.sin(angle) + }) + } } - } - this.endCycle = 0; - }, 1); + this.endCycle = 0; + }, 1); + } + setTimeout(() => { + this.dmg = 0 + }) } - setTimeout(() => { - this.dmg = 0 - }) }, onEnd() { if (tech.isMutualism && this.isMutualismActive && !tech.isEnergyHealth) { @@ -3659,27 +3714,31 @@ const b = { deathCycles: 110 + RADIUS * 5, isImproved: false, beforeDmg(who) { - if (tech.isIncendiary && simulation.cycle + this.deathCycles < this.endCycle && !tech.isForeverDrones) { - const max = Math.max(Math.min(this.endCycle - simulation.cycle - this.deathCycles, 1500), 0) - b.explosion(this.position, max * 0.1 + this.isImproved * 110 + 60 * Math.random()); //makes bullet do explosive damage at end - if (tech.isForeverDrones) { - this.endCycle = 0 - b.drone({ - x: m.pos.x + 30 * (Math.random() - 0.5), - y: m.pos.y + 30 * (Math.random() - 0.5) - }, 5) - bullet[bullet.length - 1].endCycle = Infinity - } else { - this.endCycle -= max - } - } else { + if (who.isInvulnerable) { //move away from target after hitting const unit = Vector.mult(Vector.normalise(Vector.sub(this.position, who.position)), -20) Matter.Body.setVelocity(this, { x: unit.x, y: unit.y }); this.lockedOn = null - if (this.endCycle > simulation.cycle + this.deathCycles) { - this.endCycle -= 60 - if (simulation.cycle + this.deathCycles > this.endCycle) this.endCycle = simulation.cycle + this.deathCycles + } else { + if (tech.isIncendiary && simulation.cycle + this.deathCycles < this.endCycle && !tech.isForeverDrones) { + const max = Math.max(Math.min(this.endCycle - simulation.cycle - this.deathCycles, 1500), 0) + b.explosion(this.position, max * 0.1 + this.isImproved * 110 + 60 * Math.random()); //makes bullet do explosive damage at end + if (tech.isForeverDrones) { + this.endCycle = 0 + b.drone({ x: m.pos.x + 30 * (Math.random() - 0.5), y: m.pos.y + 30 * (Math.random() - 0.5) }, 5) + bullet[bullet.length - 1].endCycle = Infinity + } else { + this.endCycle -= max + } + } else { + //move away from target after hitting + const unit = Vector.mult(Vector.normalise(Vector.sub(this.position, who.position)), -20) + Matter.Body.setVelocity(this, { x: unit.x, y: unit.y }); + this.lockedOn = null + if (this.endCycle > simulation.cycle + this.deathCycles) { + this.endCycle -= 60 + if (simulation.cycle + this.deathCycles > this.endCycle) this.endCycle = simulation.cycle + this.deathCycles + } } } }, @@ -4193,28 +4252,39 @@ const b = { }; } bullet[me].beforeDmg = function (who) { - if (tech.oneSuperBall) mobs.statusStun(who, 120) // (2.3) * 2 / 14 ticks (2x damage over 7 seconds) - if (tech.isFoamBall) { - for (let i = 0, len = 5 * this.mass; i < len; i++) { - const radius = 5 + 8 * Math.random() - const velocity = { x: Math.max(0.5, 2 - radius * 0.1), y: 0 } - b.foam(this.position, Vector.rotate(velocity, 6.28 * Math.random()), radius) + if (!who.isInvulnerable) { + if (tech.oneSuperBall) mobs.statusStun(who, 120) // (2.3) * 2 / 14 ticks (2x damage over 7 seconds) + if (tech.isFoamBall) { + for (let i = 0, len = 5 * this.mass; i < len; i++) { + const radius = 5 + 8 * Math.random() + const velocity = { x: Math.max(0.5, 2 - radius * 0.1), y: 0 } + b.foam(this.position, Vector.rotate(velocity, 6.28 * Math.random()), radius) + } + this.endCycle = 0 } - this.endCycle = 0 - } - if (tech.isIncendiary) { - b.explosion(this.position, this.mass * 280); //makes bullet do explosive damage at end - this.endCycle = 0 - } else if (tech.isSuperBounce) { - const cycle = () => { - Matter.Body.setDensity(bullet[me], bullet[me].calcDensity() * 1.33);//33% more density and damage - this.endCycle = simulation.cycle + Math.floor(300 + 90 * Math.random()); //reset to full duration of time - Matter.Body.setVelocity(this, Vector.mult(Vector.normalise(this.velocity), 60)); //reset to high velocity + if (tech.isIncendiary) { + b.explosion(this.position, this.mass * 280); //makes bullet do explosive damage at end + this.endCycle = 0 + } else if (tech.isSuperBounce) { + const cycle = () => { + Matter.Body.setDensity(bullet[me], bullet[me].calcDensity() * 1.33);//33% more density and damage + this.endCycle = simulation.cycle + Math.floor(300 + 90 * Math.random()); //reset to full duration of time + Matter.Body.setVelocity(this, Vector.mult(Vector.normalise(this.velocity), 60)); //reset to high velocity + + let count = 5 + const wait = () => { + count-- + if (count > 0) requestAnimationFrame(wait); + simulation.drawList.push({ //add dmg to draw queue + x: this.position.x, + y: this.position.y, + radius: radius, + color: 'rgba(255, 0, 0, 0.33)', + time: 8 + }); + } + requestAnimationFrame(wait); - let count = 5 - const wait = () => { - count-- - if (count > 0) requestAnimationFrame(wait); simulation.drawList.push({ //add dmg to draw queue x: this.position.x, y: this.position.y, @@ -4223,17 +4293,8 @@ const b = { time: 8 }); } - requestAnimationFrame(wait); - - simulation.drawList.push({ //add dmg to draw queue - x: this.position.x, - y: this.position.y, - radius: radius, - color: 'rgba(255, 0, 0, 0.33)', - time: 8 - }); + requestAnimationFrame(cycle); } - requestAnimationFrame(cycle); } }; }, @@ -5394,7 +5455,7 @@ const b = { cd: 0, fireCount: 0, fireLimit: 5 + 2 * tech.isFoamBotUpgrade, - delay: Math.floor((150 + (tech.isFoamBotUpgrade ? 0 : 250)) * b.fireCDscale),// + 30 - 20 * tech.isFoamBotUpgrade,//20 + Math.floor(85 * b.fireCDscale) - 20 * tech.isFoamBotUpgrade, + delay: Math.floor((145 + (tech.isFoamBotUpgrade ? 0 : 230)) * b.fireCDscale),// + 30 - 20 * tech.isFoamBotUpgrade,//20 + Math.floor(85 * b.fireCDscale) - 20 * tech.isFoamBotUpgrade, acceleration: 0.005 * (1 + 0.5 * Math.random()), range: 60 * (1 + 0.3 * Math.random()) + 3 * b.totalBots(), //how far from the player the bot will move endCycle: Infinity, @@ -7717,7 +7778,7 @@ const b = { return `spray bubbly foam that sticks to mobs
slows mobs and does damage over time
${this.ammoPack.toFixed(0)} bubbles per ${powerUps.orb.ammo()}` }, ammo: 0, - ammoPack: 24, + ammoPack: 28, have: false, charge: 0, isDischarge: false, @@ -7842,9 +7903,9 @@ const b = { if (tech.isRailGun) { this.do = this.railDo this.fire = this.railFire - } else if (tech.isGrapple) { - this.do = () => { } - this.fire = this.grappleFire + // } else if (tech.isGrapple) { + // this.do = () => { } + // this.fire = this.grappleFire } else { this.do = () => { } this.fire = this.harpoonFire @@ -8054,36 +8115,36 @@ const b = { m.fireCDcycle = m.cycle + 10 //can't fire until mouse is released this.charge += 0.00001 }, - grappleFire() { - const harpoonSize = (tech.isLargeHarpoon ? 1 + 0.1 * Math.sqrt(this.ammo) : 1) //* (m.crouch ? 0.7 : 1) - const where = { - x: m.pos.x + harpoonSize * 40 * Math.cos(m.angle), - y: m.pos.y + harpoonSize * 40 * Math.sin(m.angle) - } - const num = Math.min(this.ammo, tech.extraHarpoons + 1) - if (!m.crouch && num > 1) { //multiple harpoons - const SPREAD = 0.06 - let angle = m.angle - SPREAD * num / 2; - for (let i = 0; i < num; i++) { - if (this.ammo > 0) { - this.ammo-- - b.grapple(where, angle, true, harpoonSize) - angle += SPREAD - } - } - this.ammo++ //make up for the ammo used up in fire() - simulation.updateGunHUD(); - m.fireCDcycle = m.cycle + Math.floor(75 * b.fireCDscale) // cool down - // } else if (m.crouch) { - // b.harpoon(where, null, m.angle, harpoonSize, false, 70) - } else { - if (tech.crouchAmmoCount) tech.crouchAmmoCount = 1 - b.grapple(where, m.angle, harpoonSize) - } - // m.fireCDcycle = m.cycle + Math.floor(75 * b.fireCDscale) // cool down - m.fireCDcycle = m.cycle + 5 + 40 * b.fireCDscale + 60 * (m.energy < 0.05) + // grappleFire() { + // const harpoonSize = (tech.isLargeHarpoon ? 1 + 0.1 * Math.sqrt(this.ammo) : 1) //* (m.crouch ? 0.7 : 1) + // const where = { + // x: m.pos.x + harpoonSize * 40 * Math.cos(m.angle), + // y: m.pos.y + harpoonSize * 40 * Math.sin(m.angle) + // } + // const num = Math.min(this.ammo, tech.extraHarpoons + 1) + // if (!m.crouch && num > 1) { //multiple harpoons + // const SPREAD = 0.06 + // let angle = m.angle - SPREAD * num / 2; + // for (let i = 0; i < num; i++) { + // if (this.ammo > 0) { + // this.ammo-- + // b.grapple(where, angle, true, harpoonSize) + // angle += SPREAD + // } + // } + // this.ammo++ //make up for the ammo used up in fire() + // simulation.updateGunHUD(); + // m.fireCDcycle = m.cycle + Math.floor(75 * b.fireCDscale) // cool down + // // } else if (m.crouch) { + // // b.harpoon(where, null, m.angle, harpoonSize, false, 70) + // } else { + // if (tech.crouchAmmoCount) tech.crouchAmmoCount = 1 + // b.grapple(where, m.angle, harpoonSize) + // } + // // m.fireCDcycle = m.cycle + Math.floor(75 * b.fireCDscale) // cool down + // m.fireCDcycle = m.cycle + 5 + 40 * b.fireCDscale + 60 * (m.energy < 0.05) - }, + // }, harpoonFire() { const where = { x: m.pos.x + 30 * Math.cos(m.angle), diff --git a/js/level.js b/js/level.js index e5e2c2d..3e00aba 100644 --- a/js/level.js +++ b/js/level.js @@ -28,17 +28,17 @@ const level = { // m.immuneCycle = Infinity //you can't take damage // tech.tech[297].frequency = 100 // m.couplingChange(10) - // m.setField("wormhole") //1 standing wave 2 perfect diamagnetism 3 negative mass 4 molecular assembler 5 plasma torch 6 time dilation 7 metamaterial cloaking 8 pilot wave 9 wormhole + // m.setField("grappling hook") //1 standing wave 2 perfect diamagnetism 3 negative mass 4 molecular assembler 5 plasma torch 6 time dilation 7 metamaterial cloaking 8 pilot wave 9 wormhole 10 grappling hook // m.energy = 0 // simulation.molecularMode = 2 // m.damage(0.1); // b.giveGuns("super balls") //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("foam") //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("harpoon") //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[8].ammo = 100000000 // requestAnimationFrame(() => { tech.giveTech("MACHO") }); // for (let i = 0; i < 1; ++i) tech.giveTech("electrostatic induction") - // for (let i = 0; i < 1; ++i) tech.giveTech("enthalpy") - // for (let i = 0; i < 10; ++i) tech.giveTech("quasiparticles") + // for (let i = 0; i < 1; ++i) tech.giveTech("grappling hook") + // for (let i = 0; i < 1; ++i) tech.giveTech("superdeterminism") // for (let i = 0; i < 1; ++i) tech.giveTech("fine-structure constant") // for (let i = 0; i < 1; ++i) tech.giveTech("nail-bot upgrade") // requestAnimationFrame(() => { for (let i = 0; i < 30; i++) tech.giveTech("laser-bot") }); @@ -49,11 +49,11 @@ const level = { // for (let i = 0; i < 10; i++) powerUps.directSpawn(1750, -500, "research"); // for (let i = 0; i < 10; i++) powerUps.directSpawn(1750, -500, "coupling"); - // level.biohazard(); + // level.testing(); // for (let i = 0; i < 4; ++i) spawn.hopMother(1900, -500) // for (let i = 0; i < 0; ++i) spawn.hopper(1900, -500) // for (let i = 0; i < 1; ++i) spawn.shooterBoss(1900, -2500) - // spawn.suckerBoss(1900, -500, 25) + // spawn.beetleBoss(1900, -500, 25) // spawn.slasher2(2000, -1150) // spawn.zombie(-3000, -500 + 300 * Math.random(), 30, 5, "white") // zombie(x, y, radius, sides, color) // for (let i = 0; i < 20; ++i) spawn.starter(1000 + 1000 * Math.random(), -500 + 300 * Math.random()) @@ -239,6 +239,10 @@ const level = { //
m.field.description = "${m.fieldUpgrades[m.fieldMode].description}" // `, 1200); }, + announceMobTypes() { + simulation.makeTextLog(`spawn.${spawn.pickList[0]}(x,y)`) + simulation.makeTextLog(`spawn.${spawn.pickList[1]}(x,y)`) + }, disableExit: false, nextLevel() { if (!level.disableExit) { @@ -1874,6 +1878,7 @@ const level = { //****************************************************************************************************************** //****************************************************************************************************************** template() { + // level.announceMobTypes() simulation.enableConstructMode() level.setPosToSpawn(0, -50); //normal spawn level.exit.x = 1500; @@ -2375,9 +2380,6 @@ const level = { ctx.fillStyle = "rgba(68, 68, 68,0.95)" ctx.fillRect(2030, 0, 150, 1800); }; - - - level.setPosToSpawn(460, -100); //normal spawn // level.enter.x = -1000000; //hide enter graphic for first level by moving to the far left level.exit.x = 2800; @@ -3451,6 +3453,7 @@ const level = { simulation.draw.drawMapPath = simulation.draw.drawMapSight }, reservoir() { + level.announceMobTypes() level.exit.x = 1700; level.exit.y = -4510; spawn.mapRect(level.exit.x, level.exit.y + 25, 100, 25); @@ -3966,6 +3969,7 @@ const level = { powerUps.addResearchToLevel() //needs to run after mobs are spawned }, factory() { + level.announceMobTypes() // simulation.enableConstructMode() //remove this!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // level.difficultyIncrease(10 * 4) //30 is near max on hard //60 is near max on why @@ -4249,6 +4253,7 @@ const level = { powerUps.spawn(5200, -1300, "ammo"); }, labs() { + level.announceMobTypes() level.isProcedural = true //used in generating text for the level builder level.defaultZoom = 1700 simulation.zoomTransition(level.defaultZoom) @@ -5347,6 +5352,7 @@ const level = { powerUps.addResearchToLevel() //needs to run after mobs are spawned }, pavilion() { + level.announceMobTypes() level.isEndlessFall = true; const vanish = [] level.exit.x = -850; @@ -5499,6 +5505,7 @@ const level = { } }, testChamber() { + level.announceMobTypes() level.setPosToSpawn(0, -50); //lower start level.exit.y = level.enter.y - 550; spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); @@ -5757,6 +5764,7 @@ const level = { }, lock() { + level.announceMobTypes() level.setPosToSpawn(0, -65); //lower start spawn.mapRect(level.enter.x, level.enter.y + 20, 100, 20); level.exit.y = 2010; @@ -6006,6 +6014,7 @@ const level = { powerUps.addResearchToLevel() //needs to run after mobs are spawned }, sewers() { + level.announceMobTypes() const button1 = level.button(6600, 2675) // const hazard = level.hazard(4550, 2750, 4550, 150) const hazard = level.hazard(simulation.isHorizontalFlipped ? -4550 - 4550 : 4550, 2750, 4550, 150) @@ -6196,6 +6205,7 @@ const level = { }, satellite() { + level.announceMobTypes() level.isEndlessFall = true; const boost1 = level.boost(5825, 235, 1400) const elevator = level.elevator(4210, -1265, 380, 50, -3450) //, 0.003, { up: 0.01, down: 0.2 } @@ -6372,6 +6382,7 @@ const level = { } }, rooftops() { + level.announceMobTypes() level.isEndlessFall = true; // level.fallPosition = { x: 5000, y:-4000} const elevator = level.elevator(1450, -990, 235, 45, -2000) @@ -6560,6 +6571,7 @@ const level = { } }, aerie() { + level.announceMobTypes() level.isEndlessFall = true; const boost1 = level.boost(-425, 100, 1400) const boost2 = level.boost(5350, 275, 2850); @@ -6789,6 +6801,7 @@ const level = { } }, skyscrapers() { + level.announceMobTypes() level.isEndlessFall = true; const boost1 = level.boost(475, 0, 1300) const boost2 = level.boost(4450, 0, 1300); @@ -6927,6 +6940,7 @@ const level = { } }, highrise() { + level.announceMobTypes() level.isEndlessFall = true; const elevator1 = level.elevator(-790, -190, 180, 25, -1150, 0.0025, { up: 0.01, @@ -7212,6 +7226,7 @@ const level = { } }, warehouse() { + level.announceMobTypes() level.isEndlessFall = true; level.custom = () => { ctx.fillStyle = "#444" //light fixtures @@ -7531,6 +7546,7 @@ const level = { } }, office() { + level.announceMobTypes() let button, door let isReverse = false if (Math.random() < 0.75) { //normal direction start in top left diff --git a/js/player.js b/js/player.js index 92d7dcd..f269256 100644 --- a/js/player.js +++ b/js/player.js @@ -568,7 +568,7 @@ const m = { if (tech.squirrelFx !== 1) dmg *= 0.78//Math.pow(0.78, (tech.squirrelFx - 1) / 0.4) if (tech.isAddBlockMass && m.isHolding) dmg *= 0.1 if (tech.isSpeedHarm && player.speed > 0.1) dmg *= 1 - Math.min(player.speed * 0.0165, 0.66) - if (tech.isHarmReduce && input.field && m.fieldCDcycle < m.cycle) dmg *= 0.25 + if (tech.isHarmReduce && input.field) dmg *= 0.25 if (tech.isNeutronium && input.field && m.fieldCDcycle < m.cycle) dmg *= 0.1 if (tech.isBotArmor) dmg *= 0.94 ** b.totalBots() if (tech.isHarmArmor && m.lastHarmCycle + 600 > m.cycle) dmg *= 0.33; @@ -2779,14 +2779,10 @@ const m = { effect: () => { m.fieldMeterColor = "#48f" //"#0c5" m.eyeFillColor = m.fieldMeterColor - m.fieldShieldingScale = 0; m.fieldBlockCD = 3; m.grabPowerUpRange2 = 10000000 - m.fieldPosition = { - x: m.pos.x, - y: m.pos.y - } + m.fieldPosition = { x: m.pos.x, y: m.pos.y } m.fieldAngle = m.angle m.perfectPush = (isFree = false) => { if (m.fieldCDcycle < m.cycle) { @@ -2794,10 +2790,7 @@ const m = { if ( Vector.magnitude(Vector.sub(mob[i].position, m.fieldPosition)) - mob[i].radius < m.fieldRange && !mob[i].isUnblockable && - Vector.dot({ - x: Math.cos(m.fieldAngle), - y: Math.sin(m.fieldAngle) - }, Vector.normalise(Vector.sub(mob[i].position, m.fieldPosition))) > m.fieldThreshold && + Vector.dot({ x: Math.cos(m.fieldAngle), y: Math.sin(m.fieldAngle) }, Vector.normalise(Vector.sub(mob[i].position, m.fieldPosition))) > m.fieldThreshold && Matter.Query.ray(map, mob[i].position, m.fieldPosition).length === 0 ) { mob[i].locatePlayer(); @@ -2816,11 +2809,7 @@ const m = { } } if (tech.blockDmg) { //electricity - Matter.Body.setVelocity(mob[i], { - x: 0.5 * mob[i].velocity.x, - y: 0.5 * mob[i].velocity.y - }); - + Matter.Body.setVelocity(mob[i], { x: 0.5 * mob[i].velocity.x, y: 0.5 * mob[i].velocity.y }); if (mob[i].isShielded) { for (let j = 0, len = mob.length; j < len; j++) { if (mob[j].id === mob[i].shieldID) mob[j].damage(tech.blockDmg * m.dmgScale * (tech.isBlockRadiation ? 6 : 2), true) @@ -3684,6 +3673,94 @@ const m = { ctx.strokeStyle = "rgba(255,0,110,0.06)" ctx.stroke(); } + // } else if (true) { //plasma sword slash + // const plasmaSweepCycles = 30 + // m.plasmaSweep = 0 + // m.plasmaSlashDirection = m.flipLegs//Math.random() > 0.5 ? 1 : -1; + // m.hold = function () { + // if (m.isHolding) { + // m.drawHold(m.holdingTarget); + // m.holding(); + // m.throwBlock(); + // m.plasmaSweep = 0 + // // } else if (true) { //not hold but field button is pressed + // } else if (input.field && m.fieldCDcycle < m.cycle) { //not hold but field button is pressed + // if (m.energy > m.fieldRegen) m.energy -= m.fieldRegen + // m.grabPowerUp(); + // m.lookForPickUp(); + + // //graphics + // if (m.plasmaSweep === 0) m.plasmaSlashDirection = m.flipLegs //Math.random() > 0.5 ? 1 : -1; + // const angle = m.angle //+ 1 * (m.plasmaSweep - plasmaSweepCycles / 2) / plasmaSweepCycles * m.plasmaSlashDirection + // const plasmaSweepCapped = Math.min(m.plasmaSweep, plasmaSweepCycles - 8) / plasmaSweepCycles + // const range = 100 * plasmaSweepCapped + // const arc = 1.3 + // const A = { x: m.pos.x + range * Math.cos(angle - arc), y: m.pos.y + range * Math.sin(angle - arc) } + // const B = { x: m.pos.x + range * Math.cos(angle + arc), y: m.pos.y + range * Math.sin(angle + arc) } + // const controlRange = 500 * plasmaSweepCapped + // const AC = { x: m.pos.x + controlRange * Math.cos(angle - arc / 2), y: m.pos.y + controlRange * Math.sin(angle - arc / 2) } + // const BC = { x: m.pos.x + controlRange * Math.cos(angle + arc / 2), y: m.pos.y + controlRange * Math.sin(angle + arc / 2) } + // const innerControlRange = 300 * plasmaSweepCapped + // const ACinner = { x: m.pos.x + innerControlRange * Math.cos(angle - arc / 2), y: m.pos.y + innerControlRange * Math.sin(angle - arc / 2) } + // const BCinner = { x: m.pos.x + innerControlRange * Math.cos(angle + arc / 2), y: m.pos.y + innerControlRange * Math.sin(angle + arc / 2) } + // ctx.beginPath(); + // ctx.moveTo(A.x, A.y) + // ctx.bezierCurveTo(AC.x, AC.y, BC.x, BC.y, B.x, B.y); //outer curve + // ctx.bezierCurveTo(BCinner.x, BCinner.y, ACinner.x, ACinner.y, A.x, A.y); //inner curve + // // ctx.strokeStyle = "#000" + // // ctx.stroke(); + // ctx.fillStyle = "rgba(255,0,255,0.5)" + // ctx.fill(); + + // //draw control points for graphics reference + // ctx.lineWidth = '0.5' + // ctx.beginPath(); + // ctx.arc(A.x, A.y, 5, 0, 2 * Math.PI); + // ctx.stroke(); + // ctx.beginPath(); + // ctx.arc(B.x, B.y, 5, 0, 2 * Math.PI); + // ctx.stroke(); + // ctx.beginPath(); + // ctx.arc(AC.x, AC.y, 5, 0, 2 * Math.PI); + // ctx.stroke(); + // ctx.beginPath(); + // ctx.arc(BC.x, BC.y, 5, 0, 2 * Math.PI); + // ctx.stroke(); + // ctx.beginPath(); + // ctx.arc(ACinner.x, ACinner.y, 5, 0, 2 * Math.PI); + // ctx.stroke(); + // ctx.beginPath(); + // ctx.arc(BCinner.x, BCinner.y, 5, 0, 2 * Math.PI); + // ctx.stroke(); + + // //mob collision detection + // collideRange = 160 + // const collideCenter = { + // x: m.pos.x + collideRange * Math.cos(angle), + // y: m.pos.y + collideRange * Math.sin(angle) + // } + // ctx.beginPath(); + // ctx.arc(collideCenter.x, collideCenter.y, 140, 0, 2 * Math.PI); + // ctx.stroke(); + + // //push mob away and slow them? + + + // //sweeping motion and cooldown + // m.plasmaSweep++ + // if (m.plasmaSweep > plasmaSweepCycles) { + // m.plasmaSweep = 0 + // if (m.fireCDcycle < m.cycle + 30) m.fieldCDcycle = m.cycle + 30 + // } + // } else if (m.holdingTarget && m.fieldCDcycle < m.cycle) { //holding, but field button is released + // m.pickUp(); + // m.plasmaSweep = 0 + // } else { + // m.plasmaSweep = 0 + // m.holdingTarget = null; //clears holding target (this is so you only pick up right after the field button is released and a hold target exists) + // } + // m.drawRegenEnergy("rgba(0, 0, 0, 0.2)") + // } } else { m.hold = function () { if (m.isHolding) { @@ -4864,6 +4941,31 @@ const m = { // m.drawRegenEnergy() // }, }, + { + name: "grappling hook", + // description: `use energy to pull yourself towards the map
generate 6 energy per second`, + description: `use energy to fire a hook that attaches to map,
pulls player, damages mobs, and destroys blocks
generate 6 energy per second`, + effect: () => { + m.fieldFire = true; + // m.holdingMassScale = 0.01; //can hold heavier blocks with lower cost to jumping + m.fieldMeterColor = "#333" + m.eyeFillColor = m.fieldMeterColor + m.fieldHarmReduction = 0.45; //55% reduction + + m.hold = function () { + if (input.field) { + if (m.fieldCDcycle < m.cycle) { + if (m.energy > 0.02) m.energy -= 0.02 + const where = { x: m.pos.x + 40 * Math.cos(m.angle), y: m.pos.y + 40 * Math.sin(m.angle) } + b.grapple(where, m.angle) + if (m.fieldCDcycle < m.cycle + 20) m.fieldCDcycle = m.cycle + 20 + } + m.grabPowerUp(); + } + m.drawRegenEnergy() + } + } + }, ], //************************************************************************************ //************************************************************************************ diff --git a/js/powerup.js b/js/powerup.js index 934a523..1b27ff1 100644 --- a/js/powerup.js +++ b/js/powerup.js @@ -914,54 +914,57 @@ const powerUps = { for (let i = 0; i < b.guns.length; i++) { if (!b.guns[i].have) options.push(i); } - let totalChoices = Math.min(options.length, (tech.isDeterminism ? 1 : 2 + tech.extraChoices + 2 * (m.fieldMode === 8))) - if (tech.isFlipFlopChoices) totalChoices += tech.isRelay ? (tech.isFlipFlopOn ? -1 : 7) : (tech.isFlipFlopOn ? 7 : -1) //flip the order for relay - function removeOption(index) { - for (let i = 0; i < options.length; i++) { - if (options[i] === index) { - options.splice(i, 1) //remove a previous choice from option pool - return + // console.log(options.length) + if (options.length > 0 || !tech.isSuperDeterminism) { + let totalChoices = Math.min(options.length, (tech.isDeterminism ? 1 : 2 + tech.extraChoices + 2 * (m.fieldMode === 8))) + if (tech.isFlipFlopChoices) totalChoices += tech.isRelay ? (tech.isFlipFlopOn ? -1 : 7) : (tech.isFlipFlopOn ? 7 : -1) //flip the order for relay + function removeOption(index) { + for (let i = 0; i < options.length; i++) { + if (options[i] === index) { + options.splice(i, 1) //remove a previous choice from option pool + return + } } } - } - //check for guns that were a choice last time and remove them - for (let i = 0; i < b.guns.length; i++) { - if (options.length - 1 < totalChoices) break //you have to repeat choices if there are not enough choices left to display - if (b.guns[i].isRecentlyShown) removeOption(i) - } - for (let i = 0; i < b.guns.length; i++) b.guns[i].isRecentlyShown = false //reset recently shown back to zero - // if (options.length > 0) { - let text = powerUps.buildColumns(totalChoices, "gun") - for (let i = 0; i < totalChoices; i++) { - const choose = options[Math.floor(Math.seededRandom(0, options.length))] //pick an element from the array of options - // text += `

  ${b.guns[choose].name}
${b.guns[choose].description}
` - text += powerUps.gunText(choose, `powerUps.choose('gun',${choose})`) - - b.guns[choose].isRecentlyShown = true - removeOption(choose) - if (options.length < 1) break - } - if (tech.isExtraBotOption) { - const botTech = [] //make an array of bot options - for (let i = 0, len = tech.tech.length; i < len; i++) { - if (tech.tech[i].isBotTech && tech.tech[i].count < tech.tech[i].maxCount && tech.tech[i].allowed()) botTech.push(i) + //check for guns that were a choice last time and remove them + for (let i = 0; i < b.guns.length; i++) { + if (options.length - 1 < totalChoices) break //you have to repeat choices if there are not enough choices left to display + if (b.guns[i].isRecentlyShown) removeOption(i) } - if (botTech.length > 0) { //pick random bot tech - // const choose = botTech[Math.floor(Math.random() * botTech.length)]; - // const isCount = tech.tech[choose].count > 0 ? `(${tech.tech[choose].count+1}x)` : ""; - // text += `
⭓▸●■   ${tech.tech[choose].name} ${isCount}
${tech.tech[choose].descriptionFunction ? tech.tech[choose].descriptionFunction() : tech.tech[choose].description}
` - const choose = botTech[Math.floor(Math.random() * botTech.length)]; - const techCountText = tech.tech[choose].count > 0 ? `(${tech.tech[choose].count + 1}x)` : ""; - const style = localSettings.isHideImages ? powerUps.hideStyle : `style="background-image: url('img/${tech.tech[choose].name}.webp');"` - text += `
+ for (let i = 0; i < b.guns.length; i++) b.guns[i].isRecentlyShown = false //reset recently shown back to zero + // if (options.length > 0) { + let text = powerUps.buildColumns(totalChoices, "gun") + for (let i = 0; i < totalChoices; i++) { + const choose = options[Math.floor(Math.seededRandom(0, options.length))] //pick an element from the array of options + // text += `
  ${b.guns[choose].name}
${b.guns[choose].description}
` + text += powerUps.gunText(choose, `powerUps.choose('gun',${choose})`) + + b.guns[choose].isRecentlyShown = true + removeOption(choose) + if (options.length < 1) break + } + if (tech.isExtraBotOption) { + const botTech = [] //make an array of bot options + for (let i = 0, len = tech.tech.length; i < len; i++) { + if (tech.tech[i].isBotTech && tech.tech[i].count < tech.tech[i].maxCount && tech.tech[i].allowed()) botTech.push(i) + } + if (botTech.length > 0) { //pick random bot tech + // const choose = botTech[Math.floor(Math.random() * botTech.length)]; + // const isCount = tech.tech[choose].count > 0 ? `(${tech.tech[choose].count+1}x)` : ""; + // text += `
⭓▸●■   ${tech.tech[choose].name} ${isCount}
${tech.tech[choose].descriptionFunction ? tech.tech[choose].descriptionFunction() : tech.tech[choose].description}
` + const choose = botTech[Math.floor(Math.random() * botTech.length)]; + const techCountText = tech.tech[choose].count > 0 ? `(${tech.tech[choose].count + 1}x)` : ""; + const style = localSettings.isHideImages ? powerUps.hideStyle : `style="background-image: url('img/${tech.tech[choose].name}.webp');"` + text += `
⭓▸●■   ${tech.tech[choose].name} ${techCountText}
${tech.tech[choose].descriptionFunction ? tech.tech[choose].descriptionFunction() : tech.tech[choose].description}
` + } } + if (tech.isOneGun && b.inventory.length > 0) text += `
replaces your current gun
` + document.getElementById("choose-grid").innerHTML = text + powerUps.showDraft(); } - if (tech.isOneGun && b.inventory.length > 0) text += `
replaces your current gun
` - document.getElementById("choose-grid").innerHTML = text - powerUps.showDraft(); // } } }, diff --git a/js/tech.js b/js/tech.js index 50edafc..9c4a5cb 100644 --- a/js/tech.js +++ b/js/tech.js @@ -232,7 +232,7 @@ const tech = { // } // } if (tech.isDivisor && b.activeGun && b.guns[b.activeGun].ammo % 3 === 0) dmg *= 1.77 - if (tech.isNoGroundDamage) dmg *= m.onGround ? 0.78 : 1.88 + if (tech.isNoGroundDamage) dmg *= m.onGround ? 0.75 : 2 if (tech.isDilate) dmg *= 1.5 + 0.6 * Math.sin(m.cycle * 0.0075) if (tech.isGunChoice && tech.buffedGun === b.inventoryGun) dmg *= 1 + 0.31 * b.inventory.length if (powerUps.boost.endCycle > m.cycle) dmg *= 1 + powerUps.boost.damage @@ -955,9 +955,9 @@ const tech = { frequency: 1, frequencyDefault: 1, allowed() { - return !m.isShipMode && !tech.isAlwaysFire, !tech.isGrapple + return !m.isShipMode && !tech.isAlwaysFire }, - requires: "not ship mode, automatic, grappling hook", + requires: "not ship mode, automatic", effect() { tech.isFireMoveLock = true; b.setFireCD(); @@ -6682,10 +6682,10 @@ const tech = { }, requires: "foam", effect() { - tech.foamDamage += 0.011 * 0.43 + tech.foamDamage += 0.01 * 0.43 }, remove() { - tech.foamDamage = 0.011; + tech.foamDamage = 0.01; } }, { @@ -6831,9 +6831,9 @@ const tech = { frequency: 2, frequencyDefault: 2, allowed() { - return tech.haveGunCheck("harpoon") && !tech.isFilament && !tech.isHarpoonPowerUp && !tech.isGrapple && !tech.isBoostReplaceAmmo + return tech.haveGunCheck("harpoon") && !tech.isFilament && !tech.isHarpoonPowerUp && !tech.isBoostReplaceAmmo }, - requires: "harpoon, not UHMWPE, induction furnace, grappling hook, quasiparticles", + requires: "harpoon, not UHMWPE, induction furnace, quasiparticles", ammoBonus: 9, effect() { tech.isRailGun = true; @@ -6852,52 +6852,52 @@ const tech = { } } }, - { - name: "grappling hook", - description: `harpoons attach to the map and pull you
your rope extends while holding fire`, - isGunTech: true, - maxCount: 1, - count: 0, - frequency: 2, - frequencyDefault: 2, - allowed() { - return tech.haveGunCheck("harpoon") && !tech.isFilament && !tech.isHarpoonPowerUp && !tech.isRailGun && !tech.isFireMoveLock - }, - requires: "harpoon, not railgun, UHMWPE, induction furnace, Higgs mechanism", - effect() { - tech.isGrapple = true; - for (i = 0, len = b.guns.length; i < len; i++) { //find which gun - if (b.guns[i].name === "harpoon") b.guns[i].chooseFireMethod() - } - }, - remove() { - if (tech.isGrapple) { - tech.isGrapple = false; - for (i = 0, len = b.guns.length; i < len; i++) { //find which gun - if (b.guns[i].name === "harpoon") b.guns[i].chooseFireMethod() - } - } - } - }, - { - name: "bulk modulus", - description: `while grappling become invulnerable
drain energy`, - isGunTech: true, - maxCount: 1, - count: 0, - frequency: 2, - frequencyDefault: 2, - allowed() { - return tech.haveGunCheck("harpoon") && tech.isGrapple && !tech.isRailEnergy - }, - requires: "grappling hook, not alternator", - effect() { - tech.isImmuneGrapple = true; - }, - remove() { - tech.isImmuneGrapple = false - } - }, + // { + // name: "grappling hook", + // description: `harpoons attach to the map and pull you
your rope extends while holding fire`, + // isGunTech: true, + // maxCount: 1, + // count: 0, + // frequency: 2, + // frequencyDefault: 2, + // allowed() { + // return tech.haveGunCheck("harpoon") && !tech.isFilament && !tech.isHarpoonPowerUp && !tech.isRailGun && !tech.isFireMoveLock + // }, + // requires: "harpoon, not railgun, UHMWPE, induction furnace, Higgs mechanism", + // effect() { + // tech.isGrapple = true; + // for (i = 0, len = b.guns.length; i < len; i++) { //find which gun + // if (b.guns[i].name === "harpoon") b.guns[i].chooseFireMethod() + // } + // }, + // remove() { + // if (tech.isGrapple) { + // tech.isGrapple = false; + // for (i = 0, len = b.guns.length; i < len; i++) { //find which gun + // if (b.guns[i].name === "harpoon") b.guns[i].chooseFireMethod() + // } + // } + // } + // }, + // { + // name: "bulk modulus", + // description: `while grappling become invulnerable
drain energy`, + // isGunTech: true, + // maxCount: 1, + // count: 0, + // frequency: 2, + // frequencyDefault: 2, + // allowed() { + // return tech.haveGunCheck("harpoon") && !tech.isRailEnergy + // }, + // requires: "not alternator", + // effect() { + // tech.isImmuneGrapple = true; + // }, + // remove() { + // tech.isImmuneGrapple = false + // } + // }, { name: "alternator", description: "+90% harpoon energy efficiency", @@ -6907,9 +6907,9 @@ const tech = { frequency: 2, frequencyDefault: 2, allowed() { - return tech.haveGunCheck("harpoon") && !tech.isImmuneGrapple + return tech.haveGunCheck("harpoon") }, - requires: "harpoon, not bulk modulus", + requires: "harpoon", effect() { tech.isRailEnergy = true; }, @@ -6994,9 +6994,9 @@ const tech = { frequency: 2, frequencyDefault: 2, allowed() { - return tech.haveGunCheck("harpoon") && !tech.isRailGun && !tech.isGrapple + return tech.haveGunCheck("harpoon") && !tech.isRailGun }, - requires: "harpoon, not grappling hook, railgun", + requires: "harpoon, not railgun", effect() { tech.isFilament = true; }, @@ -7013,9 +7013,9 @@ const tech = { frequency: 2, frequencyDefault: 2, allowed() { - return tech.haveGunCheck("harpoon") && !tech.isRailGun && !tech.isGrapple + return tech.haveGunCheck("harpoon") && !tech.isRailGun }, - requires: "harpoon, not grappling hook, railgun", + requires: "harpoon, not railgun", effect() { tech.isHarpoonPowerUp = true }, @@ -7657,9 +7657,9 @@ const tech = { frequency: 3, frequencyDefault: 3, allowed() { - return (m.fieldMode === 8 || m.fieldMode === 2 || m.fieldMode === 3) && (build.isExperimentSelection || powerUps.research.count > 3) + return (m.fieldMode === 8 || m.fieldMode === 2 || m.fieldMode === 3 || m.fieldMode === 10) && (build.isExperimentSelection || powerUps.research.count > 3) }, - requires: "perfect diamagnetism, negative mass, pilot wave", + requires: "perfect diamagnetism, negative mass, grappling hook, pilot wave", effect() { tech.isFieldHarmReduction = true for (let i = 0; i < 2; i++) { @@ -7706,9 +7706,9 @@ const tech = { frequency: 2, frequencyDefault: 2, allowed() { - return (m.fieldMode === 8 || m.fieldMode === 3) && !tech.isCloakHealLastHit + return m.fieldMode === 8 || m.fieldMode === 3 }, - requires: "negative mass, pilot wave, not patch", + requires: "negative mass, pilot wave", effect() { tech.lastHitDamage += 4; }, @@ -7746,16 +7746,16 @@ const tech = { }, { name: "aerostat", - description: `+88% damage while off the ground
-22% damage while on the ground`, + description: `+100% damage while off the ground
-25% damage while on the ground`, isFieldTech: true, maxCount: 1, count: 0, frequency: 2, frequencyDefault: 2, allowed() { - return m.fieldMode === 3 + return m.fieldMode === 3 || m.fieldMode === 10 }, - requires: "negative mass", + requires: "negative mass, grappling hook", effect() { tech.isNoGroundDamage = true }, @@ -7831,9 +7831,9 @@ const tech = { isBotTech: true, isNonRefundable: true, allowed() { - return powerUps.research.count > 1 && (m.fieldMode === 4 || m.fieldMode === 8) + return powerUps.research.count > 1 && (m.fieldMode === 4 || m.fieldMode === 10 || m.fieldMode === 8) }, - requires: "molecular assembler, pilot wave", + requires: "molecular assembler, grappling hook, pilot wave", effect() { for (let i = 0; i < 2; i++) { if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1) @@ -7856,9 +7856,9 @@ const tech = { isBotTech: true, isNonRefundable: true, allowed() { - return powerUps.research.count > 2 && (m.fieldMode === 4 || m.fieldMode === 8) + return powerUps.research.count > 2 && (m.fieldMode === 4 || m.fieldMode === 10 || m.fieldMode === 8) }, - requires: "molecular assembler, pilot wave", + requires: "molecular assembler, grappling hook, pilot wave", effect() { for (let i = 0; i < 3; i++) { if (powerUps.research.count > 0) powerUps.research.changeRerolls(-1) @@ -8121,9 +8121,9 @@ const tech = { frequency: 2, frequencyDefault: 2, allowed() { - return (m.fieldMode === 5 || m.fieldMode === 4 || m.fieldMode === 2 || m.fieldMode === 8) + return (m.fieldMode === 10 || m.fieldMode === 4 || m.fieldMode === 8) }, - requires: "molecular assembler, plasma torch, perfect diamagnetism, pilot wave", + requires: "plasma torch, grappling hook, pilot wave", effect() { tech.isHarmReduce = true }, @@ -8479,9 +8479,9 @@ const tech = { frequency: 2, frequencyDefault: 2, allowed() { - return m.fieldMode === 7 && !tech.lastHitDamage && !tech.isEnergyHealth + return m.fieldMode === 7 && !tech.isEnergyHealth }, - requires: "metamaterial cloaking, not dynamic equilibrium, mass-energy", + requires: "metamaterial cloaking, not mass-energy", effect() { tech.isCloakHealLastHit = true; }, @@ -8586,9 +8586,9 @@ const tech = { frequency: 3, frequencyDefault: 3, allowed() { - return (m.fieldMode === 8 || m.fieldMode === 3 || m.fieldMode === 6 || m.fieldMode === 9) && (build.isExperimentSelection || powerUps.research.count > 2) + return (m.fieldMode === 8 || m.fieldMode === 6 || m.fieldMode === 9 || m.fieldMode === 10) && (build.isExperimentSelection || powerUps.research.count > 2) }, - requires: "wormhole, time dilation, negative mass, pilot wave", + requires: "wormhole, time dilation, negative mass, pilot wave, grappling hook", effect() { tech.fieldDuplicate = 0.11 powerUps.setPowerUpMode(); //needed after adjusting duplication chance @@ -10982,6 +10982,30 @@ const tech = { }, remove() { } }, + { + name: "mobs!", + descriptionFunction() { + if (this.mobType === "") this.mobType = spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)] + return `spawn 20 ${this.mobType} mobs` + }, + maxCount: 1, + count: 0, + frequency: 0, + isNonRefundable: true, + isJunk: true, + allowed() { return true }, + requires: "", + mobType: "", + effect() { + if (this.mobType === "") this.mobType = spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)] + for (let i = 0; i < 20; i++) { + spawn[this.mobType](m.pos.x, m.pos.y - 700) + } + simulation.makeTextLog(`spawn.${this.mobType}(x,y)`) + + }, + remove() { } + }, { name: "black hole cluster", description: `spawn 30 nearby black holes`, @@ -11718,8 +11742,8 @@ const tech = { isTimeCrystals: null, isGroundState: null, isRailGun: null, - isGrapple: null, - isImmuneGrapple: null, + // isGrapple: null, + // isImmuneGrapple: null, isDronesTravel: null, isTechDebt: null, isPlasmaBall: null, diff --git a/js/visibility.js b/js/visibility.js deleted file mode 100644 index d1686b7..0000000 --- a/js/visibility.js +++ /dev/null @@ -1,44 +0,0 @@ -// https://ncase.me/sight-and-light/ -// redblobgames.com/articles/visibility -// https://github.com/Silverwolf90/2d-visibility/tree/master/src -// could apply to explosions, neutron bomb, player LOS - - -const v = { - points: [], - populate() { - v.points = [{ - x: -150, - y: -950 - }, { - x: 710, - y: -950 - }, { - x: 710, - y: -940 - }, { - x: 710, - y: -710 - }, { - x: 710, - y: -700 - }, { - x: -150, - y: -700 - }] - }, - draw() { - ctx.beginPath(); - ctx.moveTo(v.points[0].x, v.points[0].y) - for (let i = 0, len = v.points.length; i < len; i++) { - ctx.lineTo(v.points[i].x, v.points[i].y) - } - // ctx.fillStyle = "#333" - ctx.globalCompositeOperation = "destination-in"; - ctx.fill(); - ctx.globalCompositeOperation = "source-over"; - ctx.clip(); - } -} -v.populate(); -// console.log(v.points) \ No newline at end of file diff --git a/todo.txt b/todo.txt index 32e22e5..76a7a9c 100644 --- a/todo.txt +++ b/todo.txt @@ -1,20 +1,58 @@ ******************************************************** NEXT PATCH ************************************************** -new community map - LaunchSite by Des Boot +grappling hook is now a field (work in progress) + reworked physics to allow faster speeds, but more control + improved rate of power up grabbing + more player control to hook retraction rate + changed hook shape and field image graphics + grappling hook field coupling, more tech, bug fixes, and general polish to be added soon -added a short color animation after grabbing basic power ups +aerostat - 88->100% damage in air 22-> 25% damage on ground +foam damage reduced 10%, ammo increased about 10% +after hitting an invulnerable mob (drones,spores,worms,iceIX,fleas) don't die or lose cycles +added JUNK tech: mobs! - summon 20 random mobs +added announcement of mob names in console at start of new level -reduced overall damage done to player by ~6% -commodities exchange spawns 5-10 -> 6-12 power ups on cancel -residual dipolar coupling spawns 5 -> 6 coupling power ups - -bots maintain relative position to player after the no camera tracking teleport - for portals and falling off level - -the once every 7 seconds stuck check now also check to see if you stay stuck for 3 seconds before resetting you. +bug fixes *********************************************************** TODO ***************************************************** +grappling hook is a field + check for places that the player could get into but not out of + maybe grapple could grab more then 1 power up? + grapple slices blocks + cut large blocks into 2,3 + use dead mob code for mobs > 5 sides + write code to cut large blocks in half and remove one half + need several field tech + new field tech ideas + increase hook damage + hook damage aura + hook's line does damage + make several auto targeting harpoons after taking damage + how to make them not drain energy + generate ___ after destroying blocks + energy, drones, iceIX, explosion, nails, junk bots? + coupling effect: defense?, bonus from ammo power ups, fire rate + +tech - killing a mob heals for the last damage you took + disable cloaking heal? maybe you don't need to disable, just don't heal twice + heal for 50%? + heal from mob damage or from kills? + +make phonon the default wave gun type and make a tech to switch to the normal wave beam + nerf phonon, buff wave + +sword slash for plasma torch (giving up on this for now, had trouble making graphics look good) + activates when mouse is close to player + gradual activation + sharp cut off + use length of torch as cut off length + make it look like hollow knight slash + what about upgrades to extruder,plasma ball + give them their own version of a slash? + make a tech that buffs the slash, but it disables extruder,plasma ball + more (all) bosses need to be made of parts good examples: spiderBoss, dragonFlyBoss, beetleBoss methods: